Skip to content

Improve sslmode support #11

Open
Open
@nyurik

Description

@nyurik

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions