sfackler / rust-postgres

Native PostgreSQL driver for the Rust programming language
Apache License 2.0
3.42k stars 436 forks source link

Can not connect to Postgres 16 #1105

Closed IndigoCurnick closed 5 months ago

IndigoCurnick commented 6 months ago

I use this crate to connect to Postgres in AWS. In Postgres 14 I connected like so

let rds_client_string = format!(
        "host={} user={} password={} dbname={}",
        rds_endpoint, rds_creds.username, rds_creds.password, rds_db_name
    );

    let res = tokio_postgres::connect(&rds_client_string, NoTls).await;

    let (rds_client, connection) =
        res.expect("Tokio postgres is not connecting");

With no issues. However, we recently updated to Postgres 16 and this no longer works. I tried following this article but to no avail, by downloading and using pem files from here

However, I can still connect to the database just fine using software like PgAdmin (though, that is through a bastion using AWS SSM - not sure what they are doing).

Any help would be greatly appreciated! I'm thinking this is some kind of SSL/TLS issue but not sure how to resolve it

sfackler commented 6 months ago

Can you provide literally any detail more specific than "no longer works"?

IndigoCurnick commented 6 months ago

Ah yes I forgot to include the error!

Tokio postgres is not connecting: Error { kind: Db, cause: Some(DbError { severity: "FATAL", parsed_severity: Some(Fatal), code: SqlState(E28000), message: "no pg_hba.conf entry for host, user \"postgres\", database \"postgres\", no encryption", detail: None, hint: None, position: None, where_: None, schema: None, table: None, column: None, datatype: None, constraint: None, file: Some("auth.c"), line: Some(542), routine: Some("ClientAuthentication") }) }
sfackler commented 6 months ago

It sounds like your database now requires that you connect with TLS. You'll need to replace the NoTls with a TLS implementation like postgres-openssl, postgres-native-tls, or tokio-postgres-rustls.

IndigoCurnick commented 6 months ago

Could you elaborate on how to do this? I managed to get this compiling where I created a TlsConnector, using certificates from here

let bytes = fs::read("rds-ca-2029-root.pem").unwrap();
    let cert = Certificate::from_pem(&bytes).unwrap();

    let connector = TlsConnector::builder()
        .add_root_certificate(cert)
        .build()
        .unwrap();
    let connector = MakeTlsConnector::new(connector);

But I get a certificate verify failed error

sfackler commented 6 months ago

Is that the entire text of the error message?

IndigoCurnick commented 6 months ago

Unfortunately yes, here's the full error output

Tokio postgres is not connecting: Error { kind: Tls, cause: Some(Ssl(Error { code: ErrorCode(1), cause: Some(Ssl(ErrorStack([Error { code: 167772294, library: "SSL routines", function: "tls_post_process_server_certificate", reason: "certificate verify failed", file: "ssl/statem/statem_clnt.c", line: 2091 }]))) }, X509VerifyResult { code: 19, error: "self-signed certificate in certificate chain" })) }

Curiously, trying to connect with Python and this works fine

con = psycopg2.connect(
        host=HOST,
        user=USER,
        password=PASSWORD,
        port=5432
    )

So now I'm not sure at all what the issue might be

sfackler commented 6 months ago

There is definitely more text there than certificate verify failed.

Does rds-ca-2029-root.pem contain more thatn one certificate? Certificate::from_pem will only load the first.

psycopg2 doesn't verify certificates by default IIRC.

IndigoCurnick commented 6 months ago

Here's the entire error

Tokio postgres is not connecting: Error { kind: Tls, cause: Some(Ssl(Error { code: ErrorCode(1), cause: Some(Ssl(ErrorStack([Error { code: 167772294, library: "SSL routines", function: "tls_post_process_server_certificate", reason: "certificate verify failed", file: "ssl/statem/statem_clnt.c", line: 2091 }]))) }, X509VerifyResult { code: 19, error: "self-signed certificate in certificate chain" })) }

I see - it does contain more than one certificate. Is there a convenient way to parse all of them into Vec? If not I can write something to do that. Is there a way to make the tokio-postgres behave like psycopg2 like it used to?

sfackler commented 6 months ago

If you use postgres-openssl you can just give it the path to the certs file with set_ca_file.

tokio-postgres never behaved like psycopg2. Your database used to not require TLS to login.

IndigoCurnick commented 5 months ago
pub async fn get_pg_client_with_tls(
    pg_uri: String,
    pg_username: String,
    pg_password: String,
    pg_db_name: String,
) -> Result<RdsClient, String> {
    let pg_connection_str = format!(
        "host={} user={} password={} dbname={} sslmode=require",
        pg_uri, pg_username, pg_password, pg_db_name
    );

    let bytes = PEM.as_bytes();

    let mut buf = BufReader::new(bytes);
    let mut root_store = RootCertStore::empty();
    let _no =
        root_store.add_parsable_certificates(rustls_pemfile::certs(&mut buf).map(|r| r.unwrap()));

    let config = rustls::ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();
    let tls = MakeRustlsConnect::new(config);

    let (client, connection) = tokio_postgres::connect(&pg_connection_str, tls)
        .await
        .unwrap();

    let _connection_handle = tokio::spawn(async move {
        if let Err(e) = connection.await {
            error!("connection error: {}", e);
            panic!("No postgres connection");
        }
    });
    return Ok(client);
}

I was eventually able to piece this function together. I think some more documentation on TLS might be helpful as this was a little hard to figure out - and in the end it required me to find four new crates for dealing with it. I'm not sure if there was an easier way to do it