prax_postgres/tls.rs
1//! TLS connector construction for the connection pool, via rustls.
2//!
3//! Enabled by the `tls` cargo feature (on by default). Certificates are
4//! verified against the Mozilla root store ([`webpki_roots`]) — chain and
5//! hostname — for every TLS-capable [`crate::config::SslMode`]. This is
6//! deliberately stricter than libpq, whose `sslmode=require` performs no
7//! certificate verification; there is currently no encrypt-without-verify
8//! mode.
9
10use std::sync::Arc;
11
12use postgres_rustls::MakeTlsConnector;
13use rustls::{ClientConfig, RootCertStore};
14
15/// Build the TLS connector handed to deadpool's `Manager`.
16///
17/// The same connector serves every TLS mode: `Prefer` (tokio-postgres falls
18/// back to plaintext only when the *server* declines TLS), and
19/// `Require`/`VerifyCa`/`VerifyFull` (driver-level `SslMode::Require`, so a
20/// server that refuses TLS fails the connection). Verification is always
21/// webpki chain + hostname — see the module docs.
22///
23/// This is the workspace's shared rustls connector, exposed so downstream
24/// tooling (e.g. `prax-cli`'s introspector) can reuse the same certificate
25/// verification behavior. Available only when the `tls` cargo feature is
26/// enabled (on by default).
27pub fn make_tls_connector() -> MakeTlsConnector {
28 let mut roots = RootCertStore::empty();
29 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
30
31 // Select aws-lc-rs explicitly: workspace feature unification can enable
32 // both rustls providers (aws-lc-rs via sqlx, ring via other crates), in
33 // which case rustls cannot auto-determine a process-level default.
34 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
35 let client_config = ClientConfig::builder_with_provider(provider)
36 .with_safe_default_protocol_versions()
37 .expect("safe default protocol versions are available")
38 .with_root_certificates(roots)
39 .with_no_client_auth();
40
41 MakeTlsConnector::new(tokio_rustls::TlsConnector::from(Arc::new(client_config)))
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use tokio_postgres::tls::MakeTlsConnect;
48
49 #[test]
50 fn connector_is_constructible_and_cloneable() {
51 // deadpool requires MakeTlsConnect + Clone + Sync + Send + 'static;
52 // construction also proves the rustls provider and root store load.
53 let connector = make_tls_connector();
54 let mut cloned = connector.clone();
55 // A syntactically invalid domain must fail in make_tls_connect
56 // (DNS name parsing) without any network I/O.
57 assert!(
58 <MakeTlsConnector as MakeTlsConnect<tokio::net::TcpStream>>::make_tls_connect(
59 &mut cloned,
60 "invalid..domain"
61 )
62 .is_err()
63 );
64 }
65}