Skip to content

fix(ssl): sslmode=prefer negotiates TLS without cert verification#726

Merged
NikolayS merged 1 commit intomainfrom
fix/prefer-tls-no-verify
Mar 24, 2026
Merged

fix(ssl): sslmode=prefer negotiates TLS without cert verification#726
NikolayS merged 1 commit intomainfrom
fix/prefer-tls-no-verify

Conversation

@NikolayS
Copy link
Copy Markdown
Owner

Closes #722

psql sslmode=prefer tries TLS first and falls back to plaintext only if TLS is unavailable — it does not verify the certificate. rpg was using webpki roots for prefer, causing fallback to plaintext on self-signed certs.

Fix: use the no-verify TLS config (same as sslmode=require) for the prefer TLS attempt.

@NikolayS
Copy link
Copy Markdown
Owner Author

REV: fix(ssl): sslmode=prefer negotiates TLS without cert verification

Summary

APPROVE with one actionable note and one scope issue to resolve before merge.


1. Correctness of make_tls_config_require for prefer

The function is correctly repurposed here. NoVerifier accepts any server certificate unconditionally — verify_server_cert returns Ok(ServerCertVerified::assertion()) without inspecting the cert at all. This is exactly what psql/libpq sslmode=prefer does: encrypt if possible, never reject based on the certificate.

Using make_tls_config_require for prefer is semantically sound. The name is slightly misleading when called from a non-require context, but that is a naming concern, not a correctness concern.

2. Semantics match psql/libpq

psql sslmode=prefer: try TLS with no cert check; fall back to plaintext only if the server sends 'N' to the SSLRequest. The new code matches this. The old code used connect_tls_default which calls make_tls_config_default (webpki system roots), causing a cert verification failure on self-signed certs. That failure was swallowed by Err(_), silently downgrading to plaintext even though the server was perfectly capable of TLS. Bug was real and the fix is correct.

3. Fallback logic and the broad Err(_) arm

This is the one actionable concern.

With the old code, any TLS failure — including cert rejection — caused fallback to plaintext. That was wrong but the cert rejection was itself the bug. With NoVerifier, the only failure path that should trigger fallback is the server responding 'N' to SSLRequest, which tokio-postgres surfaces as the string "server does not support TLS" and maps to ConnectionError::SslNotSupported.

The new code still catches Err(_) without discrimination. That means any other error (auth failure mid-handshake, network reset, client cert load failure, etc.) will silently fall back to plaintext instead of surfacing as an error. This was pre-existing behavior inherited from the old arm, but now that the cert-verification escape hatch is closed, the broad catch is more visible.

The Require arm correctly propagates all errors with ?. For prefer the fallback is intentional, but it should be scoped. Suggested narrowing:

Err(ConnectionError::SslNotSupported) | Err(ConnectionError::TlsError(_)) => {
    (connect_plain(pg_config, params).await?, None)
}
Err(e) => return Err(e),

This preserves psql semantics (fall back when TLS is unavailable or handshake fails at the transport layer) while propagating auth failures and other non-TLS errors. Not a blocker given the behavior was pre-existing, but worth a follow-up issue.

4. Missing cfg.ssl_mode(TokioSslMode::Require)

The Require, VerifyCa, and VerifyFull arms all clone pg_config and explicitly set cfg.ssl_mode(TokioSslMode::Require) before passing to connect_tls_with_config. The new Prefer arm passes pg_config directly without setting this. tokio-postgres defaults ssl_mode to Prefer, which causes it to attempt TLS but accept 'N' from the server gracefully. That is actually correct here — the outer match already handles the 'N' fallback case explicitly, and the inner connect_tls_with_config just needs to attempt TLS. No bug, but the asymmetry is worth a brief comment to prevent a future reader from thinking it was an oversight.

5. Code reuse / duplication

No concern. make_tls_config_require is shared cleanly between Require and Prefer. If the name becomes confusing, a future rename to make_tls_config_no_verify would be a cosmetic improvement, but not urgent.

6. Out-of-scope changes

CHANGELOG.md adds an 0.8.1 entry and Cargo.toml bumps the version from 0.8.0 to 0.8.1. Version management for 0.8.1 is tracked in PR #720. These changes should be removed from this PR to avoid a merge conflict or double-bump, or coordination with #720 confirmed explicitly.


Decision

APPROVE conditional on removing the CHANGELOG.md and Cargo.toml version bump before merge (or confirming coordination with #720). The core fix is correct. The broad Err(_) fallback is a recommended follow-up, not a blocker for this change.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 24, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.79%. Comparing base (384a2ef) to head (c9eb1dc).
⚠️ Report is 1 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #726   +/-   ##
=======================================
  Coverage   68.79%   68.79%           
=======================================
  Files          46       46           
  Lines       30998    30999    +1     
=======================================
+ Hits        21324    21325    +1     
  Misses       9674     9674           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@NikolayS NikolayS force-pushed the fix/prefer-tls-no-verify branch from 52f3588 to c9eb1dc Compare March 24, 2026 03:47
@NikolayS NikolayS merged commit 9bf4b40 into main Mar 24, 2026
10 checks passed
@NikolayS NikolayS deleted the fix/prefer-tls-no-verify branch March 27, 2026 23:26
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.

fix(ssl): sslmode=prefer does not upgrade to TLS on TLS-capable server

2 participants