cornucopia-rs / cornucopia

Generate type-checked Rust from your PostgreSQL.
Other
884 stars 41 forks source link

Cornucopia dead_pool and TLS. #202

Open 9876691 opened 1 year ago

9876691 commented 1 year ago

When using a cloud provider you always need TLS. That's a good thing.

All the examples for cornucopia use NoTLS, that's OK. All the examples for deadpool use NoTLS. https://github.com/bikeshedder/deadpool/tree/master/examples

OK. So I managed to get TLS working with Azure Postgres and it looks like this.

pub fn create_pool(database_url: &str) -> deadpool_postgres::Pool {
    let config = tokio_postgres::Config::from_str(database_url).unwrap();

    let manager = if database_url.contains("sslmode=require") {
        let mut root_store = rustls::RootCertStore::empty();
        root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
            rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
                ta.subject,
                ta.spki,
                ta.name_constraints,
            )
        }));

        let tls_config = rustls::ClientConfig::builder()
            .with_safe_defaults()
            .with_root_certificates(root_store)
            .with_no_client_auth();
        let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config);
        deadpool_postgres::Manager::new(config, tls)
    } else {
        deadpool_postgres::Manager::new(config, tokio_postgres::NoTls)
    };

    deadpool_postgres::Pool::builder(manager).build().unwrap()
}

However it doesn't work with Digital Ocean.

This guy has written a blog post on getting deadpool to work with TLS https://medium.com/ecliptical-software-inc/a-curious-tale-of-rust-tls-and-postgres-in-the-cloud-969a4d2bea9

I can't get that to compile.

Solution

Get a working create_pool function and add it to cornucopia client, so we can all use it.

If someone has working code for Postgres on Digital Ocean please let me know.

Prior Art

SQLx works with all providers I've tried with no special code.

Thanks.

9876691 commented 1 year ago

I think I've figured out how SQLx do this.

They set a variable like so

let accept_invalid_certs = !matches!(
        options.ssl_mode,
        PgSslMode::VerifyCa | PgSslMode::VerifyFull
    );

https://github.com/launchbadge/sqlx/blob/main/sqlx-postgres/src/connection/tls.rs#L50

Now when they connect via this code https://github.com/launchbadge/sqlx/blob/main/sqlx-core/src/net/tls/tls_rustls.rs#L99

let config = if tls_config.accept_invalid_certs {
        if let Some(user_auth) = user_auth {
            config
                .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier))
                .with_single_cert(user_auth.0, user_auth.1)
                .map_err(Error::tls)?
        } else {
            config
                .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier))
                .with_no_client_auth()
        }
    }

They basically ignore the certificates and that's why it works on all the cloud providers.

So I guess cornucopia needs something like this, to stop people searching for a TLS solution.

Final Solution

use std::str::FromStr;
use std::sync::Arc;

pub fn create_pool(database_url: &str) -> deadpool_postgres::Pool {

    let config = tokio_postgres::Config::from_str(database_url).unwrap();

    let manager = if database_url.contains("sslmode=require") {

        let tls_config = rustls::ClientConfig::builder()
            .with_safe_defaults()
            .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier))
            .with_no_client_auth();

        let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config);
        deadpool_postgres::Manager::new(config, tls)
    } else {
        deadpool_postgres::Manager::new(config, tokio_postgres::NoTls)
    };

    deadpool_postgres::Pool::builder(manager).build().unwrap()
}

struct DummyTlsVerifier;

impl ServerCertVerifier for DummyTlsVerifier {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::Certificate,
        _intermediates: &[rustls::Certificate],
        _server_name: &ServerName,
        _scts: &mut dyn Iterator<Item = &[u8]>,
        _ocsp_response: &[u8],
        _now: SystemTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        Ok(ServerCertVerified::assertion())
    }
}