Description
It seems practically every user of the postgres lib is struggling with support sslmode=require|verify-ca|verifu-full
per pg ssl docs. I believe this requires these new features, and this lib is probably the best place to add them to because they are mostly postgres specific, and because rustls doesn't want them :(
- improve connection string parsing to detect
sslmode
parameter. - provide a way to skip certificate validation
- provide a way to skip host validation
The overall expected behavior for sslmode:
let (verify_ca, verify_hostname) = match ssl_mode {
SslMode::Disable | SslMode::Prefer => (false, false),
SslMode::Require => match pg_certs.ssl_root_cert {
// If a root CA file exists, the behavior of sslmode=require will be the same as
// that of verify-ca, meaning the server certificate is validated against the CA.
// For more details, check out the note about backwards compatibility in
// https://postgresql.org/docs/current/libpq-ssl.html#LIBQ-SSL-CERTIFICATES
// See also notes in
// https://github.com/sfu-db/connector-x/blob/b26f3b73714259dc55010f2233e663b64d24f1b1/connectorx/src/sources/postgres/connection.rs#L25
Some(_) => (true, false),
None => (false, false), // no root CA file, so no verification
},
SslMode::VerifyCa => (true, false),
SslMode::VerifyFull => (true, true),
};
If verify_ca
is false
, then we need to provide a way to skip certificate validation. If verify_hostname
is false
, then we need to provide a way to skip hostname validation.
sslmode
parsing
This code works around a current limitation in the postgres SSL parsing, which really should be fixed in sfackler/rust-postgres#988 -- basically the rust-postgres lib should support the additional verify-ca
and verify-full
values, but without that patch, we can use regex to replace them with required
in the connection string. See example in my code
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SslModeOverride {
Unmodified(SslMode),
VerifyCa,
VerifyFull,
}
/// Special treatment for sslmode=verify-ca & sslmode=verify-full - if found, replace them with sslmode=require
pub fn parse_conn_str(conn_str: &str) -> Result<(Config, SslModeOverride)> {
let mut mode = SslModeOverride::Unmodified(SslMode::Disable);
let exp = r"(?P<before>(^|\?|&| )sslmode=)(?P<mode>verify-(ca|full))(?P<after>$|&| )";
let re = Regex::new(exp).unwrap();
let pg_cfg = if let Some(captures) = re.captures(conn_str) {
let captured_value = &captures["mode"];
mode = match captured_value {
"verify-ca" => SslModeOverride::VerifyCa,
"verify-full" => SslModeOverride::VerifyFull,
_ => unreachable!(),
};
let conn_str = re.replace(conn_str, "${before}require${after}");
Config::from_str(conn_str.as_ref())
} else {
Config::from_str(conn_str)
};
let pg_cfg = pg_cfg.map_err(|e| BadConnectionString(e, conn_str.to_string()))?;
if let SslModeOverride::Unmodified(_) = mode {
mode = SslModeOverride::Unmodified(pg_cfg.get_ssl_mode());
}
Ok((pg_cfg, mode))
}
enable ssl but ignore cert verification
In OpenSSL, we can use builder.set_verify(SslVerifyMode::NONE)
, but unfortunately rustls doesn't want to allow this, hence this workaround. See example usage from maplibre/martin#474 pull request.
struct NoCertificateVerification {}
impl rustls::client::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp: &[u8],
_now: std::time::SystemTime,
) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
Ok(rustls::client::ServerCertVerified::assertion())
}
}
....
if !verify_ca {
builder
.dangerous()
.set_certificate_verifier(std::sync::Arc::new(NoCertificateVerification {}));
}
allow connections without hostname validation
OpenSSL supports this code:
if !verify_hostname {
connector.set_callback(|cfg, _domain| {
cfg.set_verify_hostname(false);
Ok(())
});
}
but there is no similar API for rustls. There are some examples in the rustls/rustls#578 issue, but rustls seems not to want to implement/maintain it, so we need a workaround.