Support sslcert/sslkey client certificate authentication for PostgreSQL - #441
Merged
Merged
Conversation
PostgreSQL servers that enforce certificate authentication in pg_hba.conf (`hostssl ... cert`, `clientcert=verify-*`) reject DBHub with "connection requires a valid client certificate" because the DSN parser only read sslmode and sslrootcert. Add the standard libpq `sslcert` / `sslkey` parameters, accepted both as DSN query params and as TOML source fields. Connector (src/connectors/postgres/index.ts): - Read sslcert/sslkey and pass the PEM contents to node-postgres as TLS `cert`/`key`, on top of whatever the sslmode branch already built (require keeps rejectUnauthorized=false; verify-* keep CA handling). - Fail fast on an inconsistent DSN: one of the pair without the other, or a client cert with sslmode=disable / unset (node-postgres would silently drop it on a plaintext connection). Encrypted PEM keys are rejected with a clear message instead of Node's opaque decoder error. - Share the ~/ expansion + FailedToReadCertificate wrapping across all three SSL files. TOML loader (src/config/toml-loader.ts): - New sslcert/sslkey fields: PostgreSQL only, both-or-neither, sslmode in require/verify-ca/verify-full, file must exist and be readable. - ~/ expansion, backfill from DSN, DSN/field conflict check, and DSN building all mirror sslrootcert via shared helpers. Tests cover the parser and loader rules, plus a new Testcontainers suite that boots Postgres with `hostssl all all all cert` and verifies a cert connection succeeds (client_dn = CN=user) and a cert-less one is refused. Docs: docs/config/toml.mdx, docs/config/command-line.mdx, dbhub.toml.example, CLAUDE.md. Closes #439 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UzToxWXzC7oQ9xMnhM192
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
Only minor documentation and comment alignment nits remain; no blocking issues were identified.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
Adds PostgreSQL client certificate authentication through sslcert/sslkey DSN parameters and TOML settings.
Changes:
- Adds validation, PEM loading, TLS integration, and encrypted-key detection.
- Extends TOML parsing, DSN generation, and configuration types.
- Adds unit/integration tests and documentation updates.
| File | Description |
|---|---|
src/types/config.ts |
Adds certificate configuration fields. |
src/connectors/postgres/index.ts |
Loads and applies client certificates. |
src/connectors/postgres/failed-to-read-certificate.ts |
Expands certificate error handling. |
src/connectors/__tests__/postgres-client-cert.integration.test.ts |
Tests PostgreSQL certificate authentication. |
src/connectors/__tests__/dsn-parser.test.ts |
Tests DSN certificate parsing and validation. |
src/config/toml-loader.ts |
Validates and merges certificate settings. |
src/config/__tests__/toml-loader.test.ts |
Tests TOML certificate configuration. |
docs/config/toml.mdx |
Documents TOML certificate settings. |
docs/config/command-line.mdx |
Documents DSN parameters. |
dbhub.toml.example |
Adds certificate configuration examples. |
CLAUDE.md |
Updates PostgreSQL configuration guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…riction Address review: CLAUDE.md and the postgresSslFileParams comment said client certificates apply in "any TLS mode", but disable and an unset sslmode are rejected. Name the three accepted modes explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UzToxWXzC7oQ9xMnhM192
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes bytebase/dbhub#439
Problem
PostgreSQL servers that enforce certificate authentication in
pg_hba.conf(hostssl ... cert,clientcert=verify-ca/verify-full) reject DBHub withFATAL: connection requires a valid client certificate. The DSN parser only readsslmodeandsslrootcert; the standard libpqsslcert/sslkeyparameters were silently dropped and the TOML config had no matching fields.Change
sslcertandsslkeyare now accepted as DSN query parameters and as TOML[[sources]]fields, mirroring howsslrootcertworks:Rules
sslmodemust berequire,verify-caorverify-full. libpq sends the client cert in every SSL mode, but node-postgres only negotiates TLS in these modes, so a cert withdisable/ nosslmodewould be silently ignored. It is rejected instead.sslrootcert:require+ client cert authenticates the client without verifying the server.sslpasswordis left for a follow-up.Connector (
src/connectors/postgres/index.ts): reads the two params, passes the PEM contents to node-postgres as TLScert/keyon top of whatever thesslmodebranch already built (requirekeepsrejectUnauthorized: false;verify-*keep CA handling). Validation lives in the parser as well as the TOML loader so--dsn/DSNenv get the same checks. The~/expansion andFailedToReadCertificatewrapping are shared across all three SSL files.TOML loader (
src/config/toml-loader.ts): new fields with type / both-or-neither / sslmode / file-readable validation;~/expansion; backfill from DSN; DSN-vs-field conflict check; and DSN building. Thesslrootcertfile check and DSN-param emission were pulled into shared helpers (validateReadableFile,postgresSslFileParams) rather than copied three times.Docs:
docs/config/toml.mdx,docs/config/command-line.mdx,dbhub.toml.example,CLAUDE.md.Tests
dsn-parser.test.ts: cert/key under each TLS mode, alongsidesslrootcert,~expansion, one-without-the-other,disable/ unset sslmode, missing files, encrypted PKCS#8 and legacy PEM keys.toml-loader.test.ts: accepted with each TLS mode and withsslrootcert, rejected for MySQL / one-without-the-other /disable/ unset / missing file / directory, DSN backfill, DSN conflict and~-equal non-conflict, DSN merge and build with percent-encoding and no duplication.postgres-client-cert.integration.test.ts: generates a CA + server + client cert withopenssl, bootspostgres:15-alpinewithssl=onandhostssl all all all cert, and checks that a cert connection succeeds (pg_stat_ssl.client_dncontainsCN=testuser) under bothverify-caandrequire, and that a cert-less connection is refused. Skipped ifopensslis not installed.Verified locally:
pnpm test:unit(1150 passed),pnpm run build. The container I worked in has no Docker, so the integration suite is validated by CI on this PR rather than locally.Out of scope
sslpassword(encrypted keys)🤖 Generated with Claude Code
https://claude.ai/code/session_012UzToxWXzC7oQ9xMnhM192
Generated by Claude Code