fix(ssl): implement sslmode=require without cert verification#711
fix(ssl): implement sslmode=require without cert verification#711
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #711 +/- ##
==========================================
- Coverage 68.84% 68.79% -0.05%
==========================================
Files 46 46
Lines 30870 30992 +122
==========================================
+ Hits 21251 21318 +67
- Misses 9619 9674 +55 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add Section D to tests/compat/test-connections.sh covering sslmodes: - D1: sslmode=disable (verifies ssl=f in pg_stat_ssl) - D2: sslmode=prefer (verifies successful connection) - D3: sslmode=require vs TLS server (verifies ssl=t; regression test for the bug fixed in PR #711 / issue #710) - D4: sslmode=require vs plain server (verifies non-zero exit with SSL/TLS error message) - D5: sslmode=verify-ca (SKIP — known bug, see issue #712) - D6: sslmode=verify-full (SKIP — known bug, see issue #712) Section D requires TEST_PG_TLS_HOST/TEST_PG_TLS_PORT env vars; if unset the entire section is skipped with a clear message. Also update .github/workflows/checks.yml (connection-tests job): - Add a 'Start TLS postgres' step that generates a self-signed cert and launches a postgres:17 container with ssl=on on port 5433 - Pass TEST_PG_TLS_HOST/TEST_PG_TLS_PORT/TEST_PG_TLS_PASSWORD env vars to the 'Run connection tests' step
REV Review — PR #711: fix(ssl): sslmode=require without cert verificationSummaryCorrectly implements Blocking findingsNone. Non-blocking findings
The branch
The new "Security rules" section is a useful addition. Consider whether it belongs in a dedicated Potential issues
The new Test coverageStrong. The PR adds 7 targeted unit tests for
Tests are well-structured, use Gaps (non-blocking, noted above):
VerdictAPPROVE |
sslmode=require means encrypt-only: the connection must use TLS but
the server certificate is not verified. Previously, require fell back
to connect_tls_default() which loads Mozilla webpki roots and
verifies the chain, causing failures against self-signed certs.
Changes:
- Add NoVerifier: a ServerCertVerifier that accepts any cert without
chain or hostname validation (sslmode=require semantics)
- Add make_tls_config_require(): builds a ClientConfig using NoVerifier,
with optional client cert (mutual TLS) support
- Update SslMode::Require branch in connect_one() to use
connect_tls_with_config() + make_tls_config_require() instead of
connect_tls_default()
- Improve map_connect_error(): walk the tokio-postgres error source
chain to extract the real message; classify into specific variants:
SslNotSupported (server has no TLS), SslCertVerificationFailed
(verify-ca/verify-full cert errors), TlsError (other TLS failures)
- Add SslNotSupported and SslCertVerificationFailed error variants with
psql-matching messages ("SSL error: server does not support SSL",
"SSL error: certificate verification failed: ...")
- Extract classify_connect_error() for testability
- Add 7 unit tests covering all SSL error classification paths
Closes #710
70298a7 to
90785c4
Compare
Add Section D to tests/compat/test-connections.sh covering sslmodes: - D1: sslmode=disable (verifies ssl=f in pg_stat_ssl) - D2: sslmode=prefer (verifies successful connection) - D3: sslmode=require vs TLS server (verifies ssl=t; regression test for the bug fixed in PR #711 / issue #710) - D4: sslmode=require vs plain server (verifies non-zero exit with SSL/TLS error message) - D5: sslmode=verify-ca (SKIP — known bug, see issue #712) - D6: sslmode=verify-full (SKIP — known bug, see issue #712) Section D requires TEST_PG_TLS_HOST/TEST_PG_TLS_PORT env vars; if unset the entire section is skipped with a clear message. Also update .github/workflows/checks.yml (connection-tests job): - Add a 'Start TLS postgres' step that generates a self-signed cert and launches a postgres:17 container with ssl=on on port 5433 - Pass TEST_PG_TLS_HOST/TEST_PG_TLS_PORT/TEST_PG_TLS_PASSWORD env vars to the 'Run connection tests' step
* test(ci): add SSL mode connection tests (Section D) Add Section D to tests/compat/test-connections.sh covering sslmodes: - D1: sslmode=disable (verifies ssl=f in pg_stat_ssl) - D2: sslmode=prefer (verifies successful connection) - D3: sslmode=require vs TLS server (verifies ssl=t; regression test for the bug fixed in PR #711 / issue #710) - D4: sslmode=require vs plain server (verifies non-zero exit with SSL/TLS error message) - D5: sslmode=verify-ca (SKIP — known bug, see issue #712) - D6: sslmode=verify-full (SKIP — known bug, see issue #712) Section D requires TEST_PG_TLS_HOST/TEST_PG_TLS_PORT env vars; if unset the entire section is skipped with a clear message. Also update .github/workflows/checks.yml (connection-tests job): - Add a 'Start TLS postgres' step that generates a self-signed cert and launches a postgres:17 container with ssl=on on port 5433 - Pass TEST_PG_TLS_HOST/TEST_PG_TLS_PORT/TEST_PG_TLS_PASSWORD env vars to the 'Run connection tests' step * fix(tests): address REV blocking findings in D4 and CI readiness loop - D4 (test_ssl_require_fail): add guard to skip when TEST_PGHOST is unset, preventing set -u abort when script is run without plain-server env vars. Use ${VAR:-} defaults throughout to be safe with set -u. - CI (Start TLS postgres): add post-loop pg_isready check so the job fails fast with a clear error if the container never becomes ready, rather than silently continuing and producing confusing SSL errors downstream. - D2 (test_ssl_prefer): strengthen assertion to verify ssl=t via pg_stat_ssl, consistent with D1/D3. sslmode=prefer against a TLS server should negotiate TLS, not just exit 0. - Trailing whitespace: clean up results echo line. * fix(tests): D2 sslmode=prefer asserts exit 0 only, not ssl=t With a self-signed cert prefer falls back to plaintext — correct behavior. D3 (sslmode=require) is the authoritative TLS negotiation test.
Summary
sslmode=requiremeans encrypt-only: TLS is mandatory but the servercertificate is not verified (that is
verify-ca/verify-full's job).Previously, the
Requirebranch calledconnect_tls_default()whichloads Mozilla webpki roots and verifies the certificate chain, causing
connection failures against self-signed certs that psql handles fine.
Changes
NoVerifier: newServerCertVerifierthat accepts any servercertificate without chain or hostname validation
make_tls_config_require(): builds aClientConfigusingNoVerifier, with optional mutual TLS (client cert) supportconnect_one():SslMode::Requirenow callsconnect_tls_with_config()with the new config instead ofconnect_tls_default()map_connect_error(): walk the tokio-postgres error source chainto extract the real message; introduce two new error variants:
SslNotSupported: server returned 'N' to SSLRequest — messageSSL error: server does not support SSL(matches psql wording)SslCertVerificationFailed: rustls cert chain/hostname error —message
SSL error: certificate verification failed: ...TlsErrorprefix changed fromTLS error:toSSL error:(matches psql convention)
classify_connect_error(): extracted frommap_connect_error()so the classification rules are unit-testable without a live server
(UnknownIssuer and NotValidForName), SslRequired (server-side),
generic TlsError, auth error, plain connection failure
Testing
Verified against a TLS-enabled Postgres (self-signed cert, no CA) at
localhost:15436 —
sslmode=requirenow connects and returnsssl=t.All 7 new unit tests pass. Full clippy (no warnings) and rustfmt clean.
Closes #710