diff --git a/Cargo.lock b/Cargo.lock index a70bc4c95df..9119bc84392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4127,6 +4127,7 @@ dependencies = [ "stable-hash 0.3.4", "thiserror 2.0.18", "tokio", + "tokio-postgres", "tokio-stream", ] diff --git a/store/postgres/Cargo.toml b/store/postgres/Cargo.toml index 289b2f4179b..72748579600 100644 --- a/store/postgres/Cargo.toml +++ b/store/postgres/Cargo.toml @@ -24,6 +24,7 @@ lru_time_cache = "0.11" postgres = "0.19.1" openssl = { version = "0.10.76", features = ["vendored"] } postgres-openssl = "0.5.2" +tokio-postgres = "0.7" rand.workspace = true serde = { workspace = true } serde_json = { workspace = true } diff --git a/store/postgres/src/pool/manager.rs b/store/postgres/src/pool/manager.rs index dbbcb288b83..2e0afba1f98 100644 --- a/store/postgres/src/pool/manager.rs +++ b/store/postgres/src/pool/manager.rs @@ -7,7 +7,7 @@ use deadpool::managed::{Hook, RecycleError, RecycleResult}; use diesel::IntoSql; use diesel_async::pooled_connection::{PoolError as DieselPoolError, PoolableConnection}; -use diesel_async::{AsyncConnection, RunQueryDsl}; +use diesel_async::RunQueryDsl; use graph::env::ENV_VARS; use graph::prelude::error; use graph::prelude::AtomicMovingStats; @@ -17,6 +17,8 @@ use graph::prelude::MetricsRegistry; use graph::prelude::PoolWaitStats; use graph::slog::info; use graph::slog::Logger; +use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; +use postgres_openssl::MakeTlsConnector; use std::collections::HashMap; use std::sync::atomic::AtomicBool; @@ -100,11 +102,44 @@ impl deadpool::managed::Manager for ConnectionManager { type Error = DieselPoolError; async fn create(&self) -> Result { - let res = diesel_async::AsyncPgConnection::establish(&self.connection_url).await; - if let Err(ref e) = res { - self.handle_error(e); - } - res.map_err(DieselPoolError::ConnectionError) + // diesel-async's AsyncPgConnection::establish() uses + // tokio_postgres::NoTls, which never negotiates TLS. This breaks + // connections to any PostgreSQL server that requires encrypted + // connections via pg_hba.conf. + // + // We use postgres-openssl to provide TLS support, with + // SslVerifyMode::NONE to match libpq's default sslmode=prefer + // behavior: encrypt the connection when the server supports it, + // but don't verify the server certificate. tokio-postgres handles + // the sslmode parameter from the connection URL to decide whether + // TLS is used (disable/prefer/require); we configure how the + // handshake behaves. + // + // Note: tokio-postgres does not support sslmode=verify-ca or + // sslmode=verify-full. Certificate verification would require + // upstream support in the tokio-postgres URL parser. + let tls = make_tls_connector().map_err(|e| { + DieselPoolError::ConnectionError(diesel::ConnectionError::BadConnection( + e.to_string(), + )) + })?; + + let (client, conn) = + tokio_postgres::connect(&self.connection_url, tls) + .await + .map_err(|e| { + self.handle_error(&e); + DieselPoolError::ConnectionError( + diesel::ConnectionError::BadConnection(e.to_string()), + ) + })?; + + diesel_async::AsyncPgConnection::try_from_client_and_connection(client, conn) + .await + .map_err(|e| { + self.handle_error(&e); + DieselPoolError::ConnectionError(e) + }) } async fn recycle( @@ -249,6 +284,18 @@ impl StateTracker { } } +/// Build an OpenSSL TLS connector for PostgreSQL connections. +/// +/// Uses `SslVerifyMode::NONE` (encrypt without certificate verification), +/// matching libpq's behavior for sslmode=prefer and sslmode=require. +/// tokio-postgres handles the sslmode URL parameter to decide whether TLS +/// is actually used (disable/prefer/require). +fn make_tls_connector() -> Result { + let mut builder = SslConnector::builder(SslMethod::tls())?; + builder.set_verify(SslVerifyMode::NONE); + Ok(MakeTlsConnector::new(builder.build())) +} + fn brief_error_msg(error: &dyn std::error::Error) -> String { // For 'Connection refused' errors, Postgres includes the IP and // port number in the error message. We want to suppress that and