fix(postgres): follow libpq sslmode semantics instead of always verifying - #25
Merged
Merged
Conversation
…ying Every sslmode except `disable` verified the server certificate against the OS trust store. That made `prefer` — the default, and what a URL inherits when it says nothing — unable to reach a managed database at all: RDS/Aurora, Cloud SQL and Azure present a certificate signed by the provider's own private CA, which no OS trust store carries, so the handshake failed with UnknownIssuer before the first query. libpq, and therefore the asyncpg/psycopg callers this ORM is a drop -in for, authenticates the server only under `verify-ca` and `verify-full`; `prefer` and `require` encrypt and nothing more. `prefer` and `require` now use a verifier that accepts the presented certificate. The connection is still TLS — the handshake signature is still checked against that certificate's key — and only the question of who the certificate belongs to goes unasked, exactly as under libpq. Verification stays available, and gains the spelling libpq uses for it: `sslmode=verify-ca` (chain) and `sslmode=verify-full` (chain and hostname), with `sslrootcert=<path>` to add a provider's CA bundle to the trust store. tokio-postgres parses neither spelling, so both are consumed here and handed to the driver as `sslmode=require`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PrJLyTdr7CMvrzxENq3NfP
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.
The bug
Every
sslmodeexceptdisableverified the server certificate against the OStrust store:
That makes
prefer— the default, and what a URL inherits when it says nothingabout TLS — unable to reach a managed PostgreSQL at all. Amazon RDS/Aurora,
Cloud SQL and Azure all present a certificate signed by the provider's own
private CA, which no OS trust store carries, so the connection dies in the
handshake with
UnknownIssuerbefore the first query:libpq — and therefore asyncpg and psycopg, which callers migrate from —
authenticates the server only under
verify-caandverify-full.preferandrequireencrypt and nothing more;requirepromises TLS, not identity. Acodebase swapping asyncpg for yara-orm keeps its URLs and loses its database.
The fix
preferandrequirenow use a verifier that accepts the presentedcertificate. The connection is still TLS, and the handshake signature is still
checked against that certificate's key — only the question of whose
certificate it is goes unasked, exactly as under libpq.
Verification stays available and gains the spelling libpq uses for it:
sslmodedisableprefer(default)requireverify-caverify-fullsslrootcert=<path>adds a PEM file's roots to the trust store for theverifying modes — a provider's CA bundle, typically. As in libpq it does not by
itself turn verification on; an unreadable file, or one holding no certificate,
is a configuration error rather than a silent fallback to the OS roots.
tokio-postgres parses neither
verify-*spelling norsslrootcert, so both areconsumed by the engine and the driver is handed
sslmode=require.verify-caexists for reaching a database through a name its certificate doesnot carry — a proxy endpoint, a tunnel, a bare IP — so it skips only the
hostname check, by wrapping
WebPkiServerVerifierand translatingNotValidForNameinto acceptance. Everything else about the chain still holds.Verification
Against a local PostgreSQL 16 with TLS on and a private CA, checking
pg_stat_sslfor each connection that succeeds:prefer(default), private CAssl=trequire, private CAssl=tverify-full, CA untrustedverify-ca, CA untrustedverify-full+sslrootcertssl=tverify-ca+sslrootcertssl=tdisablessl=fverify-full+ unreadablesslrootcertpreferverify-caverify-fullSuite:
1996 passed, 2919 skipped(PostgreSQL; the skips are the backends notrunning locally), and
61 passedincargo test --lib, six of them new aroundthe URL parsing, the
sslrootcerterror path and connector construction at eachverification level.
Release
Version bumped to 1.17.0 across
Cargo.toml,pyproject.tomlandpython/yara_orm/__init__.py, with aCHANGELOG.mdentry, so this can be cutas a release. It is a behaviour change for anyone who was relying on
requireto verify — they want
verify-fullnow — hence the minor rather than a patch.Unrelated observation
The published
1.16.0wheel fails the handshake against an RSA servercertificate even when its CA is trusted (
decrypt_error, i.e.BadSignature);ECDSA certificates are fine. Neither a debug nor a release build from this
source reproduces it, so it looks like the dependency set that wheel was built
against — there is no committed
Cargo.lock. Worth a look before the nextrelease, separately from this PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PrJLyTdr7CMvrzxENq3NfP