Skip to content

feat(tls): present client certificate to upstream servers for mTLS#1173

Open
andy-paine wants to merge 1 commit into
pgdogdev:mainfrom
incident-io:andypaine/upstream-client-cert
Open

feat(tls): present client certificate to upstream servers for mTLS#1173
andy-paine wants to merge 1 commit into
pgdogdev:mainfrom
incident-io:andypaine/upstream-client-cert

Conversation

@andy-paine

Copy link
Copy Markdown

Summary

PgDog can validate an upstream Postgres server's certificate (tls_verify + tls_server_ca_certificate) but has no way to present its own client certificate on that connection. This means it can't connect to servers that require mutual TLS — e.g. Cloud SQL with "require trusted client certificate", or any Postgres configured with clientcert=verify-full.

This adds the missing upstream leg. Two new settings, named to mirror the existing tls_server_ca_certificate:

  • tls_server_certificate — the client certificate PgDog presents to the server
  • tls_server_private_key — its private key

(also available as PGDOG_TLS_SERVER_CERTIFICATE / PGDOG_TLS_SERVER_PRIVATE_KEY.)

Closes #890

Implementation

  • The certificate + key are loaded exactly the way build_acceptor loads PgDog's own server certificate (CertificateDer::from_pem_file / PrivateKeyDer::from_pem_file), then attached via rustls with_client_auth_cert across every tls_verify mode (disabled / prefer / verify_ca / verify_full).
  • Setting only one of the two is rejected at load time, so misconfigurations surface on reload rather than at connection time.
  • The upstream connector cache key now includes the client cert/key paths, so a cached no-client-auth connector isn't reused after a cert is configured.

Tests

  • connector_requires_both_client_cert_and_key — both-or-neither validation.
  • client_cert_is_part_of_connector_cache_key — adding a client cert rebuilds the connector; identical inputs reuse the cache.
  • test_upstream_client_cert_config_parses (pgdog-config) — the new keys parse and are optional.

Note on the schema diff

Regenerating .schema/pgdog.schema.json via pgdog-jsonschema (required by the jsonschema CI check) also corrected a small pre-existing drift on main: the top-level array key was query_parser in the committed schema but the source field is query_parsers. That correction is unrelated to this change but is needed for the schema check to pass.

PgDog could validate an upstream Postgres server's certificate
(`tls_verify` + `tls_server_ca_certificate`) but had no way to present
its own client certificate on that connection, so it could not satisfy
servers that require mutual TLS (e.g. Cloud SQL with "require trusted
client certificate", or any Postgres using `clientcert=verify-full`).

Add two settings, mirroring the existing `tls_server_ca_certificate`
naming for the upstream leg:

  - tls_server_certificate: client cert PgDog presents to the server
  - tls_server_private_key: its private key

The certificate is loaded as a full chain (leaf + intermediates) and
attached to the client config across every `tls_verify` mode. Setting
only one of the two is rejected at load time so misconfigurations
surface on reload rather than at connection time.

Closes pgdogdev#890

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mTLS between pgdog and db server

2 participants