From 2dacb147e0857f43e71808136f285ca18adddc14 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 3 Nov 2024 13:57:13 +0000 Subject: [PATCH 01/11] rely on Unpin to remove unnecessary unsafe projections --- src/lib.rs | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eccd1ce..10f74bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ mod private { use x509_cert::{der::Decode, TbsCertificate}; pub struct TlsConnectFuture { - pub inner: tokio_rustls::Connect, + inner: tokio_rustls::Connect, } impl Future for TlsConnectFuture @@ -42,11 +42,8 @@ mod private { { type Output = io::Result>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // SAFETY: If `self` is pinned, so is `inner`. - #[allow(unsafe_code)] - let fut = unsafe { self.map_unchecked_mut(|this| &mut this.inner) }; - fut.poll(cx).map_ok(RustlsStream) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner).poll(cx).map_ok(RustlsStream) } } @@ -74,16 +71,6 @@ mod private { pub struct RustlsStream(TlsStream); - impl RustlsStream { - pub fn project_stream(self: Pin<&mut Self>) -> Pin<&mut TlsStream> { - // SAFETY: When `Self` is pinned, so is the inner `TlsStream`. - #[allow(unsafe_code)] - unsafe { - self.map_unchecked_mut(|this| &mut this.0) - } - } - } - impl tokio_postgres::tls::TlsStream for RustlsStream where S: AsyncRead + AsyncWrite + Unpin, @@ -126,11 +113,11 @@ mod private { S: AsyncRead + AsyncWrite + Unpin, { fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { - self.project_stream().poll_read(cx, buf) + Pin::new(&mut self.0).poll_read(cx, buf) } } @@ -139,22 +126,25 @@ mod private { S: AsyncRead + AsyncWrite + Unpin, { fn poll_write( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - self.project_stream().poll_write(cx, buf) + Pin::new(&mut self.0).poll_write(cx, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project_stream().poll_flush(cx) + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) } fn poll_shutdown( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - self.project_stream().poll_shutdown(cx) + Pin::new(&mut self.0).poll_shutdown(cx) } } } From 2fd93b77af2ed83301456d28f1b627b47a8a049e Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 3 Nov 2024 13:58:51 +0000 Subject: [PATCH 02/11] upgrade warnings --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 10f74bf..ac97ceb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![doc = include_str!("../README.md")] #![forbid(rust_2018_idioms)] -#![deny(missing_docs, unsafe_code)] +#![forbid(missing_docs, unsafe_code)] #![warn(clippy::all, clippy::pedantic)] use std::{convert::TryFrom, sync::Arc}; From 7ba212ab1556c259faeb64a1f6ae094986bf6217 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 3 Nov 2024 13:34:58 +0000 Subject: [PATCH 03/11] correctly parse x509 certificates for channel binding --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac97ceb..22e4e8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ mod private { use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_postgres::tls::{ChannelBinding, TlsConnect}; use tokio_rustls::{client::TlsStream, TlsConnector}; - use x509_cert::{der::Decode, TbsCertificate}; + use x509_cert::{der::Decode, Certificate}; pub struct TlsConnectFuture { inner: tokio_rustls::Connect, @@ -78,10 +78,10 @@ mod private { fn channel_binding(&self) -> ChannelBinding { let (_, session) = self.0.get_ref(); match session.peer_certificates() { - Some(certs) if !certs.is_empty() => TbsCertificate::from_der(&certs[0]) + Some(certs) if !certs.is_empty() => Certificate::from_der(&certs[0]) .ok() .and_then(|cert| { - let digest = match cert.signature.oid { + let digest = match cert.signature_algorithm.oid { // Note: SHA1 is upgraded to SHA256 as per https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 ID_SHA_1 | ID_SHA_256 From b156fb8d2ca5f2c7001676c59a0b0adcd5f126a9 Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 16:01:56 -0500 Subject: [PATCH 04/11] add integration test suite Add integration test suite that uses `bollard` to create postgres containers. Includes SCRAM/SASL (i.e. channel_binding) tests and mTLS tests. --- Cargo.toml | 1 + tests/integration.rs | 205 ++++++++++++++++++++++++++++++++ tests/support/ca.crt | 31 +++++ tests/support/ca.key | 52 ++++++++ tests/support/certstore.rs | 61 ++++++++++ tests/support/client.crt | 25 ++++ tests/support/client.key | 28 +++++ tests/support/client_sha384.crt | 25 ++++ tests/support/client_sha512.crt | 25 ++++ tests/support/docker.rs | 188 +++++++++++++++++++++++++++++ tests/support/mod.rs | 2 + tests/support/server.crt | 26 ++++ tests/support/server.key | 28 +++++ tests/support/server_sha384.crt | 26 ++++ tests/support/server_sha512.crt | 26 ++++ tests/support/sql_setup.sh | 37 ++++++ 16 files changed, 786 insertions(+) create mode 100644 tests/integration.rs create mode 100644 tests/support/ca.crt create mode 100644 tests/support/ca.key create mode 100644 tests/support/certstore.rs create mode 100644 tests/support/client.crt create mode 100644 tests/support/client.key create mode 100644 tests/support/client_sha384.crt create mode 100644 tests/support/client_sha512.crt create mode 100644 tests/support/docker.rs create mode 100644 tests/support/mod.rs create mode 100644 tests/support/server.crt create mode 100644 tests/support/server.key create mode 100644 tests/support/server_sha384.crt create mode 100644 tests/support/server_sha512.crt create mode 100755 tests/support/sql_setup.sh diff --git a/Cargo.toml b/Cargo.toml index 8b83023..a0e6641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ rustls = { version = "0.23", default-features = false, features = [ "tls12", "ring", ] } +bollard = { version = "0.19.2" } diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..3545953 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,205 @@ +use tokio_postgres::config::{ChannelBinding, SslMode}; +use tokio_postgres::Config; +use tokio_postgres_rustls::MakeRustlsConnect; + +mod support; +use support::certstore::CertStore; +use support::docker::PostgresContainer; + +#[tokio::test] +async fn ssl_user_without_client_cert_rejected() { + let mut pg = PostgresContainer::new( + "ssl-user-without-client-cert-rejected", + "./tests/support/sql_setup.sh", + "./tests/support/ca.crt", + "./tests/support/server.crt", + "./tests/support/server.key", + ) + .await + .expect("start postgres test container"); + + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(CertStore::roots()) + .with_no_client_auth(); + let tls = MakeRustlsConnect::new(tls_config); + + let mut pg_config = Config::new(); + pg_config + .host("localhost") + .port(pg.port) + .dbname("postgres") + .user("ssl_user") + .ssl_mode(SslMode::Prefer); + + let Err(err) = pg_config.connect(tls).await else { + let _ = pg.cleanup().await; + panic!("connect to postgres as ssl_user without client auth should fail"); + }; + + if err.to_string() != "db error: FATAL: connection requires a valid client certificate" { + let _ = pg.cleanup().await; + panic!("connect to postgres as ssl_user without client auth failed with unexpected error: {:?}", err); + } + + let _ = pg.cleanup().await; +} + +#[tokio::test] +async fn ssl_user_ok() { + let mut pg = PostgresContainer::new( + "ssl-user-with-client-cert-ok", + "./tests/support/sql_setup.sh", + "./tests/support/ca.crt", + "./tests/support/server.crt", + "./tests/support/server.key", + ) + .await + .expect("start postgres test container"); + + let certs = CertStore::sha256(); + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(certs.roots) + .with_client_auth_cert(certs.client_certs, certs.client_key) + .expect("build rustls client config"); + let tls = MakeRustlsConnect::new(tls_config); + + let mut pg_config = Config::new(); + pg_config + .host("localhost") + .port(pg.port) + .dbname("postgres") + .user("ssl_user") + .ssl_mode(SslMode::Require); + let (client, conn) = pg_config.connect(tls).await.expect("connect"); + tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) }); + + let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare"); + let rows = client.query(&stmt, &[]).await.expect("query"); + assert_eq!(1, rows.len()); + let res: i32 = (&rows[0]).get(0); + assert_eq!(1, res); + + let _ = pg.cleanup().await; +} + +#[tokio::test] +async fn scram_test_sha256_ok() { + let mut pg = PostgresContainer::new( + "scram-sha256", + "./tests/support/sql_setup.sh", + "./tests/support/ca.crt", + "./tests/support/server.crt", + "./tests/support/server.key", + ) + .await + .expect("start postgres test container"); + + let certs = CertStore::sha256(); + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(certs.roots) + .with_client_auth_cert(certs.client_certs, certs.client_key) + .expect("build rustls client config"); + let tls = MakeRustlsConnect::new(tls_config); + + let mut pg_config = Config::new(); + pg_config + .host("localhost") + .port(pg.port) + .dbname("postgres") + .user("scram_user") + .password("password") + .ssl_mode(SslMode::Require) + .channel_binding(ChannelBinding::Require); + let (client, conn) = pg_config.connect(tls).await.expect("connect"); + tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) }); + + let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare"); + let rows = client.query(&stmt, &[]).await.expect("query"); + assert_eq!(1, rows.len()); + let res: i32 = (&rows[0]).get(0); + assert_eq!(1, res); + + let _ = pg.cleanup().await; +} + +#[tokio::test] +async fn scram_test_sha384_ok() { + let mut pg = PostgresContainer::new( + "scram-sha384", + "./tests/support/sql_setup.sh", + "./tests/support/ca.crt", + "./tests/support/server_sha384.crt", + "./tests/support/server.key", + ) + .await + .expect("start postgres test container"); + + let certs = CertStore::sha384(); + let config = rustls::ClientConfig::builder() + .with_root_certificates(certs.roots) + .with_client_auth_cert(certs.client_certs, certs.client_key) + .expect("build rustls client config"); + let tls = MakeRustlsConnect::new(config); + + let mut pg_config = Config::new(); + pg_config + .host("localhost") + .port(pg.port) + .dbname("postgres") + .user("scram_user") + .password("password") + .ssl_mode(SslMode::Require) + .channel_binding(ChannelBinding::Require); + + let (client, conn) = pg_config.connect(tls).await.expect("connect"); + tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) }); + + let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare"); + let rows = client.query(&stmt, &[]).await.expect("query"); + assert_eq!(1, rows.len()); + let res: i32 = (&rows[0]).get(0); + assert_eq!(1, res); + + let _ = pg.cleanup().await; +} + +#[tokio::test] +async fn scram_test_sha512_ok() { + let mut pg = PostgresContainer::new( + "scram-sha512", + "./tests/support/sql_setup.sh", + "./tests/support/ca.crt", + "./tests/support/server_sha512.crt", + "./tests/support/server.key", + ) + .await + .expect("start postgres test container"); + + let certs = CertStore::sha512(); + let config = rustls::ClientConfig::builder() + .with_root_certificates(certs.roots) + .with_client_auth_cert(certs.client_certs, certs.client_key) + .expect("build rustls client config"); + let tls = MakeRustlsConnect::new(config); + + let mut pg_config = Config::new(); + pg_config + .host("localhost") + .port(pg.port) + .dbname("postgres") + .user("scram_user") + .password("password") + .ssl_mode(SslMode::Require) + .channel_binding(ChannelBinding::Require); + + let (client, conn) = pg_config.connect(tls).await.expect("connect"); + tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) }); + + let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare"); + let rows = client.query(&stmt, &[]).await.expect("query"); + assert_eq!(1, rows.len()); + let res: i32 = (&rows[0]).get(0); + assert_eq!(1, res); + + let _ = pg.cleanup().await; +} diff --git a/tests/support/ca.crt b/tests/support/ca.crt new file mode 100644 index 0000000..e8a78f9 --- /dev/null +++ b/tests/support/ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgIUevsMJsfhFBpmvZl+RHLkMspwjlIwDQYJKoZIhvcNAQEL +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwODMxMDYyNzA1WhcNMzUwODI5MDYyNzA1WjAoMSYwJAYDVQQDDB10b2tpby1w +b3N0Z3Jlcy1ydXN0bHMgVGVzdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBALW2JKpGbL5Dnua1XPQOeJ5lgbwqJKrdYwl+dMQbp/ir7jkd/tiidnKS +RUIu8FBe4RpoMfwdK6LZtcsMjTMgwCRj1kVp2v5rDTyy8a/Exg2zMQUScIimC3vQ +ynXp4DUOLL7sS6divvC8n6ZzwjTc3Ph7k4NcsarmRYOgjh05CcC3KuaipV5pPJdC +p+qxdptwLUBDVGfGxQI0PFRfpEOFFfn6Rlbxt++WeR9V48oRORJRRrWgEUmKHXmk +m6QwVy3XqxMWSjYufnOUOhwvzkqXHGpafVFahJL9BlO2CdarcquAIm178yJjauF9 +jGXEKteLhfM6jjQ35fFKGCoswNrx4EkEQDem5To7Dlt24br8mhcv8GNFOdnzCXw+ +MOBe6AbANDePqrnShAdHkiYs/s4JzUtgzH1A0GnmIEfBjH382bsdwS2otxnbfkbY +3HNqmu273NV9QU9XWPZ9iQ75lVVrEo9kWTqh48ncEi7H83WCVNnzLQwYNVH9Qbx/ +tRlVLo3YuZ0Dp3nGiNLf2Y7uSA5ZBqH8SLH63+rEcAAl1/ODyykSkwIe/XFMvvmG +KiZpSQrOIYgROZWOsLKFH/1jVqsnXFEcuIh8Dz2y03pn3neXnaACQLVHA/M9MwjB +wzgtroyKEulHUipgpeGzeWG0MfbQy3PhmxO/IXk39orhFrs9VpPJAgMBAAGjYzBh +MB0GA1UdDgQWBBQChDTcpf5T0sdoqlE04yBW6x3eAjAfBgNVHSMEGDAWgBQChDTc +pf5T0sdoqlE04yBW6x3eAjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAX/UJcLwp9U5D6oxYzKoQCZFM63Dmb3yTSqwj +/LX1AZZ807jd/NLy8ozz8hWX5nlLMDtfXQW7vqPb1BXw38bWTymlsc74r9uLxMxb +omD90T/LsKBf1JlMkNgmVLKrCkK/h53oqfjRBgmU2XTsty6957FvYdM+NihzrpZH +yWjxrZ3Ks/BYRh/FBwNUNvBLUFucDl3ozSN1pmEqw37tG/+IO7xBjRCDNlrbgc0g +Ac8Sy4OG/mgM6g8BZeJETCWakWK1B0ENGfCf8W3RijY4Azzn79w3E3sclZsJES2Z ++KhIXOx3HdXEd+zmZbttjZrPgLGNf4gAExL6nrGqgQuXRiFK47lNFN4ENwi3Ftf+ +W6sEUpof/D2CZOgm8Y8nAlVvWwo1KIT0WbbKffks9H5h7L3JmUUpy18e4w5YM4jl +Al9QJaj2MSUKba7oGhv/bz5A2bXv/v2XLGNZT2/6H1+NGp+W97ykR8ZMLybWbwPw +LiJEe1OiVi3Usj/3JafO5TU5RSiCqJDzfAzv+e1Yfo0utM2fozgkSPYtZzL9/Q7l +CdkdhZisoq3M5iAWE0LeUK1hOBWCYFFUXIsedKvsX0zJhryogWiD+e/Guo+hmWSC +5dwj7qYnmAmjD+lorqzGztAcx4UHZ82ANc1RfWfWV7JdqZeU+gHTHzYxJtBux3Ch +i6sUXKc= +-----END CERTIFICATE----- diff --git a/tests/support/ca.key b/tests/support/ca.key new file mode 100644 index 0000000..11fbc63 --- /dev/null +++ b/tests/support/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC1tiSqRmy+Q57m +tVz0DnieZYG8KiSq3WMJfnTEG6f4q+45Hf7YonZykkVCLvBQXuEaaDH8HSui2bXL +DI0zIMAkY9ZFadr+aw08svGvxMYNszEFEnCIpgt70Mp16eA1Diy+7EunYr7wvJ+m +c8I03Nz4e5ODXLGq5kWDoI4dOQnAtyrmoqVeaTyXQqfqsXabcC1AQ1RnxsUCNDxU +X6RDhRX5+kZW8bfvlnkfVePKETkSUUa1oBFJih15pJukMFct16sTFko2Ln5zlDoc +L85KlxxqWn1RWoSS/QZTtgnWq3KrgCJte/MiY2rhfYxlxCrXi4XzOo40N+XxShgq +LMDa8eBJBEA3puU6Ow5bduG6/JoXL/BjRTnZ8wl8PjDgXugGwDQ3j6q50oQHR5Im +LP7OCc1LYMx9QNBp5iBHwYx9/Nm7HcEtqLcZ235G2Nxzaprtu9zVfUFPV1j2fYkO ++ZVVaxKPZFk6oePJ3BIux/N1glTZ8y0MGDVR/UG8f7UZVS6N2LmdA6d5xojS39mO +7kgOWQah/Eix+t/qxHAAJdfzg8spEpMCHv1xTL75hiomaUkKziGIETmVjrCyhR/9 +Y1arJ1xRHLiIfA89stN6Z953l52gAkC1RwPzPTMIwcM4La6MihLpR1IqYKXhs3lh +tDH20Mtz4ZsTvyF5N/aK4Ra7PVaTyQIDAQABAoICAA8yaYSYGTYcX6i52ElNAId2 +w8/h0mk71LGRkuBn07d3YZgDSzIA/Fj+pK4xUMXX+YmE11FXezPGc27E4PY6sfcZ +Jm1tufP3Lgc6i/4APfQdXWcoMKMtB97WCmisI6UsR0+8YWNhXciybcO1ALP9GLnr +U93znaRGV9/3SItnHsqwRvU8+wmXUYrw9ywopVvAmPg76U44kHdruPPxf8Jp2fmr +FcDlvFVcR0r4mvFmWViP1ZMRDQESfP6UZrPnmxLpa5RQE+rOHqM57VY4JU+Vrbmz +UbHBGT9Vy/aqTc5XTmLLd5WifSYOHH05S8mjC1Y1hAFgj04Msr1oTfcRARwOiIxO +gLUVF1XxSrFWhLLvN4MtY2vx1NWBOKBzXBH30BJk6dNP3P92Yqq5H26U3CeJ8PqW +vCyz+/BN79sNhISl9MjYP7h73pjigH7lq06MU6+gGYS2Hok6WvH1kBo/iUv6pGNJ +utP9MpuFTOTV9wPsuX8V2C4FBEXpR0N1HcULZh26LwtaMkLrh8wgqJ6YP6hyVEqg +MkGH5t2uBiezXHD1CBEPWnEBxQJB/f3EzRAUIKf7e0Zj8SU2Fk0d+RL+Hx7Rzgca +NHl7pG3uXHdbzqeAQDL5+0so4GLCgQurKSg501gwcJOJ6Izoj0458alVXGRqNl9v +uktOEB5oEYI22awm5idlAoIBAQDtpoU1Z5oMwo4/pMJGmpzNj/6nuRa6AiEi0LYk +4Cu5lNewLOafCAW5e+3vY0MSBPSr1WaOVcgJ5a+RzX7DutP5pyX5lEq9YhLnZGLx +HJB5ZYRfAqeQ7hu6rIMrl6EODPTqmcWAg0Q98hhYOwCIl9+vTQiQA3yKoGu5bcq8 +t/TPFhSl1CEz8proZnUjwOMaSZctwZSq5WHCFaPfTZfOPfaHpKL1D6OZsddTiron +cWIhEQSAlCLu24sMA5BPpo7Were/uQWFwuMNCzuuZ8IWJ3gyAiouiTRfWK1uq/01 +DMkArTUSO0ens3MVrVTqYWye09rOxeHyZdn75EL0fJ2kwQ9LAoIBAQDDveoIqT+P +1kIFBtEzbntSs6fceV4BrM6FJGlhN0DY7tvk5SAQByP83jtnMGD5WK9Gv4inhQjO +0Qmesd1WqnjdXHWmraIWbrcWl2T7+0KxhzS8wY7un7ZCajd4El8gVSX7uj1QheaJ +4cY/VthJTpJTmlVmXYIM5QGKw3I5mCCdDl9wqpy6QQqXzk6rXN3mMc+zKcKlqseO +A5eRr2HCTSh7KuMNz4lGHpD++Q8Rd3APmpXRN34mau7x7r+ZQ50LG8DdydwqqSI8 +TLf2al0wBNWN6HTDBvn0s6D0bJejaXs+oXFk3cWSf09PqW1ggOpKH+AoGNMPyLmF +l4WzWEJH6Di7AoIBAFPPDdsvlhw54ICBUiIkFHhJ97yegW1HovfsMs/+Xm0sGISA +0TP0WFB5bAeoMdcA+GhA8dHwXoNfBPOeTL4p/OdmEvZZXaI8G/6OYHY3ZJvrOSto +fD+YOR80B24wIxQZbaXuZMBVipNt9TfoVt/U74Wa3RIsqJcay+Kw3XaASU1etBln +He57+DVNLLJXK7/xg+cXBZ7fFgKkLw5pBsgtAewEG5Pg2AkztoQ3wD54teqDXdsa +YtdphTfPae0tB+KLlJGAZnotqHTJKVrwLHozoSfBq66NpHKbi04kST+UKq6aZsTv +Mu3pUwSqA/BEO1In1FpnXq9lS+KRNSuTZuTbUNECggEBALBN3X8hmnueu2Y1prZR +e8iYwsgFlROjJ3yquDP1BXafZfY8He3x0ItRS2X9IEtWgsERgS+J1s4ZYDXM9JS8 +BRM01U9ks0rLPCOgBzgjLwYhxvGA0ZHCzvHxI7cFrSK9LvIV5GYjxOtKLTsdqMAW +ghvFNch2zs14czQeKEMR78YmjPY45eOo87aZ/yfABKnzEaXJJmX7dbRHLfztX/sX +bi/KCCqHVJiQrX7Kq7TKxGd5kBmahwhpZoEIzxBn4wp/W+8ogyL1pj1zyHqtgPPg +Vs1zo7+KCBdMt9uP9+EbAAQAe9cjHXJ/MtuIuliipORQ46CaA3kDDX7zZFyOUVzd +IEMCggEAaJA+yPsv8+aYUqCNf1Qn0JiZG1f9hKWvXYXPVp/Hl3Z8Fa/XHeaaQUiG +5OEFwZAGNkM9XvRaZpSR5FkXNJcjpZWJ8TZN5hSf3K756RDp/zZccrLw31DYOt8l +1TMLLwew3DIWKk5ACDQSzQhnUBlIr+Baex3sbPBNxhW6sJBvXcVqCvvWxP5T0M80 +l3fXrnGY2ARPKA9HS4DbY5+6UHHNigLCUlN2PDXl2x9JRYRx96ghYJIJKBYySGyD +xAHC95Sh/lWvVVPqpzE2LZ7hVZs77iP5TLbHEYCGC7SB84Qj5QbhJgn+QS61torv +UyfWRyCU+6FfBOhBgBK2dy+BRkI/Xw== +-----END PRIVATE KEY----- diff --git a/tests/support/certstore.rs b/tests/support/certstore.rs new file mode 100644 index 0000000..e2ebc7c --- /dev/null +++ b/tests/support/certstore.rs @@ -0,0 +1,61 @@ +use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}; + +pub(crate) struct CertStore<'a> { + pub roots: rustls::RootCertStore, + pub client_certs: Vec>, + pub client_key: PrivateKeyDer<'a>, +} + +impl CertStore<'_> { + pub(crate) fn roots<'a>() -> rustls::RootCertStore { + let mut roots = rustls::RootCertStore::empty(); + roots + .add(CertificateDer::from_pem_file("tests/support/ca.crt").expect("load ca cert")) + .expect("add root ca"); + roots + } + + pub(crate) fn sha256<'a>() -> CertStore<'a> { + let client_certs = + vec![CertificateDer::from_pem_file("tests/support/client.crt") + .expect("load client cert")]; + let client_key = + PrivateKeyDer::from_pem_file("tests/support/client.key").expect("load client key"); + + CertStore { + roots: CertStore::roots(), + client_certs: client_certs, + client_key: client_key, + } + } + + pub(crate) fn sha384<'a>() -> CertStore<'a> { + let client_certs = vec![ + CertificateDer::from_pem_file("tests/support/client_sha384.crt") + .expect("load client cert"), + ]; + let client_key = + PrivateKeyDer::from_pem_file("tests/support/client.key").expect("load client key"); + + CertStore { + roots: CertStore::roots(), + client_certs: client_certs, + client_key: client_key, + } + } + + pub(crate) fn sha512<'a>() -> CertStore<'a> { + let client_certs = vec![ + CertificateDer::from_pem_file("tests/support/client_sha512.crt") + .expect("load client cert"), + ]; + let client_key = + PrivateKeyDer::from_pem_file("tests/support/client.key").expect("load client key"); + + CertStore { + roots: CertStore::roots(), + client_certs: client_certs, + client_key: client_key, + } + } +} diff --git a/tests/support/client.crt b/tests/support/client.crt new file mode 100644 index 0000000..4cf1298 --- /dev/null +++ b/tests/support/client.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEODCCAiCgAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCEowDQYJKoZIhvcNAQEL +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwODMxMTcwMjEzWhcNMzUwODI5MTcwMjEzWjATMREwDwYDVQQDDAhzc2xfdXNl +cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAd3WQ9BirmgmTFYSiA +EFr44lN7b4T7eGDZBo91sTOujxxRipSEmN38BvbSPjFu7CvxDfzNy0Zcuu64W1jV +KZQeGx9PdRQC9mkEK4EyW0w7D8nJLBjGeDJXZcbtfYuBFaE3Y4bX+gnCwyhdX9co +I+zOlLCuQddhaigWvacH/BO8gA4qkuZ6ecfrlty3IOXQqo9iSimIWETPpQOoPvqC +EqQvjJC6WQNS9FdnUT/P6W/DQpjjWno+8yDukKk9dVxm+PZE5DQIN4oB6lBhKymo +iqJGu513SAbRk1XhqwIs8U5TKdM5+qKLBI8tyjhWoE7tPeyGTFD9w2wMZbACIoCy +1i0CAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYI +KwYBBQUHAwIwHQYDVR0OBBYEFDxIpoyY05nscnzC3DY4ETjR3CO/MB8GA1UdIwQY +MBaAFAKENNyl/lPSx2iqUTTjIFbrHd4CMA0GCSqGSIb3DQEBCwUAA4ICAQCBYqg1 +H7iWywxuIUOT2wnKggkYmzcRH9dGmzeaYD9xh8xr9M59d7XOrQmcdO64pJjv/rzB +mgGBZN4Y+QJ0VMuJbndJo6yAavj8FX68mo+8rOYfQ/gjyHmeAvTDY6pSZ+K/GqZ6 +vApTYH9jiYZMhZDnBeA7Vz6naXuan8xrq5+z9qv2ZDfY/rw+DBKUccpNhgfW4taQ +6WCCe0bXbXOhowPhavi9j19CIYkgXNkQTKOAVO9RUroT5gaIO6qZfvfMhXlG0fnT +o7OaKlgEusRYKv4JerkdLXlKYj/dm7/Its4cAa2O9qBhr1apttl1PprTai+jQ1QF +QmtqG2TiFgyhoP2T8Zziq6yOBum4tOw9Y/nwQeb881NFSe6NpBJyYOZPj8jLHIDG +1r95rhjcPlD08H+I6Ury5hEfN7mJiQ2Sulgg8iRHELaA+1kp2nppe8IK2V8YrU+k +AUnTunmU8kxWyjyTGk5YHm8RIY49Kp+Mj0j9my0tGbDhEI5EmRDA6gFEsRyIBntv +922ix8I4O1gFJTnu3cpO2ftrkJVUmvNRxOzos/5TL06O4tYgInxFsjnCgWgXDkus +3RVT/bMVxfU/Vn68xWCDnzPmd8uKlkvPltINu1Z63Pz2CeYIfpPO9RXIk8zkhF5A +fu88OwYPIa1bkopIvFY/X5h0Klwh9t489p7k6A== +-----END CERTIFICATE----- diff --git a/tests/support/client.key b/tests/support/client.key new file mode 100644 index 0000000..8b1cdba --- /dev/null +++ b/tests/support/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwHd1kPQYq5oJk +xWEogBBa+OJTe2+E+3hg2QaPdbEzro8cUYqUhJjd/Ab20j4xbuwr8Q38zctGXLru +uFtY1SmUHhsfT3UUAvZpBCuBMltMOw/JySwYxngyV2XG7X2LgRWhN2OG1/oJwsMo +XV/XKCPszpSwrkHXYWooFr2nB/wTvIAOKpLmennH65bctyDl0KqPYkopiFhEz6UD +qD76ghKkL4yQulkDUvRXZ1E/z+lvw0KY41p6PvMg7pCpPXVcZvj2ROQ0CDeKAepQ +YSspqIqiRrudd0gG0ZNV4asCLPFOUynTOfqiiwSPLco4VqBO7T3shkxQ/cNsDGWw +AiKAstYtAgMBAAECggEAVyvpFcDoyRpAZ02VOh0tO0IekFC0EcAAxPsW9rI77AdW +WZ94tuts2M9yyP+mfgRXzKYrlLpuB51Ff3xr5mvYATLnnNdQ7X9L5Q4F1JSOwQwo +0+mJtJekDdLZkx2g+wiBIsOONUO4xNUMkPGqyw3YvlFAIrWV7FwQ1dfvtnnKyR89 +cy4gLaef+E/BMUJcibg6AZhqk82Eeb0eemYGjpDRbePYOKx+2SeNlNg4ousWdd8O +2NKEpYktqnr1U+sEduOzYMQCFTllmxUWPm6NVDyBmzmJqsaFvhbrT5Y/gAloXrld +n7m1gh7/8XvOhlKULj/9QcfrpFIfS/m+/5HTIw+tdwKBgQDcHW6DkOOpsTJD7xVD +tvcTmbw/ktailm6FXJpdx9kpmwS+hh4doiZrWDqbNV1cYLIySSlPW/UOq50xEoFy +Ypk+zuYqd1U8kI/5xZenN2/k3bHhrooKczcku7R2eniCpeGX9STOMwxlqwrNxiPF +Mj5irzP6W/dIksImufoxVq22wwKBgQDM1CZTKqSneEoHbDyqFCJJpf0icKeZGEeL +X1Ax/3llvMKRfbQStSz4pcsEmGJ67nwnjMl+9WDuXH44ayYfXRDrlrGGLoUFAEwx +uYtJO4CeWgdFsbNAQ0tgS1UgHpgJwGzU+aZ4/+1n8rwQfIc9U9qJq85gdeClOCpk +AsYZQXPQTwKBgBoqyYuMevQeY5pd3spJAwBqg02PeEKl3YevJY7GhlOC4UKBES12 +rFiQiY8P/gRQXn7J4cZUaUKQNkSyLX9ap1snH4em3hQwH12TFt9DIOM1U8M/i5uI +bOLKCb8X+iGV9Maq1vBIc03W0pb52iDEQ1RZ/bTin/gZskkGTO72nfofAoGAQ5fC +6fEJkwZcD6UFQ+8O/ZTcOxBIDnp3qaOTeFxhOFSZNJ/x27aUF53GqEaNQRoQOJD6 +MX5SNXaqZH3ff7wbiFMbVDdnyCW3/zTGpS0QnbEB81rX114dsrJJbicKbzVj9MhJ +ymZSdtoge6RMkM1qMcJTgXuE3aDyRDyyGMWDrXsCgYAj9C2bdqyC9g6qF0YdcV3j +ZnxyqJOvT0w1d9orHw/Jr6GkJiCbzBX3a+F2OQ7exaCdxlS5crQoF4icboJzq0ng +jqps7X/u6+zrW9Em7O0GvQa6UT2Q/6LL1cwriwlOMOKe87zGLtoFaM82DxU/0HuN +qaYs9t11B8TqUSeYGwmnzQ== +-----END PRIVATE KEY----- diff --git a/tests/support/client_sha384.crt b/tests/support/client_sha384.crt new file mode 100644 index 0000000..8414948 --- /dev/null +++ b/tests/support/client_sha384.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEODCCAiCgAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCEswDQYJKoZIhvcNAQEM +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwODMxMTcxMzU1WhcNMzUwODI5MTcxMzU1WjATMREwDwYDVQQDDAhzc2xfdXNl +cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAd3WQ9BirmgmTFYSiA +EFr44lN7b4T7eGDZBo91sTOujxxRipSEmN38BvbSPjFu7CvxDfzNy0Zcuu64W1jV +KZQeGx9PdRQC9mkEK4EyW0w7D8nJLBjGeDJXZcbtfYuBFaE3Y4bX+gnCwyhdX9co +I+zOlLCuQddhaigWvacH/BO8gA4qkuZ6ecfrlty3IOXQqo9iSimIWETPpQOoPvqC +EqQvjJC6WQNS9FdnUT/P6W/DQpjjWno+8yDukKk9dVxm+PZE5DQIN4oB6lBhKymo +iqJGu513SAbRk1XhqwIs8U5TKdM5+qKLBI8tyjhWoE7tPeyGTFD9w2wMZbACIoCy +1i0CAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYI +KwYBBQUHAwIwHQYDVR0OBBYEFDxIpoyY05nscnzC3DY4ETjR3CO/MB8GA1UdIwQY +MBaAFAKENNyl/lPSx2iqUTTjIFbrHd4CMA0GCSqGSIb3DQEBDAUAA4ICAQCpYLo9 +XiQUSXOZ3lM8KVDjurlfx6nbbTd2J8oGsW4NJT0s12zAIUJstl7c2G8FMGiAFouK +uZBOPk7VIOCExFERJNcS/IMT+XJVb2IX9m9xwMmEkNYyOrnnVpsf2n27WS+umZE3 +GrJIPSr7IMhk+pIbXwPEmwKy8KvkA+/G91I+ZbP+qaRgKRlHhRmpoarN0u4LsbxH +AR+MB8kyjfXVpdbhU8eJJl3cLrKLcYN2VD/Zp9Am7g6AI2qDkCndtU3yx3Abzqfy +kkQCTEP/ZOu1HOaOdHlOmN4RgKBvnUOfpJonN49JetfCgwk+PxSWix9Nq7TeDqQ1 +4DBu0U0UIlhERkyIpJZyAulnhTzpUJjQ52uPs3roTuuAJM6iahaZ+cFhT3x+QiJE +bhdsqRVEkdic+DMR3kPDdfpOuGc0AE9KNFT5DoMTbGf3bTmRUAF/tMteWRD5+NDP +E7OpAgF1Aor08nU1vSNhKvZz7I1Y4LaemIWLPSjj1QMKBgX+Hu/74C6vTcF1WYr8 +woHZvw99jt9ChOVURrAR3pn6hCDjubsMpBi2qWRatWfVIAUwJmQgXLZ5f84YUgbf +giVbufElQZ2yNXQJbNXsfdWcuSR4xDvHDn3VS47mF317BJJHm3SW1Ug8k5o0Bb12 +WrLNo1YGTrbqNeFEINCx/ktVqvoztv2NhIzExg== +-----END CERTIFICATE----- diff --git a/tests/support/client_sha512.crt b/tests/support/client_sha512.crt new file mode 100644 index 0000000..3bb67d7 --- /dev/null +++ b/tests/support/client_sha512.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEODCCAiCgAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCEwwDQYJKoZIhvcNAQEN +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwODMxMTcxNDA3WhcNMzUwODI5MTcxNDA3WjATMREwDwYDVQQDDAhzc2xfdXNl +cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAd3WQ9BirmgmTFYSiA +EFr44lN7b4T7eGDZBo91sTOujxxRipSEmN38BvbSPjFu7CvxDfzNy0Zcuu64W1jV +KZQeGx9PdRQC9mkEK4EyW0w7D8nJLBjGeDJXZcbtfYuBFaE3Y4bX+gnCwyhdX9co +I+zOlLCuQddhaigWvacH/BO8gA4qkuZ6ecfrlty3IOXQqo9iSimIWETPpQOoPvqC +EqQvjJC6WQNS9FdnUT/P6W/DQpjjWno+8yDukKk9dVxm+PZE5DQIN4oB6lBhKymo +iqJGu513SAbRk1XhqwIs8U5TKdM5+qKLBI8tyjhWoE7tPeyGTFD9w2wMZbACIoCy +1i0CAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYI +KwYBBQUHAwIwHQYDVR0OBBYEFDxIpoyY05nscnzC3DY4ETjR3CO/MB8GA1UdIwQY +MBaAFAKENNyl/lPSx2iqUTTjIFbrHd4CMA0GCSqGSIb3DQEBDQUAA4ICAQBtZyR8 +27/5xhw9B+r6IONa/TgW6cX34OSbPGKYnG5VfXqqV/lJUSF9JNQdcBCs2V6u1Q+3 +Dft6z4dogpyBLM9vUYyJmjMMGoopRo+yAQRkF4k6+4ZLxWlUODXqZjslX0ri2CKF +9IxehyqHuOHzZBufpjhSw7wl8rib5FCNf+RbrsW91PH8Jy03XFBitIgIywy1naPU +EpFzG2NktcNOk7y1XFUCroFH1TxOrr1KhvecnWiibCcF4nkPwfvLPdpcml9Z64ed +MFAie6twar/Tmaumzf/KuhPpkTFs2NHQhGtXdaAHU3x7fof70MAGPTe3WZKsMISk +zlcxSKONoxKYxh5OpSsfhpdh8S+0xwk5S9UXiDlpv9UQwJy83RP23GJTEi8Ow9DH +tVfUJvdcLlm+PrGCAIB5fol1lymrK3f22hb0N/aLButcHJ8acmbIACQD/uIs1eBW +rAEfoyFITAv64dzqdRKdyaR8quS7MNQI7nkHJCz82HUZI2mWBiBwcNtT8I+ulm+w +Ud/lSmeMx+PyuBQ+auG6E7cTKKObeEN2bocK4hxRy7obtx2HtCmE8N8cRM/RE1Ft +qV7f+0u9xficDbH5TdmIEuWaXPb1owFABIv5s6+AW7zdFiq7NZUekEEWJPA4RGCZ +y1V2+XILwKpuENWSRCPJOCRQI+vDM24t+i/vFA== +-----END CERTIFICATE----- diff --git a/tests/support/docker.rs b/tests/support/docker.rs new file mode 100644 index 0000000..8dcb99a --- /dev/null +++ b/tests/support/docker.rs @@ -0,0 +1,188 @@ +use bollard::models::{ContainerCreateBody, HostConfig}; +use bollard::query_parameters::{ + CreateContainerOptionsBuilder, InspectContainerOptionsBuilder, RemoveContainerOptionsBuilder, + StartContainerOptionsBuilder, StopContainerOptionsBuilder, +}; +use bollard::Docker; +use std::collections::HashMap; +use std::env::current_dir; +use std::error::Error; +use tokio::time::{sleep, timeout, Duration, Instant}; +use tokio_postgres::{Config, NoTls}; + +pub(crate) struct PostgresContainer { + docker: Docker, + id: String, + pub port: u16, + closed: bool, +} + +impl PostgresContainer { + /// Spin up Postgres 17 + pub(crate) async fn new( + test_name: &str, + setup_script: &str, + ca_cert: &str, + server_cert: &str, + server_key: &str, + ) -> Result> { + let docker = Docker::connect_with_defaults()?; + let container_name = format!("test-pg-{}", test_name); + let pwd = current_dir()? + .to_str() + .expect("pwd pathref to str") + .to_owned(); + + let binds = vec![ + format!("{pwd}/{setup_script}:/docker-entrypoint-initdb.d/sql_setup.sh:ro"), + format!("{pwd}/{ca_cert}:/etc/postgresql/certs/ca.crt:ro"), + format!("{pwd}/{server_cert}:/etc/postgresql/certs/server.crt:ro"), + format!("{pwd}/{server_key}:/etc/postgresql/certs/server.key:ro"), + ]; + + let mut exposed: HashMap> = HashMap::new(); + exposed.insert("5433/tcp".into(), HashMap::new()); + + let host_config = HostConfig { + binds: Some(binds), + publish_all_ports: Some(true), + ..Default::default() + }; + + let env = vec![ + "POSTGRES_PASSWORD=postgres".to_string(), + "POSTGRES_USER=postgres".to_string(), + "POSTGRES_DB=postgres".to_string(), + ]; + + let body = ContainerCreateBody { + image: Some("postgres:17".to_string()), + env: Some(env), + exposed_ports: Some(exposed), + host_config: Some(host_config), + ..Default::default() + }; + + let created = docker + .create_container( + Some( + CreateContainerOptionsBuilder::default() + .name(&container_name) + .build(), + ), + body, + ) + .await?; + let id = created.id; + + docker + .start_container(&id, Some(StartContainerOptionsBuilder::default().build())) + .await?; + + let inspect = docker + .inspect_container(&id, Some(InspectContainerOptionsBuilder::default().build())) + .await?; + + let host_port = inspect + .network_settings + .as_ref() + .and_then(|ns| ns.ports.as_ref()) + .and_then(|ports| ports.get("5433/tcp")) + .and_then(|opt| opt.as_ref()) + .and_then(|vec| vec.first()) + .and_then(|pb| pb.host_port.as_ref()) + .and_then(|hp| hp.parse::().ok()) + .ok_or("failed to resolve host port for 5433/tcp")?; + + if !(wait_for_pg(host_port, Duration::from_secs(30)) + .await + .is_ok()) + { + cleanup(docker, id).await; + panic!("postgres container startup probe failed"); + }; + + Ok(Self { + docker, + id, + port: host_port, + closed: false, + }) + } + + #[must_use = "must cleanup container"] + pub(crate) async fn cleanup(&mut self) { + cleanup(self.docker.clone(), self.id.clone()).await; + // TODO: add error handling and mark `closed` conditional on `Ok` + self.closed = true; + } +} + +impl Drop for PostgresContainer { + fn drop(&mut self) { + // can't run async `cleanup` here, so best we can do is be noisy + // TODO: log instead of panic? + assert!(self.closed); + } +} + +async fn cleanup(docker: Docker, id: String) { + let _ = docker + .stop_container( + &id, + Some(StopContainerOptionsBuilder::default().t(5).build()), + ) + .await; + let _ = docker + .remove_container( + &id, + Some( + RemoveContainerOptionsBuilder::default() + .v(true) + .force(true) + .build(), + ), + ) + .await; +} + +async fn wait_for_pg(host_port: u16, max_wait: Duration) -> Result<(), &'static str> { + let mut cfg = Config::new(); + cfg.host("localhost") + .port(host_port) + .user("startup_probe") + .dbname("postgres") + .ssl_mode(tokio_postgres::config::SslMode::Disable) + .connect_timeout(Duration::from_secs(2)); + + let deadline = Instant::now() + max_wait; + + loop { + let Ok((client, conn)) = cfg.connect(NoTls).await else { + if Instant::now() >= deadline { + return Err("postgres not ready in time"); + } + sleep(Duration::from_millis(500)).await; + continue; + }; + let conn_task = tokio::spawn(async move { + let _ = conn.await; + }); + + let ok = timeout(Duration::from_secs(2), client.simple_query("SELECT 1")) + .await + .ok() + .and_then(|r| r.ok()) + .is_some(); + + conn_task.abort(); + + if ok { + return Ok(()); + } + if Instant::now() >= deadline { + return Err("postgres not ready in time"); + } + sleep(Duration::from_millis(500)).await; + } +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs new file mode 100644 index 0000000..65e1ecd --- /dev/null +++ b/tests/support/mod.rs @@ -0,0 +1,2 @@ +pub(super) mod certstore; +pub(super) mod docker; diff --git a/tests/support/server.crt b/tests/support/server.crt new file mode 100644 index 0000000..2ba8e67 --- /dev/null +++ b/tests/support/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAj+gAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCEcwDQYJKoZIhvcNAQEL +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwODMxMDYyOTIxWhcNMzUwODI5MDYyOTIxWjAUMRIwEAYDVQQDDAlsb2NhbGhv +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBvh7v41L8QC/NV8ku +YwU6KW2qov8bPbrmNaBR1FAKvrfWsKxb7hAABV2lb/hWUVPdXiaBkTk+SdVzfTAb +zSnAhH8DYjq1wcgqZs1f8ZFnQjyQeXGep8Rz8ai6N+G+Rc+Fr7Nak/XyYmOjE+20 +oztYt5aYJJbYbNC6QiARmhtJBL4mIEnoeztqPT0A10oTZ/Ive77++kY2RiLA/Ixc +LPFdSYgUsxS47xHlV2UibFqCDIBKhf25hX0NwDNOhVLQcuE0XoodIh0AETyyD7zW +FTwBICfds9XryFS6HNHXWgm6lZfWx1bWgw9DlKaSX4ERf7xJPI7m7N+jctEN4j+X +UjddAgMBAAGjgYwwgYkwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAww +CgYIKwYBBQUHAwEwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMB0GA1UdDgQW +BBTjkxY8SOkPy/sMImjLMGz7Til/4jAfBgNVHSMEGDAWgBQChDTcpf5T0sdoqlE0 +4yBW6x3eAjANBgkqhkiG9w0BAQsFAAOCAgEArkKyeGG1AEhc18jcoBzW2ecRvWuq +dCsCfCJvdQz2kQk9yQL7RZzvXDKbKBnn7PJuNGt4eZOm7RHe8lM8ERkapIduP02O +tsXHOabZGYj8TMp+IbqVq4Y49ZvCG63/RZ7RXmNhR6j+fEdsJAJpdsI5WhF6Qc64 +5BNyqXIsA2c14htnh7XIlIfKh5jICz/N21BwnIsHSdThE7mv46l1i+cl394X7UiL +XAXvLzpMFvvJXFRNfFdjgZkAfQtF2W4g7jdEwBiIuBeELo8S4HF0xw1aTPgqHlTr +pwwXqOq8Mlu+1ZyGKgh8WqmzQwVRBXg/56EHDt/QAmotILi62Qd9EDlJQoCktRHS +bXWcN5gWNHgQo+wQZHA7yxucKYSdiqgLseGArruf2XX7HC6GtkX2LerCa1r0p8bO +kYmdD1Xa8+bekZkDGO9G23X9OrmpPYO1gSIn/6AMu1pgJSAYxHN6aT9myiYYPpP7 +LF1XLJ3iCOBfUJwVcLLAEXdH87R0ym8+3aA7wA4P/eWR7rs62uOZHvNI7Ksu0qIk +aGkHdPsj72KOkWWxbXnOAg65tttNENz505iOQvhVbHjwLgjKtdAtn9xChKA5PVkl +ILTOHlqu+FhipBPVs0UZ5f5ZQt1VagW9yNjfiSC4tONmI4LcXsauu3IPvH4oDiq5 +ySXBsVOvE0O8+2Q= +-----END CERTIFICATE----- diff --git a/tests/support/server.key b/tests/support/server.key new file mode 100644 index 0000000..7c5a521 --- /dev/null +++ b/tests/support/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBvh7v41L8QC/N +V8kuYwU6KW2qov8bPbrmNaBR1FAKvrfWsKxb7hAABV2lb/hWUVPdXiaBkTk+SdVz +fTAbzSnAhH8DYjq1wcgqZs1f8ZFnQjyQeXGep8Rz8ai6N+G+Rc+Fr7Nak/XyYmOj +E+20oztYt5aYJJbYbNC6QiARmhtJBL4mIEnoeztqPT0A10oTZ/Ive77++kY2RiLA +/IxcLPFdSYgUsxS47xHlV2UibFqCDIBKhf25hX0NwDNOhVLQcuE0XoodIh0AETyy +D7zWFTwBICfds9XryFS6HNHXWgm6lZfWx1bWgw9DlKaSX4ERf7xJPI7m7N+jctEN +4j+XUjddAgMBAAECggEACzh0jNLloJAyh3lB77jmQP9H5JEqgTdyUaot88sUowvs +cpeenb1gdTKsRHxYXKq1lzhRxInZNVmwU/Jt4Ib6nJOON9jdHU6lhNlAML3VETo+ +v6XpHXYCkkkXilypMXSbJm4paOTRSUWYurKkr6cgS6NmVZZwf0Yl5RMxRSRv8KgQ +qJZ833RzccKuBaehsJNXb1IXJD8pADeVo1KJ5OLCqMnzB2zcbeebOfKKk//aXIO/ +9q4Aw52fMsm9TecwGgLaV92TtpAe0uS5UT8C0A66lpwNnksBnq3wBr+xWpZLBBvr +eQGVRkGuiEhPBWqu1cb8kY/PkZ1ltxunzTWDbCr7NwKBgQC2P17DQnMLXoN+qzWf +TZ8fQ2N1w+KEWlsRI0m6/mzkUP2cXRlP0JPfcsXk008M8XhZ5Ow3ceeDJT/HiBvI +Brst4eUgGtJTpA+vIy0egpKjxk1AN8QBMvEUt00MJxRYcK0rKPg/86Xo/Ut3yaZ7 +ki5xwT3oUl5LQMSsU7xfCShiWwKBgQC2P06fWZ35XnGRkuLlP0hiEJLhBcB15Lbm +VcMF6BUueDIvB4tDE+F3MnLxyD2p/YRT0M51AOuqI7f3BN0f7bH+T/iZ7r8qmJ5G +BbCu+YkGPeGcjqPXU2hmh7iekZUAXaQuRrwk5xyPBsAYHXJ3bQC2W68D3IRupecB +Z65Z8u+KpwKBgC4nTENcz6/AZsKsby8BxFtxgH2xdusXytpDOofdqQwFKsTvmtpo +sxoygcVacjmP6W+yltPPx9ahl05bvNViRwLuo00HHd7KvKIY4XNJlANf0+6AcOXw +1bbuWNfMCc3/8wrsHDpt5MVlaDhU3BGNSq/KRXhRa8nZBDW0Gw9iTVTjAoGAYKdA +hkhb/LW223KgPN6L/940V3zabmvnCE9xh79nBGcgjkqc8+0mRTYPOeVttqrKND1o +USs00N3yoeIFd/pyzKITAWhaIDgisJYx9wpGPnYxIfuQLxGAK+hM5GPnNvNysEw5 +WgTr43q8A84SN/4qQ4xqTEz2O0xnMBqRoAi0O78CgYEAlPz2EbEikMQqVik8OEFf +M1xg9uGV/6OqU+GCTkXxd57Spwmp+7/yeD/AxGCvmnWmIjoeljZu/6K3FDR8Fq2s +VbnFChvKYs1EHqcPX2+D8c44eG93ifFtHmVMeA2qfa+gCWLb0TM1m9+jfxn2S0CH +IOrU3mlx9ZRJl/nm6XCYdXk= +-----END PRIVATE KEY----- diff --git a/tests/support/server_sha384.crt b/tests/support/server_sha384.crt new file mode 100644 index 0000000..c95f1dc --- /dev/null +++ b/tests/support/server_sha384.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAj+gAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCE0wDQYJKoZIhvcNAQEM +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwOTAxMTg0NzMxWhcNMzUwODMwMTg0NzMxWjAUMRIwEAYDVQQDDAlsb2NhbGhv +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBvh7v41L8QC/NV8ku +YwU6KW2qov8bPbrmNaBR1FAKvrfWsKxb7hAABV2lb/hWUVPdXiaBkTk+SdVzfTAb +zSnAhH8DYjq1wcgqZs1f8ZFnQjyQeXGep8Rz8ai6N+G+Rc+Fr7Nak/XyYmOjE+20 +oztYt5aYJJbYbNC6QiARmhtJBL4mIEnoeztqPT0A10oTZ/Ive77++kY2RiLA/Ixc +LPFdSYgUsxS47xHlV2UibFqCDIBKhf25hX0NwDNOhVLQcuE0XoodIh0AETyyD7zW +FTwBICfds9XryFS6HNHXWgm6lZfWx1bWgw9DlKaSX4ERf7xJPI7m7N+jctEN4j+X +UjddAgMBAAGjgYwwgYkwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAww +CgYIKwYBBQUHAwEwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMB0GA1UdDgQW +BBTjkxY8SOkPy/sMImjLMGz7Til/4jAfBgNVHSMEGDAWgBQChDTcpf5T0sdoqlE0 +4yBW6x3eAjANBgkqhkiG9w0BAQwFAAOCAgEAK2sqMlAxC98Owl8FI/JZbhxZ+PO4 +4irTjfdNoDbZyDPmL/64MmQBvWr/jVIFqoeXDVKuU9LkWAelusqDepv1CqHMMDii +3E8McdPuDuis7Sa/YKthxLAysawCdxHLZ8qNUcrXytDym6FutKsEHXtLOk9B7AZx +LjKdd9ranjiWjndFQ0xJxDfHD+oxpXuhEFOvhZFBhXJCbZH6eM2QjYhGKJVgBVXO +Nj+OVH8Fl+S0UCeBme2azQaUqGFmGmeNtVhxc7EcpoJdK1EEJVmXh0zOiKVRhgl6 +OQYmHJzaNuVzP41/6Zug69NuBU81smNj/UVsulq+PJDcuokA765XqV5d/yfS385z +ObGRhjXCX82+mInofwhLHn90msgIc7ouzjqv6LyBXz0HnQvf8t4uoQgwP88yScQK +kYfaxjTo9DWw1qvtaTcytTmyJllvWpxHoQHFj8HY0zBsnSnRcRw04uG72IP0UKVP +cvGLfv7smss/f9jyWG2e+nlSIlz8r9jwxYqDijOwyn97AVf4RBRW1bYRmIEnlHp5 +Es+pNzPEowFvNsHVyEbB7/S2Z096w8oXKjoo6QF7J5wGmrIlbGGbMnQLReAp4crS +SzdIuZ3AiRfKVITNBbULLcmN1l6X/qhfWfLsboA1IfKpT7mjnp4pfAtueXgLoTCC +br/pwsY+KZoIbsw= +-----END CERTIFICATE----- diff --git a/tests/support/server_sha512.crt b/tests/support/server_sha512.crt new file mode 100644 index 0000000..3878775 --- /dev/null +++ b/tests/support/server_sha512.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAj+gAwIBAgIUBUXHF9U2gSfTKnUQIVPFKoucCE4wDQYJKoZIhvcNAQEN +BQAwKDEmMCQGA1UEAwwddG9raW8tcG9zdGdyZXMtcnVzdGxzIFRlc3QgQ0EwHhcN +MjUwOTAxMTg0NzQ2WhcNMzUwODMwMTg0NzQ2WjAUMRIwEAYDVQQDDAlsb2NhbGhv +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBvh7v41L8QC/NV8ku +YwU6KW2qov8bPbrmNaBR1FAKvrfWsKxb7hAABV2lb/hWUVPdXiaBkTk+SdVzfTAb +zSnAhH8DYjq1wcgqZs1f8ZFnQjyQeXGep8Rz8ai6N+G+Rc+Fr7Nak/XyYmOjE+20 +oztYt5aYJJbYbNC6QiARmhtJBL4mIEnoeztqPT0A10oTZ/Ive77++kY2RiLA/Ixc +LPFdSYgUsxS47xHlV2UibFqCDIBKhf25hX0NwDNOhVLQcuE0XoodIh0AETyyD7zW +FTwBICfds9XryFS6HNHXWgm6lZfWx1bWgw9DlKaSX4ERf7xJPI7m7N+jctEN4j+X +UjddAgMBAAGjgYwwgYkwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAww +CgYIKwYBBQUHAwEwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMB0GA1UdDgQW +BBTjkxY8SOkPy/sMImjLMGz7Til/4jAfBgNVHSMEGDAWgBQChDTcpf5T0sdoqlE0 +4yBW6x3eAjANBgkqhkiG9w0BAQ0FAAOCAgEApsYaWw3B+Cm0XxtrmQcppqwwwKjc +07IuNO556NtX56OroI6LF1zwnpJlbhzjd3HDxZ7hjKxGEndUUENas4jnhYAdhkQH +KEOzhvPR2AJPVNGfhbkPucf6n4IsMq/WIl4uLVzltvBO1o12oSSbgRK24us8yK2e +xAq0LT0mMqUz9RnC4WN+rvdvmPvJjtodvlMgc4wCFIVfJbYTeRjOC5efxZldYtsY +Ekp04fI2MNuw7KTF+z3ceKmktGtqW2ji5JiDAp1TWQ/6SEhIpOVUY6rlxBL9xZnD +NSopwmTXQmEdnN/74OG1K710vWsVRqawJDeY0a/WQZtxu0xFzeQtaNS8BCIq4Hd7 +3MpKvXK+JDxRXJFXY5jfvRBBdVgRWpFdgSUFKoMI3NpJT7z4NsKOCEwmdCAkFCRp +WxYZbPVIkxR2uDNhATq8IF8jOovJ7dqNzq22NSEFY0zLY+kVYGdmqwhNeHVgeGF0 +5OfgiYjp5qOpBz0IJEdR9U2myskMjQSGdc6yP/is1Gmu+L49GsljQRFLXl6apuhN +T2QxRxp+/l8O2cDVTa37jlFiVP8asCJ82/FBKGxTbrGS2vVEn/a/V6R7+KbSFLMM +pjMYaD7gi5Tms0ehT4BFotceWYVDtFjD8+62Bf9dHGGUAtQHS1HuYOhj1eluHJm8 +Ye7j/Go7JsEIS3s= +-----END CERTIFICATE----- diff --git a/tests/support/sql_setup.sh b/tests/support/sql_setup.sh new file mode 100755 index 0000000..0aa736a --- /dev/null +++ b/tests/support/sql_setup.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e +umask 077 + +cat /etc/postgresql/certs/ca.crt > $PGDATA/ca.crt +cat /etc/postgresql/certs/server.crt > $PGDATA/server.crt +cat /etc/postgresql/certs/server.key > $PGDATA/server.key + +cat >> "$PGDATA/postgresql.conf" <<-EOCONF +port = 5433 +ssl = on +ssl_ca_file = 'ca.crt' +ssl_cert_file = 'server.crt' +ssl_key_file = 'server.key' +EOCONF + +cat > "$PGDATA/pg_hba.conf" <<-EOCONF +# TYPE DATABASE USER ADDRESS METHOD OPTIONS +local all $POSTGRES_USER trust +host all startup_probe 0.0.0.0/0 trust +host all startup_probe ::0/0 trust +hostssl all scram_user 0.0.0.0/0 scram-sha-256 +hostssl all scram_user ::0/0 scram-sha-256 +hostssl all ssl_user 0.0.0.0/0 cert clientcert=verify-full +hostssl all ssl_user ::0/0 cert clientcert=verify-full +host all scram_user 0.0.0.0/0 reject +host all scram_user ::0/0 reject +host all ssl_user 0.0.0.0/0 reject +host all ssl_user ::0/0 reject +EOCONF + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + SET password_encryption TO 'scram-sha-256'; + CREATE ROLE scram_user PASSWORD 'password' LOGIN; + CREATE ROLE ssl_user LOGIN; + CREATE ROLE startup_probe LOGIN; +EOSQL From 9588ead26d244c4582fbf3d3c8be3ec21908d46c Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 16:03:32 -0500 Subject: [PATCH 05/11] add github workflow to run integration tests --- .github/workflows/rust.yml | 33 +++++++++++++++++++++++++++++++++ .yamllint.yml | 8 ++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/rust.yml create mode 100644 .yamllint.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..549ed90 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,33 @@ +name: Rust +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +env: + CARGO_TERM_COLOR: always +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Cache Postgres Image + uses: actions/cache@v3 + with: + path: /tmp/postgres-17.tar + key: postgres-17-${{ runner.os }} + - name: Pull Postgres 17 (if cache miss) + run: | + if [ ! -f /tmp/postgres-image.tar ]; then + docker pull postgres:17 + docker save postgres:17 -o /tmp/postgres-17.tar + fi + - name: Load Postgres 17 (if cache hit) + run: | + if [ -f /tmp/postgres-17.tar ]; then + docker load -i /tmp/postgres-17.tar + fi + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..a3692ad --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,8 @@ +extends: default +rules: + document-start: disable + brackets: + max-spaces-inside: 2 + braces: + max-spaces-inside: 2 + truthy: disable From 06d874c18f112b13dd46e1de7d527db40751d9b4 Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 16:48:52 -0500 Subject: [PATCH 06/11] remove original test Remove original test from upstream. It used zero cert verification, no client auth, and no channel binding. The new integration test suite supersedes this test. --- src/lib.rs | 77 ------------------------------------------------------ 1 file changed, 77 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 22e4e8b..6b02474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,80 +184,3 @@ where }) } } - -#[cfg(test)] -mod tests { - use super::*; - use rustls::pki_types::{CertificateDer, UnixTime}; - use rustls::{ - client::danger::ServerCertVerifier, - client::danger::{HandshakeSignatureValid, ServerCertVerified}, - Error, SignatureScheme, - }; - - #[derive(Debug)] - struct AcceptAllVerifier {} - impl ServerCertVerifier for AcceptAllVerifier { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn supported_verify_schemes(&self) -> Vec { - vec![ - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::ED25519, - ] - } - } - - #[tokio::test] - async fn it_works() { - env_logger::builder().is_test(true).try_init().unwrap(); - - let mut config = rustls::ClientConfig::builder() - .with_root_certificates(rustls::RootCertStore::empty()) - .with_no_client_auth(); - config - .dangerous() - .set_certificate_verifier(Arc::new(AcceptAllVerifier {})); - let tls = super::MakeRustlsConnect::new(config); - let (client, conn) = tokio_postgres::connect( - "sslmode=require host=localhost port=5432 user=postgres", - tls, - ) - .await - .expect("connect"); - tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) }); - let stmt = client.prepare("SELECT 1").await.expect("prepare"); - let _ = client.query(&stmt, &[]).await.expect("query"); - } -} From 731cb9d4edbd543772dbff20eb3ec8695ab59968 Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 18:08:58 -0500 Subject: [PATCH 07/11] ci: use nextest + llvm-cov and upload to codecov --- .github/workflows/rust.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 549ed90..ca25e67 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,23 +11,28 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Postgres Image - uses: actions/cache@v3 + id: cache-postgres-image + uses: actions/cache@v4 with: path: /tmp/postgres-17.tar key: postgres-17-${{ runner.os }} - - name: Pull Postgres 17 (if cache miss) + - name: Pull and Save Postgres 17 (if cache miss) + if: steps.cache-postgres-image.outputs.cache-hit != 'true' run: | - if [ ! -f /tmp/postgres-image.tar ]; then - docker pull postgres:17 - docker save postgres:17 -o /tmp/postgres-17.tar - fi + docker pull postgres:17 + docker save postgres:17 -o /tmp/postgres-17.tar - name: Load Postgres 17 (if cache hit) + if: steps.cache-postgres-image.outputs.cache-hit == 'true' run: | - if [ -f /tmp/postgres-17.tar ]; then - docker load -i /tmp/postgres-17.tar - fi + docker load -i /tmp/postgres-17.tar - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose + - uses: taiki-e/install-action@cargo-nextest + - uses: taiki-e/install-action@cargo-llvm-cov - name: Run tests - run: cargo test --verbose + run: cargo llvm-cov nextest + - name: Generate coverage report + run: cargo llvm-cov report --codecov --output-path coverage.json + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} From 608b823fb2aef72159264c7c77f37c795c20211e Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 18:41:59 -0500 Subject: [PATCH 08/11] add codecov junit test report --- .config/nextest.toml | 2 ++ .github/workflows/rust.yml | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000..76fd74b --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,2 @@ +[profile.ci.junit] +path = "junit.xml" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ca25e67..a8b0b46 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -29,9 +29,14 @@ jobs: - uses: taiki-e/install-action@cargo-nextest - uses: taiki-e/install-action@cargo-llvm-cov - name: Run tests - run: cargo llvm-cov nextest + run: cargo llvm-cov nextest --profile ci - name: Generate coverage report run: cargo llvm-cov report --codecov --output-path coverage.json + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} - name: Upload coverage report to Codecov uses: codecov/codecov-action@v5 with: From 8f5cf53f7704f71c91e2d8748ca5b90c11d80d1f Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 19:50:34 -0500 Subject: [PATCH 09/11] add `tokio-postgres` crate `runtime` feature to main dependencies The main `lib.rs` uses imports (e.g. `MakeTlsConnect`) that are behind the `runtime` feature gate in `tokio-postgres` so it is NOT a development-only dependency. --- Cargo.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0e6641..5e39049 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,16 +13,15 @@ const-oid = { version = "0.9.6", default-features = false, features = ["db"] } ring = { version = "0.17", default-features = false } rustls = { version = "0.23", default-features = false } tokio = { version = "1", default-features = false } -tokio-postgres = { version = "0.7", default-features = false } +tokio-postgres = { version = "0.7", default-features = false, features = [ + "runtime", +] } tokio-rustls = { version = "0.26", default-features = false } x509-cert = { version = "0.2.5", default-features = false, features = ["std"] } [dev-dependencies] env_logger = { version = "0.11", default-features = false } tokio = { version = "1", default-features = false, features = ["macros", "rt"] } -tokio-postgres = { version = "0.7", default-features = false, features = [ - "runtime", -] } rustls = { version = "0.23", default-features = false, features = [ "std", "logging", From 48aa92e4f9327da0ecab952b179f726c18c39069 Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Mon, 1 Sep 2025 20:00:13 -0500 Subject: [PATCH 10/11] update `Cargo.toml` version to `0.14.0` and `authors` list Bump version from `0.13.0` to `0.14.0` and add all contributors with two or more commits to `authors` list (in-order of number of commits). --- Cargo.toml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e39049..ceed1b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,17 @@ [package] name = "tokio-postgres-rustls" description = "Rustls integration for tokio-postgres" -version = "0.13.0" -authors = ["Jasper Hugo "] +version = "0.14.0" +authors = [ + "Jasper Hugo ", + "Dwayne Sykes ", + "Aumetra Weisman ", + "Conrad Ludgate ", + "Karsten Borgwaldt ", + "Philip Dubé ", + "Michael Sowka ", + "ol ", +] repository = "https://github.com/jbg/tokio-postgres-rustls" edition = "2018" license = "MIT" From a962650e3f86389f851b9c889a75f978b01e322f Mon Sep 17 00:00:00 2001 From: Dwayne Sykes Date: Tue, 2 Sep 2025 03:00:04 -0500 Subject: [PATCH 11/11] add support for `aws-lc-rs` and make `ring` optional - Refactor channel binding code - Remove hard dependency on `ring` - Default to `aws-lc-rs` feature - Add optional `ring` feature - Add test run w/ `ring` feature to CI pipeline --- .github/workflows/rust.yml | 7 ++++++- Cargo.toml | 14 +++++++++----- src/lib.rs | 39 +++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a8b0b46..1ddada6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,8 +28,13 @@ jobs: - uses: actions/checkout@v4 - uses: taiki-e/install-action@cargo-nextest - uses: taiki-e/install-action@cargo-llvm-cov - - name: Run tests + - name: Run tests w/ aws-lc-rs (default) run: cargo llvm-cov nextest --profile ci + - name: Run tests w/ ring (optional) + run: > + cargo llvm-cov nextest + --no-default-features + --features ring - name: Generate coverage report run: cargo llvm-cov report --codecov --output-path coverage.json - name: Upload test results to Codecov diff --git a/Cargo.toml b/Cargo.toml index ceed1b0..e4b4a54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,14 @@ edition = "2018" license = "MIT" readme = "README.md" +[features] +default = ["aws-lc-rs"] +aws-lc-rs = ["rustls/aws-lc-rs"] +ring = ["rustls/ring"] + [dependencies] -const-oid = { version = "0.9.6", default-features = false, features = ["db"] } -ring = { version = "0.17", default-features = false } rustls = { version = "0.23", default-features = false } +sha2 = { version = "0.10", default-features = false, features = ["oid"] } tokio = { version = "1", default-features = false } tokio-postgres = { version = "0.7", default-features = false, features = [ "runtime", @@ -29,12 +33,12 @@ tokio-rustls = { version = "0.26", default-features = false } x509-cert = { version = "0.2.5", default-features = false, features = ["std"] } [dev-dependencies] +bollard = { version = "0.19.2" } env_logger = { version = "0.11", default-features = false } -tokio = { version = "1", default-features = false, features = ["macros", "rt"] } rustls = { version = "0.23", default-features = false, features = [ "std", "logging", "tls12", - "ring", ] } -bollard = { version = "0.19.2" } +sha2 = { version = "0.10", default-features = false, features = ["std", "oid"] } +tokio = { version = "1", default-features = false, features = ["macros", "rt"] } diff --git a/src/lib.rs b/src/lib.rs index 6b02474..5fe83b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,8 @@ mod private { task::{Context, Poll}, }; - use const_oid::db::{ + use rustls::pki_types::ServerName; + use sha2::digest::const_oid::db::{ rfc5912::{ ECDSA_WITH_SHA_256, ECDSA_WITH_SHA_384, ID_SHA_1, ID_SHA_256, ID_SHA_384, ID_SHA_512, SHA_1_WITH_RSA_ENCRYPTION, SHA_256_WITH_RSA_ENCRYPTION, SHA_384_WITH_RSA_ENCRYPTION, @@ -25,8 +26,7 @@ mod private { }, rfc8410::ID_ED_25519, }; - use ring::digest; - use rustls::pki_types::ServerName; + use sha2::{Digest, Sha256, Sha384, Sha512}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_postgres::tls::{ChannelBinding, TlsConnect}; use tokio_rustls::{client::TlsStream, TlsConnector}; @@ -78,31 +78,32 @@ mod private { fn channel_binding(&self) -> ChannelBinding { let (_, session) = self.0.get_ref(); match session.peer_certificates() { - Some(certs) if !certs.is_empty() => Certificate::from_der(&certs[0]) - .ok() - .and_then(|cert| { - let digest = match cert.signature_algorithm.oid { + Some(certs) if !certs.is_empty() => Certificate::from_der(&certs[0]).map_or_else( + |_| ChannelBinding::none(), + |cert| { + match cert.signature_algorithm.oid { // Note: SHA1 is upgraded to SHA256 as per https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 ID_SHA_1 | ID_SHA_256 | SHA_1_WITH_RSA_ENCRYPTION | SHA_256_WITH_RSA_ENCRYPTION - | ECDSA_WITH_SHA_256 => &digest::SHA256, + | ECDSA_WITH_SHA_256 => ChannelBinding::tls_server_end_point( + Sha256::digest(certs[0].as_ref()).to_vec(), + ), ID_SHA_384 | SHA_384_WITH_RSA_ENCRYPTION | ECDSA_WITH_SHA_384 => { - &digest::SHA384 + ChannelBinding::tls_server_end_point( + Sha384::digest(certs[0].as_ref()).to_vec(), + ) } ID_SHA_512 | SHA_512_WITH_RSA_ENCRYPTION | ID_ED_25519 => { - &digest::SHA512 + ChannelBinding::tls_server_end_point( + Sha512::digest(certs[0].as_ref()).to_vec(), + ) } - _ => return None, - }; - - Some(digest) - }) - .map_or_else(ChannelBinding::none, |algorithm| { - let hash = digest::digest(algorithm, certs[0].as_ref()); - ChannelBinding::tls_server_end_point(hash.as_ref().into()) - }), + _ => ChannelBinding::none(), + } + }, + ), _ => ChannelBinding::none(), } }