From c9d594c5b8decaedf0f6eb95758984c77d146eac Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Fri, 22 May 2026 09:47:45 -0700 Subject: [PATCH 1/2] feat: load intermediate certificates for client auth --- pgdog/src/net/tls.rs | 191 ++++++++++++------ pgdog/tests/tls/ca_chain.pem | 39 ++++ pgdog/tests/tls/ca_intermediate.pem | 20 ++ pgdog/tests/tls/ca_root.pem | 19 ++ .../tls/client_signed_by_intermediate.pem | 20 ++ 5 files changed, 228 insertions(+), 61 deletions(-) create mode 100644 pgdog/tests/tls/ca_chain.pem create mode 100644 pgdog/tests/tls/ca_intermediate.pem create mode 100644 pgdog/tests/tls/ca_root.pem create mode 100644 pgdog/tests/tls/client_signed_by_intermediate.pem diff --git a/pgdog/src/net/tls.rs b/pgdog/src/net/tls.rs index 99781e04d..f1c91375b 100644 --- a/pgdog/src/net/tls.rs +++ b/pgdog/src/net/tls.rs @@ -179,93 +179,84 @@ fn build_acceptor(cert: &Path, key: &Path, client_ca: Option<&Path>) -> Result Result, Error> { - debug!("loading client CA certificate from: {}", ca_path.display()); + let roots = load_ca_bundle(ca_path, "client CA")?; - let certs = CertificateDer::pem_file_iter(ca_path) + WebPkiClientVerifier::builder(Arc::new(roots)) + .build() + .map_err(|e| invalid_data(format!("failed to build client certificate verifier: {e}"))) +} + +/// Load a PEM bundle from `path` and turn it into a `RootCertStore`. Every PEM block +/// in the file is added as a trust anchor, so a single file can carry a root CA +/// together with one or more intermediate CAs that signed leaf client certificates. +fn load_ca_bundle(path: &Path, label: &str) -> Result { + debug!("loading {label} bundle from {}", path.display()); + + let certs = CertificateDer::pem_file_iter(path) .map_err(|e| { - Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to read client CA certificate file: {}", e), + invalid_data(format!( + "failed to read {label} file {}: {e}", + path.display() )) })? .collect::, _>>() .map_err(|e| { - Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to parse client CA certificates: {}", e), + invalid_data(format!( + "failed to parse {label} from {}: {e}", + path.display() )) })?; if certs.is_empty() { - return Err(Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "No valid certificates found in client CA file", + return Err(invalid_data(format!( + "no PEM certificates found in {label} file {}", + path.display() ))); } + let total = certs.len(); let mut roots = rustls::RootCertStore::empty(); - let (added, _ignored) = roots.add_parsable_certificates(certs); - debug!("added {} client CA certificates from file", added); + let (added, ignored) = roots.add_parsable_certificates(certs); - if added == 0 { - return Err(Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "No valid certificates could be added from client CA file", + if ignored > 0 { + return Err(invalid_data(format!( + "{ignored} of {total} certificates in {label} bundle {} could not be loaded as trust anchors", + path.display() ))); } - WebPkiClientVerifier::builder(Arc::new(roots)) - .build() - .map_err(|e| { - Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to build client certificate verifier: {}", e), - )) - }) -} + if added == 0 { + return Err(invalid_data(format!( + "no valid trust anchors in {label} bundle {}", + path.display() + ))); + } -fn build_connector(config_key: &ConnectorConfigKey) -> Result, Error> { - let mut roots = rustls::RootCertStore::empty(); + info!( + path = %path.display(), + certs = added, + "🔐 loaded {label} bundle" + ); - if let Some(ca_path) = config_key.ca_path.as_ref() { - debug!("loading CA certificate from: {}", ca_path.display()); - - let certs = CertificateDer::pem_file_iter(ca_path) - .map_err(|e| { - Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to read CA certificate file: {}", e), - )) - })? - .collect::, _>>() - .map_err(|e| { - Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to parse CA certificates: {}", e), - )) - })?; - - if certs.is_empty() { - return Err(Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "No valid certificates found in CA file", - ))); - } + Ok(roots) +} - let (added, _ignored) = roots.add_parsable_certificates(certs); - debug!("added {} CA certificates from file", added); +fn invalid_data(msg: impl Into) -> Error { + Error::Io(std::io::Error::new( + std::io::ErrorKind::InvalidData, + msg.into(), + )) +} - if added == 0 { - return Err(Error::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "No valid certificates could be added from CA file", - ))); - } +fn build_connector(config_key: &ConnectorConfigKey) -> Result, Error> { + let roots = if let Some(ca_path) = config_key.ca_path.as_ref() { + load_ca_bundle(ca_path, "server CA")? } else if matches!( config_key.mode, TlsVerifyMode::VerifyCa | TlsVerifyMode::VerifyFull ) { debug!("no custom CA certificate provided, loading system certificates"); + let mut roots = rustls::RootCertStore::empty(); let result = rustls_native_certs::load_native_certs(); for cert in result.certs { roots.add(cert)?; @@ -277,7 +268,10 @@ fn build_connector(config_key: &ConnectorConfigKey) -> Result, ); } debug!("loaded {} system CA certificates", roots.len()); - } + roots + } else { + rustls::RootCertStore::empty() + }; let config = match config_key.mode { TlsVerifyMode::Disabled => ClientConfig::builder() @@ -714,4 +708,79 @@ mod tests { fn cn_from_empty_certs() { assert_eq!(cn_from_certs(&[]), None); } + + #[test] + fn load_ca_bundle_loads_full_chain() { + crate::logger(); + + let chain = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tls/ca_chain.pem"); + let roots = super::load_ca_bundle(&chain, "client CA") + .expect("ca_chain.pem bundles root + intermediate"); + + assert_eq!(roots.len(), 2, "every PEM block becomes a trust anchor"); + } + + #[test] + fn load_ca_bundle_errors_on_missing_file() { + crate::logger(); + let missing = PathBuf::from("/tmp/pgdog_nonexistent_ca.pem"); + assert!(super::load_ca_bundle(&missing, "client CA").is_err()); + } + + #[test] + fn client_cert_verifier_accepts_intermediate_signed_cert() { + crate::logger(); + + let chain = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tls/ca_chain.pem"); + let verifier = + super::build_client_cert_verifier(&chain).expect("verifier builds from chain bundle"); + + let client_pem = include_str!("../../tests/tls/client_signed_by_intermediate.pem"); + let leaf: CertificateDer<'static> = + rustls_pki_types::CertificateDer::pem_slice_iter(client_pem.as_bytes()) + .next() + .expect("client cert PEM has one block") + .expect("client cert parses"); + + // Use the cert's notBefore as "now" so the test does not drift if the fixture + // gets regenerated with a non-current validity window. + use x509_parser::certificate::X509Certificate; + let (_, parsed) = X509Certificate::from_der(&leaf).expect("parse leaf cert"); + let now = rustls::pki_types::UnixTime::since_unix_epoch(std::time::Duration::from_secs( + parsed.validity().not_before.timestamp() as u64 + 60, + )); + + verifier + .verify_client_cert(&leaf, &[], now) + .expect("intermediate trust anchor accepts leaf signed by it"); + } + + #[test] + fn client_cert_verifier_rejects_unknown_signer_when_only_root_loaded() { + crate::logger(); + + // Trust store contains only the root; client presents only the leaf + // (no intermediate in the handshake), so webpki cannot build the chain. + let root_only = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tls/ca_root.pem"); + let verifier = super::build_client_cert_verifier(&root_only) + .expect("verifier builds from root-only bundle"); + + let client_pem = include_str!("../../tests/tls/client_signed_by_intermediate.pem"); + let leaf: CertificateDer<'static> = + rustls_pki_types::CertificateDer::pem_slice_iter(client_pem.as_bytes()) + .next() + .expect("client cert PEM has one block") + .expect("client cert parses"); + + use x509_parser::certificate::X509Certificate; + let (_, parsed) = X509Certificate::from_der(&leaf).expect("parse leaf cert"); + let now = rustls::pki_types::UnixTime::since_unix_epoch(std::time::Duration::from_secs( + parsed.validity().not_before.timestamp() as u64 + 60, + )); + + assert!( + verifier.verify_client_cert(&leaf, &[], now).is_err(), + "leaf signed by missing intermediate must be rejected" + ); + } } diff --git a/pgdog/tests/tls/ca_chain.pem b/pgdog/tests/tls/ca_chain.pem new file mode 100644 index 000000000..9806f26a0 --- /dev/null +++ b/pgdog/tests/tls/ca_chain.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgIUNbZiJgI3lNf/AKcHxZtJJhML058wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MB4XDTI2MDUyMjE2NDAwOFoX +DTM2MDUxOTE2NDAwOFowIjEgMB4GA1UEAwwXUGdEb2cgVGVzdCBJbnRlcm1lZGlh +dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzjL2kYgLdLOJpy+YC +V9+S2trjzw9pCfFmE0QQ2UOJNu0pDW1x5or+YbLBygz4mWBzVBLF2t8qFpX9MKGX +O0vh1rcmHqC5Ih0ZDko2S1aQBPbyDp+ZWCotEImRsBzQvIdHP3ONWkj2aMo1G9Vz +8OfRgViTaR8NwRswhFi0YZTzccltvuPE4H2fgfsMOa2+/uuTRBc9mrmTpkurKO4x +arWB9D8xBwXUClC4PWa8mXwyzMxxpUVBlb/mD3Dxojgf5+8/g0bQ81CBYpf/pUoy +jDzct6wYdOsvx4+CHX6mWntQ0vKF3uk4Kbg1aUW9xfXr4i97JgAuqYnJN9KquuhB +7sgDAgMBAAGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBSj7hC3e+fc6Ch7v3bj6YsKaopSozAfBgNVHSMEGDAWgBTNRDLr +N0oIVI6H9uQifnNL2St+QTANBgkqhkiG9w0BAQsFAAOCAQEAjS4XY/XVkdM0aG2t +cZR1Dcx9/WR4NGq8QHoJufL/4mVJBN3vmEan1FoGnG+I9XKntxHFrnkY7Rok4kjK +HFOlMOaVa39YobK46LbWbstvuigCLgAq3c/bRHDugH/FovSiK+DpNbAv9gRZKsEg +HtTGQ/66kEGttNBwBejlk2gR9/4lEWT39YLjUKwuac4q4XV3JZ4LuovNNnkJD3Bg +wZHJ46yegcUsAtvQz97p1pWlWLMjvMhe1OGQFoF3rXo2IpNxtZRskIFCFzlx7qpH +S9iSapONS0Xmb/LeVtieNMyHfPT5lMIG7FQyyK8AqsL6Cft5burivqtRFtasDGMx +5q6SHA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUMStMbZtebPcG1K2pH+cHix7A3bgwDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MB4XDTI2MDUyMjE2NDAwMloX +DTM2MDUxOTE2NDAwMlowGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq9iPP2qLXtYPlWTNLzN99Gn8hZXc +DNnZuPzjHcGyDv+o+6qxhlNASiAwLI1ieETTtYvLMoOoZKTN0GpMtZJzNAlw0bbS +yxB2bIMIVUqSkwhZG3r288nPo7ioF3vKccr5ymAB7BxIx66O2F4qbq3fQCNCJcnl +jooa4b991D+4aCHDBixdTg+U+KSoTobp9+YnF6lEwv2LsB81cDBjE5DxBtrJhcOD +or6N23xkULeHnpRzCRl/HfkIaCiY85awGHuMeYnfIhbxeyqDmroO4q4oxB80Ft3D +/BHj5ccIHRBOI7dkjNZR4PM60rdUn4AAnqM9+wYtW8vDEAha9FQ64itBowIDAQAB +o2MwYTAdBgNVHQ4EFgQUzUQy6zdKCFSOh/bkIn5zS9krfkEwHwYDVR0jBBgwFoAU +zUQy6zdKCFSOh/bkIn5zS9krfkEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAAOBg8RHwjRsGBh+zJoXlXgc6uibmd13 +wYnGriI2k/xAEFmnq+QqjU9U87HDJZWY6bfaXvcnsymsTh/G41uRF0L9ZIcWuAOw +CN+O4PSRbJSoqfcN5ptgQtkIaZvtaVPKQriSCpcr/VYL4DYxbwe4b2Hb7rA/RqXR +snaJs7h0GuWYUAVRZEkmcPlF3uZZo8xSW7UGywO84hH3fah3Y3cn4hreNkzsKjb/ +elpcleR0nrDmzPvNp1t/VvZehTp25ocqyP5T55ccthEnFIoubpRPiXNm4GBQ2zDp +LmSEfQesuZ9uFj/xMeDUxrOMg+qhoxLSgr9kkkt4ArwataFMa9re6pI= +-----END CERTIFICATE----- diff --git a/pgdog/tests/tls/ca_intermediate.pem b/pgdog/tests/tls/ca_intermediate.pem new file mode 100644 index 000000000..9bd166305 --- /dev/null +++ b/pgdog/tests/tls/ca_intermediate.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgIUNbZiJgI3lNf/AKcHxZtJJhML058wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MB4XDTI2MDUyMjE2NDAwOFoX +DTM2MDUxOTE2NDAwOFowIjEgMB4GA1UEAwwXUGdEb2cgVGVzdCBJbnRlcm1lZGlh +dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzjL2kYgLdLOJpy+YC +V9+S2trjzw9pCfFmE0QQ2UOJNu0pDW1x5or+YbLBygz4mWBzVBLF2t8qFpX9MKGX +O0vh1rcmHqC5Ih0ZDko2S1aQBPbyDp+ZWCotEImRsBzQvIdHP3ONWkj2aMo1G9Vz +8OfRgViTaR8NwRswhFi0YZTzccltvuPE4H2fgfsMOa2+/uuTRBc9mrmTpkurKO4x +arWB9D8xBwXUClC4PWa8mXwyzMxxpUVBlb/mD3Dxojgf5+8/g0bQ81CBYpf/pUoy +jDzct6wYdOsvx4+CHX6mWntQ0vKF3uk4Kbg1aUW9xfXr4i97JgAuqYnJN9KquuhB +7sgDAgMBAAGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBSj7hC3e+fc6Ch7v3bj6YsKaopSozAfBgNVHSMEGDAWgBTNRDLr +N0oIVI6H9uQifnNL2St+QTANBgkqhkiG9w0BAQsFAAOCAQEAjS4XY/XVkdM0aG2t +cZR1Dcx9/WR4NGq8QHoJufL/4mVJBN3vmEan1FoGnG+I9XKntxHFrnkY7Rok4kjK +HFOlMOaVa39YobK46LbWbstvuigCLgAq3c/bRHDugH/FovSiK+DpNbAv9gRZKsEg +HtTGQ/66kEGttNBwBejlk2gR9/4lEWT39YLjUKwuac4q4XV3JZ4LuovNNnkJD3Bg +wZHJ46yegcUsAtvQz97p1pWlWLMjvMhe1OGQFoF3rXo2IpNxtZRskIFCFzlx7qpH +S9iSapONS0Xmb/LeVtieNMyHfPT5lMIG7FQyyK8AqsL6Cft5burivqtRFtasDGMx +5q6SHA== +-----END CERTIFICATE----- diff --git a/pgdog/tests/tls/ca_root.pem b/pgdog/tests/tls/ca_root.pem new file mode 100644 index 000000000..dd065932b --- /dev/null +++ b/pgdog/tests/tls/ca_root.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUMStMbZtebPcG1K2pH+cHix7A3bgwDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MB4XDTI2MDUyMjE2NDAwMloX +DTM2MDUxOTE2NDAwMlowGjEYMBYGA1UEAwwPUGdEb2cgVGVzdCBSb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq9iPP2qLXtYPlWTNLzN99Gn8hZXc +DNnZuPzjHcGyDv+o+6qxhlNASiAwLI1ieETTtYvLMoOoZKTN0GpMtZJzNAlw0bbS +yxB2bIMIVUqSkwhZG3r288nPo7ioF3vKccr5ymAB7BxIx66O2F4qbq3fQCNCJcnl +jooa4b991D+4aCHDBixdTg+U+KSoTobp9+YnF6lEwv2LsB81cDBjE5DxBtrJhcOD +or6N23xkULeHnpRzCRl/HfkIaCiY85awGHuMeYnfIhbxeyqDmroO4q4oxB80Ft3D +/BHj5ccIHRBOI7dkjNZR4PM60rdUn4AAnqM9+wYtW8vDEAha9FQ64itBowIDAQAB +o2MwYTAdBgNVHQ4EFgQUzUQy6zdKCFSOh/bkIn5zS9krfkEwHwYDVR0jBBgwFoAU +zUQy6zdKCFSOh/bkIn5zS9krfkEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAAOBg8RHwjRsGBh+zJoXlXgc6uibmd13 +wYnGriI2k/xAEFmnq+QqjU9U87HDJZWY6bfaXvcnsymsTh/G41uRF0L9ZIcWuAOw +CN+O4PSRbJSoqfcN5ptgQtkIaZvtaVPKQriSCpcr/VYL4DYxbwe4b2Hb7rA/RqXR +snaJs7h0GuWYUAVRZEkmcPlF3uZZo8xSW7UGywO84hH3fah3Y3cn4hreNkzsKjb/ +elpcleR0nrDmzPvNp1t/VvZehTp25ocqyP5T55ccthEnFIoubpRPiXNm4GBQ2zDp +LmSEfQesuZ9uFj/xMeDUxrOMg+qhoxLSgr9kkkt4ArwataFMa9re6pI= +-----END CERTIFICATE----- diff --git a/pgdog/tests/tls/client_signed_by_intermediate.pem b/pgdog/tests/tls/client_signed_by_intermediate.pem new file mode 100644 index 000000000..05c0583ab --- /dev/null +++ b/pgdog/tests/tls/client_signed_by_intermediate.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIUNppAX0RIYSjUHmhXm4DbXTSi+xcwDQYJKoZIhvcNAQEL +BQAwIjEgMB4GA1UEAwwXUGdEb2cgVGVzdCBJbnRlcm1lZGlhdGUwHhcNMjYwNTIy +MTY0MTIzWhcNMzYwNTE5MTY0MTIzWjAcMRowGAYDVQQDDBFwZ2RvZy10ZXN0LWNs +aWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMICD7yKxp3chqMd +PczFZArTX4D+XmuuJHueeuaOf6X7q5bR9u629t3E3dKABmQwG+tKEZQkMAINj0k4 +cpPI3spul2Ir+/h22225SQb3cSrAKlQwLF/MAqb1RmxJQrzVgO/F+ChBN+1voe9O +9nIbNI53BJLEKHvtXmgc6RSjhPyMyZ3PXznnWcak5iFTXwCv4xskSzW9XmkPHv9H +Fnw7So6JTEmKiXa+4Q86GYnNbo5q8RhKy11aTuejkT5KTn3WMjhPabzC/Vzj4nPg +X0qruY5bBLcAkpEpjOy16AdUK0kjkxy/PLA7fG5cxvDjxK6hzI6+/HBAY0qKUjk0 +dIbj8zECAwEAAaN1MHMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYD +VR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFDCamTERPmqBXCHpBuMsrfmCaNwn +MB8GA1UdIwQYMBaAFKPuELd759zoKHu/duPpiwpqilKjMA0GCSqGSIb3DQEBCwUA +A4IBAQBdM/3mPl38TMLAoRJpYXETh/Wrpt0zRHbKL1SHzVx9ttrx9P2JkEhfUCQo +7s6XRuvEjnolT+WY1L+qjjTs/xw0ssZgnxKmgIoXaDYu3KwBq1Hci27VtawRxie9 +Wk10BtFRVyS7OE4vVFsrfRz3uOgw5qwwgndm+P5p2A4oLlDYGBhtjYiF9v/ylOGk +hGIbzeOKxFB0wP/QT0sbtdPNy/sHXFNRs9cWRYhVzVkiRmpKLBJWxyKb67ztyB2u +PE1t5AbzF2BcYAK5f8XamHxNneNnVIl5gQsJ0ldPhIGvN3JoQBMoL6ez6/F71N+G +Xp7w+9r2WOVBaerUknu48KOgZJmv +-----END CERTIFICATE----- From 050f23dbb1200f646807ae88535285d9d64221d0 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Fri, 22 May 2026 11:10:21 -0700 Subject: [PATCH 2/2] Fight CI --- integration/load_balancer/pgx/reload_auto_role_test.go | 4 ++-- integration/tls/run.sh | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/integration/load_balancer/pgx/reload_auto_role_test.go b/integration/load_balancer/pgx/reload_auto_role_test.go index 25dde72de..e1ae2fc23 100644 --- a/integration/load_balancer/pgx/reload_auto_role_test.go +++ b/integration/load_balancer/pgx/reload_auto_role_test.go @@ -139,8 +139,8 @@ func TestReloadWithAutoRole(t *testing.T) { t.Logf("reloads: %d, write errors: %d, read errors: %d", reloads.Load(), writeErrors.Load(), readErrors.Load()) - assert.LessOrEqual(t, writeErrors.Load(), 5, "expected no write errors from reload with auto role detection") - assert.LessOrEqual(t, readErrors.Load(), 5, "expected no read errors from reload with auto role detection") + assert.LessOrEqual(t, writeErrors.Load(), int64(5), "expected no write errors from reload with auto role detection") + assert.LessOrEqual(t, readErrors.Load(), int64(5), "expected no read errors from reload with auto role detection") } // TestReconnectWithAutoRole validates that RECONNECT doesn't break read/write diff --git a/integration/tls/run.sh b/integration/tls/run.sh index c108180e1..5010830cf 100755 --- a/integration/tls/run.sh +++ b/integration/tls/run.sh @@ -3,6 +3,15 @@ set -e SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) source ${SCRIPT_DIR}/../common.sh +# Force backend Postgres to require TLS in GitHub CI so we exercise the +# client<->PgDog<->Postgres path end to end. Skipped locally because dev +# clusters aren't guaranteed to have server certs configured. +if [ "${GITHUB_ACTIONS:-}" = "true" ]; then + psql -c "ALTER SYSTEM SET ssl TO on" + PSQL_VERSION=$(psql -tAc "SELECT current_setting('server_version_num')::int / 10000") + sudo pg_ctlcluster "${PSQL_VERSION}" main restart +fi + run_pgdog integration/tls # psql requires private keys to be 0600 (git doesn't preserve this).