diff --git a/pingora-core/src/connectors/mod.rs b/pingora-core/src/connectors/mod.rs index 3e3c1c46..030dd929 100644 --- a/pingora-core/src/connectors/mod.rs +++ b/pingora-core/src/connectors/mod.rs @@ -79,6 +79,16 @@ pub struct ConnectorOptions { pub bind_to_v4: Vec, /// Bind to any of the given source IPv4 addresses pub bind_to_v6: Vec, + /// Optional custom server certificate verifier for the rustls backend. + /// + /// When set, the connector uses `dangerous().with_custom_certificate_verifier()` instead of + /// building a `RootCertStore` and using webpki for server cert validation. This is necessary + /// for certificates with custom critical extensions that webpki rejects (e.g. IEEE 2030.5). + /// + /// The verifier must handle TLS signature verification as well, since webpki is bypassed. + /// `ca_file` is ignored when this is set. + #[cfg(feature = "rustls")] + pub server_cert_verifier: Option>, } impl ConnectorOptions { @@ -119,6 +129,8 @@ impl ConnectorOptions { offload_threadpool, bind_to_v4, bind_to_v6, + #[cfg(feature = "rustls")] + server_cert_verifier: None, } } @@ -134,6 +146,8 @@ impl ConnectorOptions { offload_threadpool: None, bind_to_v4: vec![], bind_to_v6: vec![], + #[cfg(feature = "rustls")] + server_cert_verifier: None, } } } diff --git a/pingora-core/src/connectors/tls/rustls/mod.rs b/pingora-core/src/connectors/tls/rustls/mod.rs index ff375929..c07195ae 100644 --- a/pingora-core/src/connectors/tls/rustls/mod.rs +++ b/pingora-core/src/connectors/tls/rustls/mod.rs @@ -54,6 +54,8 @@ impl Connector { pub struct TlsConnector { config: Arc, ca_certs: Arc, + /// Custom server cert verifier, stored for per-connection config rebuilds in connect(). + custom_verifier: Option>, } impl TlsConnector { @@ -68,15 +70,24 @@ impl TlsConnector { // - set supported ciphers/algorithms/curves // - add options for CRL/OCSP validation + let custom_verifier = options + .as_ref() + .and_then(|o| o.server_cert_verifier.clone()); + let (ca_certs, certs_key) = { let mut ca_certs = RootCertStore::empty(); let mut certs_key = None; if let Some(conf) = options.as_ref() { - if let Some(ca_file_path) = conf.ca_file.as_ref() { - load_ca_file_into_store(ca_file_path, &mut ca_certs)?; - } else { - load_platform_certs_incl_env_into_store(&mut ca_certs)?; + // When a custom verifier is provided, skip RootCertStore loading entirely. + // The custom verifier handles CA validation without webpki, which would + // reject certificates with unrecognized critical extensions. + if custom_verifier.is_none() { + if let Some(ca_file_path) = conf.ca_file.as_ref() { + load_ca_file_into_store(ca_file_path, &mut ca_certs)?; + } else { + load_platform_certs_incl_env_into_store(&mut ca_certs)?; + } } if let Some((cert, key)) = conf.cert_key_file.as_ref() { certs_key = load_certs_and_key_files(cert, key)?; @@ -88,23 +99,53 @@ impl TlsConnector { (ca_certs, certs_key) }; - // TODO: WebPkiServerVerifier for CRL/OCSP validation - let builder = - RusTlsClientConfig::builder_with_protocol_versions(&[&version::TLS12, &version::TLS13]) - .with_root_certificates(ca_certs.clone()); - - let mut config = match certs_key { - Some((certs, key)) => { - match builder.with_client_auth_cert(certs.clone(), key.clone_key()) { - Ok(config) => config, - Err(err) => { - // TODO: is there a viable alternative to the panic? - // falling back to no client auth... does not seem to be reasonable. - panic!("Failed to configure client auth cert/key. Error: {}", err); + let mut config = if let Some(ref verifier) = custom_verifier { + // Use the custom verifier — bypasses webpki for CA and server cert validation. + let builder = RusTlsClientConfig::builder_with_protocol_versions(&[ + &version::TLS12, + &version::TLS13, + ]) + .dangerous() + .with_custom_certificate_verifier(Arc::clone(verifier)); + + match certs_key { + Some((certs, key)) => { + // Use with_client_cert_resolver() instead of with_client_auth_cert() + // to avoid webpki validation of the client certificate chain. + let provider = builder.crypto_provider().clone(); + let signing_key = provider + .key_provider + .load_private_key(key) + .or_err(InvalidCert, "Failed to load client private key")?; + let certified_key = + Arc::new(pingora_rustls::sign::CertifiedKey::new(certs, signing_key)); + builder.with_client_cert_resolver(Arc::new(SingleCertClientResolver( + certified_key, + ))) + } + None => builder.with_no_client_auth(), + } + } else { + // Default webpki path (unchanged from original behavior) + let builder = RusTlsClientConfig::builder_with_protocol_versions(&[ + &version::TLS12, + &version::TLS13, + ]) + .with_root_certificates(ca_certs.clone()); + + match certs_key { + Some((certs, key)) => { + match builder.with_client_auth_cert(certs.clone(), key.clone_key()) { + Ok(config) => config, + Err(err) => { + // TODO: is there a viable alternative to the panic? + // falling back to no client auth... does not seem to be reasonable. + panic!("Failed to configure client auth cert/key. Error: {}", err); + } } } + None => builder.with_no_client_auth(), } - None => builder.with_no_client_auth(), }; // Enable SSLKEYLOGFILE support for debugging TLS traffic @@ -118,6 +159,7 @@ impl TlsConnector { ctx: Arc::new(TlsConnector { config: Arc::new(config), ca_certs: Arc::new(ca_certs), + custom_verifier, }), }) } @@ -160,20 +202,45 @@ where let private_key: PrivateKeyDer = key_arc.key().as_slice().to_owned().try_into().unwrap(); - let builder = RusTlsClientConfig::builder_with_protocol_versions(&[ - &version::TLS12, - &version::TLS13, - ]) - .with_root_certificates(Arc::clone(&tls_ctx.ca_certs)); - debug!("added root ca certificates"); - - let mut updated_config = builder.with_client_auth_cert(certs, private_key).or_err( - InvalidCert, - "Failed to use peer cert/key to update Rustls config", - )?; - // Preserve keylog setting from original config - updated_config.key_log = Arc::clone(&config.key_log); - Some(updated_config) + if let Some(ref verifier) = tls_ctx.custom_verifier { + // Custom verifier path: bypass webpki for both server cert and client cert + let builder = RusTlsClientConfig::builder_with_protocol_versions(&[ + &version::TLS12, + &version::TLS13, + ]) + .dangerous() + .with_custom_certificate_verifier(Arc::clone(verifier)); + + let provider = builder.crypto_provider().clone(); + let signing_key = provider + .key_provider + .load_private_key(private_key) + .or_err(InvalidCert, "Failed to load peer client private key")?; + let certified_key = + Arc::new(pingora_rustls::sign::CertifiedKey::new(certs, signing_key)); + + debug!("added client cert via resolver (custom verifier)"); + let mut updated_config = builder + .with_client_cert_resolver(Arc::new(SingleCertClientResolver(certified_key))); + updated_config.key_log = Arc::clone(&config.key_log); + Some(updated_config) + } else { + // Default webpki path + let builder = RusTlsClientConfig::builder_with_protocol_versions(&[ + &version::TLS12, + &version::TLS13, + ]) + .with_root_certificates(Arc::clone(&tls_ctx.ca_certs)); + debug!("added root ca certificates"); + + let mut updated_config = builder.with_client_auth_cert(certs, private_key).or_err( + InvalidCert, + "Failed to use peer cert/key to update Rustls config", + )?; + // Preserve keylog setting from original config + updated_config.key_log = Arc::clone(&config.key_log); + Some(updated_config) + } } }; @@ -212,15 +279,23 @@ where // Builds the custom_verifier when verification_mode is set. if let Some(mode) = verification_mode { - let delegate = WebPkiServerVerifier::builder(Arc::clone(&tls_ctx.ca_certs)) - .build() - .or_err(InvalidCert, "Failed to build WebPkiServerVerifier")?; - - let custom_verifier = Arc::new(CustomServerCertVerifier::new(delegate, mode)); - - updated_config - .dangerous() - .set_certificate_verifier(custom_verifier); + if let Some(ref verifier) = tls_ctx.custom_verifier { + // Wrap the custom verifier with verification mode logic + let custom_verifier = + Arc::new(CustomServerCertVerifier::new(Arc::clone(verifier), mode)); + updated_config + .dangerous() + .set_certificate_verifier(custom_verifier); + } else { + // Default: wrap the WebPkiServerVerifier + let delegate = WebPkiServerVerifier::builder(Arc::clone(&tls_ctx.ca_certs)) + .build() + .or_err(InvalidCert, "Failed to build WebPkiServerVerifier")?; + let custom_verifier = Arc::new(CustomServerCertVerifier::new(delegate, mode)); + updated_config + .dangerous() + .set_certificate_verifier(custom_verifier); + } } } @@ -260,6 +335,26 @@ where } } +/// Client cert resolver that returns a pre-loaded CertifiedKey without webpki validation. +/// Used when `ConnectorOptions::server_cert_verifier` is set, to avoid webpki rejecting +/// certificates with unrecognized critical extensions. +#[derive(Debug)] +struct SingleCertClientResolver(Arc); + +impl pingora_rustls::ResolvesClientCert for SingleCertClientResolver { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option> { + Some(Arc::clone(&self.0)) + } + + fn has_certs(&self) -> bool { + true + } +} + #[allow(dead_code)] #[derive(Debug)] pub enum VerificationMode { @@ -272,12 +367,15 @@ pub enum VerificationMode { #[derive(Debug)] pub struct CustomServerCertVerifier { - delegate: Arc, + delegate: Arc, verification_mode: VerificationMode, } impl CustomServerCertVerifier { - pub fn new(delegate: Arc, verification_mode: VerificationMode) -> Self { + pub fn new( + delegate: Arc, + verification_mode: VerificationMode, + ) -> Self { Self { delegate, verification_mode, @@ -286,7 +384,7 @@ impl CustomServerCertVerifier { } // CustomServerCertVerifier delegates TLS signature verification and allows 3 VerificationMode: -// Full: delegates all verification to the original WebPkiServerVerifier +// Full: delegates all verification to the underlying ServerCertVerifier // SkipHostname: same as "Full" but ignores "NotValidForName" certificate errors // SkipAll: all certificate verification checks are skipped. impl RusTlsServerCertVerifier for CustomServerCertVerifier { diff --git a/pingora-core/src/listeners/tls/rustls/mod.rs b/pingora-core/src/listeners/tls/rustls/mod.rs index 0ca94d51..99567413 100644 --- a/pingora-core/src/listeners/tls/rustls/mod.rs +++ b/pingora-core/src/listeners/tls/rustls/mod.rs @@ -17,8 +17,7 @@ use std::sync::Arc; use crate::listeners::TlsAcceptCallbacks; use crate::protocols::tls::{server::handshake, server::handshake_with_callback, TlsStream}; use log::debug; -use pingora_error::ErrorType::InternalError; -use pingora_error::{Error, OrErr, Result}; +use pingora_error::Result; use pingora_rustls::load_certs_and_key_files; use pingora_rustls::ClientCertVerifier; use pingora_rustls::ServerConfig; @@ -32,6 +31,7 @@ pub struct TlsSettings { cert_path: String, key_path: String, client_cert_verifier: Option>, + callbacks: Option, } pub struct Acceptor { @@ -48,6 +48,13 @@ impl TlsSettings { /// /// Todo: Return a result instead of panicking XD pub fn build(self) -> Acceptor { + assert!( + !self.cert_path.is_empty() && !self.key_path.is_empty(), + "Certificate and key paths must be set before calling build(). \ + When using with_callbacks(), call set_certificate_chain_file() \ + and set_private_key_file() first." + ); + let Ok(Some((certs, key))) = load_certs_and_key_files(&self.cert_path, &self.key_path) else { panic!( @@ -58,17 +65,36 @@ impl TlsSettings { let builder = ServerConfig::builder_with_protocol_versions(&[&version::TLS12, &version::TLS13]); + let provider = builder.crypto_provider().clone(); let builder = if let Some(verifier) = self.client_cert_verifier { builder.with_client_cert_verifier(verifier) } else { builder.with_no_client_auth() }; - let mut config = builder - .with_single_cert(certs, key) - .explain_err(InternalError, |e| { - format!("Failed to create server listener config: {e}") - }) - .unwrap(); + + // Use CertifiedKey::new() + with_cert_resolver() instead of with_single_cert() + // to match the OpenSSL backend behavior: load cert+key without upfront validation. + // with_single_cert() runs webpki validation on the server's own cert chain, which + // rejects certificates with unrecognized critical extensions. + let signing_key = provider + .key_provider + .load_private_key(key) + .expect("Failed to load server private key"); + let certified_key = Arc::new(pingora_rustls::sign::CertifiedKey::new(certs, signing_key)); + + use pingora_rustls::ResolvesServerCert; + #[derive(Debug)] + struct SingleCert(Arc); + impl ResolvesServerCert for SingleCert { + fn resolve( + &self, + _client_hello: pingora_rustls::ClientHello<'_>, + ) -> Option> { + Some(Arc::clone(&self.0)) + } + } + + let mut config = builder.with_cert_resolver(Arc::new(SingleCert(certified_key))); if let Some(alpn_protocols) = self.alpn_protocols { config.alpn_protocols = alpn_protocols; @@ -76,7 +102,7 @@ impl TlsSettings { Acceptor { acceptor: RusTlsAcceptor::from(Arc::new(config)), - callbacks: None, + callbacks: self.callbacks, } } @@ -95,6 +121,16 @@ impl TlsSettings { self.client_cert_verifier = Some(verifier); } + /// Set the path to the certificate chain file (PEM format). + pub fn set_certificate_chain_file(&mut self, path: &str) { + self.cert_path = path.to_string(); + } + + /// Set the path to the private key file (PEM format). + pub fn set_private_key_file(&mut self, path: &str) { + self.key_path = path.to_string(); + } + pub fn intermediate(cert_path: &str, key_path: &str) -> Result where Self: Sized, @@ -104,18 +140,29 @@ impl TlsSettings { cert_path: cert_path.to_string(), key_path: key_path.to_string(), client_cert_verifier: None, + callbacks: None, }) } - pub fn with_callbacks() -> Result + /// Create a new [`TlsSettings`] with post-handshake callbacks. + /// + /// The provided callbacks will be invoked after TLS handshake completes. + /// The [`TlsRef`](crate::protocols::tls::TlsRef) passed to the callback + /// provides access to the peer certificate chain and negotiated cipher suite. + /// + /// Certificate and key files must be set separately via the builder methods + /// on the returned `TlsSettings`. + pub fn with_callbacks(callbacks: TlsAcceptCallbacks) -> Result where Self: Sized, { - // TODO: verify if/how callback in handshake can be done using Rustls - Error::e_explain( - InternalError, - "Certificate callbacks are not supported with feature \"rustls\".", - ) + Ok(TlsSettings { + alpn_protocols: None, + cert_path: String::new(), + key_path: String::new(), + client_cert_verifier: None, + callbacks: Some(callbacks), + }) } } diff --git a/pingora-core/src/protocols/tls/rustls/mod.rs b/pingora-core/src/protocols/tls/rustls/mod.rs index c7c81fc8..ba473e54 100644 --- a/pingora-core/src/protocols/tls/rustls/mod.rs +++ b/pingora-core/src/protocols/tls/rustls/mod.rs @@ -19,7 +19,39 @@ mod stream; pub use stream::*; use crate::utils::tls::WrappedX509; +use pingora_rustls::CertificateDer; pub type CaType = [WrappedX509]; -pub struct TlsRef; +/// TLS connection state exposed to post-handshake callbacks. +/// +/// Provides access to peer certificates and negotiated cipher suite +/// after a TLS handshake completes. This is the rustls equivalent of +/// the OpenSSL `SslRef` that is used as `TlsRef` in the boringssl/openssl path. +pub struct TlsRef { + /// Peer certificate chain (DER-encoded). The first entry is the leaf certificate. + peer_certs: Option>>, + /// Negotiated cipher suite name (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + cipher: Option<&'static str>, +} + +impl TlsRef { + /// Returns the peer's leaf certificate in DER encoding, if present. + pub fn peer_certificate_der(&self) -> Option<&[u8]> { + self.peer_certs + .as_ref() + .and_then(|certs| certs.first()) + .map(|cert| cert.as_ref()) + } + + /// Returns the full peer certificate chain in DER encoding. + /// The first entry is the leaf; subsequent entries are intermediates. + pub fn peer_cert_chain_der(&self) -> Option<&[CertificateDer<'static>]> { + self.peer_certs.as_deref() + } + + /// Returns the negotiated cipher suite name, if available. + pub fn current_cipher_name(&self) -> Option<&'static str> { + self.cipher + } +} diff --git a/pingora-core/src/protocols/tls/rustls/server.rs b/pingora-core/src/protocols/tls/rustls/server.rs index 4367f75a..b47cd44d 100644 --- a/pingora-core/src/protocols/tls/rustls/server.rs +++ b/pingora-core/src/protocols/tls/rustls/server.rs @@ -16,7 +16,6 @@ use crate::listeners::TlsAcceptCallbacks; use crate::protocols::tls::rustls::TlsStream; -use crate::protocols::tls::TlsRef; use crate::protocols::IO; use crate::{listeners::tls::Acceptor, protocols::Shutdown}; use async_trait::async_trait; @@ -65,7 +64,6 @@ pub async fn handshake(acceptor: &Acceptor, io: S) -> Result } /// Perform TLS handshake for the given connection with the given configuration and callbacks -/// callbacks are currently not supported within pingora Rustls and are ignored pub async fn handshake_with_callback( acceptor: &Acceptor, io: S, @@ -74,16 +72,17 @@ pub async fn handshake_with_callback( let mut tls_stream = prepare_tls_stream(acceptor, io).await?; let done = Pin::new(&mut tls_stream).start_accept().await?; if !done { - // TODO: verify if/how callback in handshake can be done using Rustls - warn!("Callacks are not supported with feature \"rustls\"."); - + // NOTE: certificate_callback is not invoked for rustls. Dynamic cert selection + // should use a custom ResolvesServerCert instead. + warn!("certificate_callback is not supported with the rustls backend; use ResolvesServerCert for dynamic cert selection"); Pin::new(&mut tls_stream) .resume_accept() .await .explain_err(TLSHandshakeFailure, |e| format!("TLS accept() failed: {e}"))?; } { - let tls_ref = TlsRef; + // Build TlsRef with connection state for the callback + let tls_ref = tls_stream.build_tls_ref(); if let Some(extension) = callbacks.handshake_complete_callback(&tls_ref).await { if let Some(digest_mut) = tls_stream.ssl_digest_mut() { digest_mut.extension.set(extension); @@ -108,8 +107,100 @@ where } } -#[ignore] -#[tokio::test] -async fn test_async_cert() { - todo!("callback support and test for Rustls") +#[cfg(test)] +mod tests { + use crate::listeners::tls::TlsSettings; + use crate::listeners::TlsAccept; + use crate::protocols::tls::TlsRef; + use async_trait::async_trait; + use pingora_rustls::{ + ClientConfig, HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, ServerName, + }; + use std::sync::Arc; + use tokio::io::{AsyncReadExt, DuplexStream}; + + #[derive(Debug)] + struct NoVerify; + + impl ServerCertVerifier for NoVerify { + fn verify_server_cert( + &self, + _: &rustls::pki_types::CertificateDer<'_>, + _: &[rustls::pki_types::CertificateDer<'_>], + _: &ServerName<'_>, + _: &[u8], + _: pingora_rustls::UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _: &[u8], + _: &rustls::pki_types::CertificateDer<'_>, + _: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _: &[u8], + _: &rustls::pki_types::CertificateDer<'_>, + _: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::aws_lc_rs::default_provider() + .signature_verification_algorithms + .supported_schemes() + } + } + + async fn client_task(client: DuplexStream) { + let config = ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoVerify)) + .with_no_client_auth(); + let connector = pingora_rustls::TlsConnector::from(Arc::new(config)); + let server_name = ServerName::try_from("openrusty.org").unwrap(); + let mut stream = connector.connect(server_name, client).await.unwrap(); + let mut buf = [0u8; 1]; + let _ = stream.read(&mut buf).await; + } + + #[tokio::test] + async fn test_handshake_complete_callback() { + struct CipherName(String); + struct Callback; + + #[async_trait] + impl TlsAccept for Callback { + async fn handshake_complete_callback( + &self, + tls: &TlsRef, + ) -> Option> { + let name = tls.current_cipher_name()?.to_string(); + Some(Arc::new(CipherName(name))) + } + } + + let cert = format!("{}/tests/keys/server.crt", env!("CARGO_MANIFEST_DIR")); + let key = format!("{}/tests/keys/key.pem", env!("CARGO_MANIFEST_DIR")); + + let mut settings = TlsSettings::with_callbacks(Box::new(Callback)).unwrap(); + settings.set_certificate_chain_file(&cert); + settings.set_private_key_file(&key); + let acceptor = settings.build(); + + let (client, server) = tokio::io::duplex(4096); + tokio::spawn(client_task(client)); + + let stream = acceptor.tls_handshake(server).await.unwrap(); + let digest = stream.ssl_digest().unwrap(); + let cipher = digest.extension.get::().unwrap(); + assert!(!cipher.0.is_empty()); + } } diff --git a/pingora-core/src/protocols/tls/rustls/stream.rs b/pingora-core/src/protocols/tls/rustls/stream.rs index f2a0ddae..680253d5 100644 --- a/pingora-core/src/protocols/tls/rustls/stream.rs +++ b/pingora-core/src/protocols/tls/rustls/stream.rs @@ -141,6 +141,32 @@ impl TlsStream { } } +impl TlsStream { + /// Build a [`TlsRef`] from the current connection state. + /// + /// Extracts peer certificates and negotiated cipher suite from the underlying + /// rustls session so they can be passed to a [`TlsAcceptCallbacks`] implementation. + pub(crate) fn build_tls_ref(&self) -> crate::protocols::tls::TlsRef { + use crate::protocols::tls::TlsRef; + + let stream = self.tls.stream.as_ref(); + match stream { + Some(s) => { + let (_io, session) = s.get_ref(); + let peer_certs = session.peer_certificates().map(|certs| certs.to_vec()); + let cipher = session + .negotiated_cipher_suite() + .and_then(|suite| suite.suite().as_str()); + TlsRef { peer_certs, cipher } + } + None => TlsRef { + peer_certs: None, + cipher: None, + }, + } + } +} + impl Deref for TlsStream { type Target = InnerStream; diff --git a/pingora-proxy/src/lib.rs b/pingora-proxy/src/lib.rs index f89f53d3..85c3ffa4 100644 --- a/pingora-proxy/src/lib.rs +++ b/pingora-proxy/src/lib.rs @@ -174,8 +174,23 @@ where SV: ProxyHttp + Send + Sync + 'static, SV::CTX: Send + Sync, { - let client_upstream = - Connector::new_custom(Some(ConnectorOptions::from_server_conf(&conf)), connector); + let opts = ConnectorOptions::from_server_conf(&conf); + Self::new_custom_with_options(inner, conf, connector, on_custom, server_options, opts) + } + + fn new_custom_with_options( + inner: SV, + conf: Arc, + connector: C, + on_custom: Option>, + server_options: Option, + connector_options: ConnectorOptions, + ) -> Self + where + SV: ProxyHttp + Send + Sync + 'static, + SV::CTX: Send + Sync, + { + let client_upstream = Connector::new_custom(Some(connector_options), connector); HttpProxy { inner, @@ -1295,6 +1310,7 @@ where connector: C, custom: Option>, server_options: Option, + connector_options: Option, } impl ProxyServiceBuilder @@ -1319,6 +1335,7 @@ where connector: (), custom: None, server_options: None, + connector_options: None, } } } @@ -1353,6 +1370,7 @@ where inner, name, server_options, + connector_options, .. } = self; ProxyServiceBuilder { @@ -1362,6 +1380,7 @@ where connector, custom: Some(on_custom), server_options, + connector_options, } } @@ -1373,6 +1392,15 @@ where self } + /// Override the default [ConnectorOptions] derived from [ServerConf]. + /// + /// This allows setting custom options such as a custom server certificate verifier + /// for the upstream TLS connector. + pub fn connector_options(mut self, options: ConnectorOptions) -> Self { + self.connector_options = Some(options); + self + } + /// Builds a new [Service] from the [ProxyServiceBuilder]. /// /// This function takes ownership of the [ProxyServiceBuilder] and returns a new [Service] with @@ -1387,9 +1415,18 @@ where connector, custom, server_options, + connector_options, } = self; - let mut proxy = HttpProxy::new_custom(inner, conf, connector, custom, server_options); + let opts = connector_options.unwrap_or_else(|| ConnectorOptions::from_server_conf(&conf)); + let mut proxy = HttpProxy::new_custom_with_options( + inner, + conf, + connector, + custom, + server_options, + opts, + ); proxy.handle_init_modules(); Service::new(name, proxy) diff --git a/pingora-rustls/src/lib.rs b/pingora-rustls/src/lib.rs index 097a8da5..bcb7047a 100644 --- a/pingora-rustls/src/lib.rs +++ b/pingora-rustls/src/lib.rs @@ -26,7 +26,9 @@ pub use no_debug::{Ellipses, NoDebug, WithTypeInfo}; use pingora_error::{Error, ErrorType, OrErr, Result}; pub use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; -pub use rustls::server::{ClientCertVerifierBuilder, WebPkiClientVerifier}; +pub use rustls::server::{ + ClientCertVerifierBuilder, ClientHello, ResolvesServerCert, WebPkiClientVerifier, +}; pub use rustls::{ client::WebPkiServerVerifier, version, CertificateError, ClientConfig, DigitallySignedStruct, Error as RusTlsError, KeyLogFile, RootCertStore, ServerConfig, SignatureScheme, Stream, @@ -38,6 +40,9 @@ pub use tokio_rustls::client::TlsStream as ClientTlsStream; pub use tokio_rustls::server::TlsStream as ServerTlsStream; pub use tokio_rustls::{Accept, Connect, TlsAcceptor, TlsConnector, TlsStream}; +pub use rustls::client::ResolvesClientCert; +pub use rustls::sign; + // This allows to skip certificate verification. Be highly cautious. pub use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};