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 rust-postgres/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.
It seems practically every user of the postgres lib is struggling with support
sslmode=require|verify-ca|verifu-fullper 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 :(sslmodeparameter.The overall expected behavior for sslmode:
If
verify_caisfalse, then we need to provide a way to skip certificate validation. Ifverify_hostnameisfalse, then we need to provide a way to skip hostname validation.sslmodeparsingThis code works around a current limitation in the postgres SSL parsing, which really should be fixed in rust-postgres/rust-postgres#988 -- basically the rust-postgres lib should support the additional
verify-caandverify-fullvalues, but without that patch, we can use regex to replace them withrequiredin the connection string. See example in my codeenable 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.allow connections without hostname validation
OpenSSL supports this code:
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.