From 7687fae471ffba25706ec8d393fabf603b8f1933 Mon Sep 17 00:00:00 2001 From: Matt Garmon Date: Fri, 22 May 2026 16:34:07 -0700 Subject: [PATCH] Make ring an optional dependency in pingora-rustls --- pingora-cache/Cargo.toml | 1 + pingora-core/Cargo.toml | 7 +++++-- pingora-core/src/connectors/http/mod.rs | 8 ++++---- pingora-core/src/connectors/tls/mod.rs | 8 ++++---- pingora-core/src/connectors/tls/rustls/mod.rs | 1 + pingora-core/src/lib.rs | 2 +- pingora-core/src/listeners/tls/mod.rs | 4 ++-- pingora-core/src/listeners/tls/rustls/mod.rs | 1 + pingora-core/src/protocols/tls/mod.rs | 6 +++--- pingora-core/src/protocols/tls/rustls/stream.rs | 7 ++++++- pingora-core/src/utils/tls/mod.rs | 4 ++-- pingora-load-balancing/Cargo.toml | 1 + pingora-proxy/Cargo.toml | 1 + pingora-rustls/Cargo.toml | 10 ++++++++-- pingora-rustls/src/lib.rs | 6 ++++++ pingora/Cargo.toml | 8 ++++++++ 16 files changed, 54 insertions(+), 21 deletions(-) diff --git a/pingora-cache/Cargo.toml b/pingora-cache/Cargo.toml index 44a063ef..79b515df 100644 --- a/pingora-cache/Cargo.toml +++ b/pingora-cache/Cargo.toml @@ -74,5 +74,6 @@ default = [] openssl = ["pingora-core/openssl"] boringssl = ["pingora-core/boringssl"] rustls = ["pingora-core/rustls"] +rustls-no-ring = ["pingora-core/rustls-no-ring"] s2n = ["pingora-core/s2n"] trace = ["dep:cf-rustracing", "dep:cf-rustracing-jaeger"] diff --git a/pingora-core/Cargo.toml b/pingora-core/Cargo.toml index 82dcb2f9..8fd5c3de 100644 --- a/pingora-core/Cargo.toml +++ b/pingora-core/Cargo.toml @@ -26,7 +26,7 @@ pingora-pool = { version = "0.8.0", path = "../pingora-pool" } pingora-error = { version = "0.8.0", path = "../pingora-error" } pingora-timeout = { version = "0.8.0", path = "../pingora-timeout" } pingora-http = { version = "0.8.0", path = "../pingora-http" } -pingora-rustls = { version = "0.8.0", path = "../pingora-rustls", optional = true } +pingora-rustls = { version = "0.8.0", path = "../pingora-rustls", optional = true, default-features = false } pingora-s2n = { version = "0.8.0", path = "../pingora-s2n", optional = true } bstr = { workspace = true } tokio = { workspace = true, features = ["net", "rt-multi-thread", "signal"] } @@ -103,7 +103,10 @@ jemallocator = "0.5" default = [] openssl = ["pingora-openssl", "openssl_derived"] boringssl = ["pingora-boringssl", "openssl_derived"] -rustls = ["pingora-rustls", "any_tls", "dep:x509-parser", "ouroboros"] +rustls = ["rustls-base", "ring"] +rustls-no-ring = ["rustls-base"] +rustls-base = ["pingora-rustls", "any_tls", "dep:x509-parser", "ouroboros"] +ring = ["pingora-rustls/ring"] s2n = ["pingora-s2n", "any_tls", "dep:x509-parser", "ouroboros", "lru"] patched_http1 = ["pingora-http/patched_http1"] openssl_derived = ["any_tls"] diff --git a/pingora-core/src/connectors/http/mod.rs b/pingora-core/src/connectors/http/mod.rs index 5a671ef2..994df00e 100644 --- a/pingora-core/src/connectors/http/mod.rs +++ b/pingora-core/src/connectors/http/mod.rs @@ -369,12 +369,12 @@ mod tests { // Creates a test connector for integration/unit tests. // For rustls, only ConnectorOptions are used here; the actual dangerous verifier is patched in the TLS connector. fn create_test_connector() -> Connector { - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] let custom_transport = { let options = ConnectorOptions::new(1); TransportConnector::new(Some(options)) }; - #[cfg(not(feature = "rustls"))] + #[cfg(not(feature = "rustls-base"))] let custom_transport = TransportConnector::new(None); Connector { h1: v1::Connector::new(None), @@ -490,7 +490,7 @@ mod tests { // Both client and server are using custom protocols, but different ones - we should create H1 sessions as fallback. // For RusTLS if there is no agreed protocol, the handshake directly fails, so this won't work // TODO: If no ALPN is matched, rustls should return None instead of failing the handshake. - #[cfg(not(feature = "rustls"))] + #[cfg(not(feature = "rustls-base"))] #[tokio::test] async fn test_incompatible_custom_client_custom_upstream() { let port = get_available_port().await; @@ -569,7 +569,7 @@ mod tests { } // Used for disabling certificate/hostname verification in rustls for tests and custom ALPN/self-signed scenarios. -#[cfg(all(test, feature = "rustls"))] +#[cfg(all(test, feature = "rustls-base"))] pub mod rustls_no_verify { use rustls::client::danger::{ServerCertVerified, ServerCertVerifier}; use rustls::pki_types::{CertificateDer, ServerName}; diff --git a/pingora-core/src/connectors/tls/mod.rs b/pingora-core/src/connectors/tls/mod.rs index 5d75789b..c8129fd9 100644 --- a/pingora-core/src/connectors/tls/mod.rs +++ b/pingora-core/src/connectors/tls/mod.rs @@ -24,10 +24,10 @@ mod s2n; #[cfg(feature = "s2n")] pub use s2n::*; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] mod rustls; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] pub use rustls::*; /// OpenSSL considers underscores in hostnames non-compliant. @@ -41,7 +41,7 @@ pub use rustls::*; /// > characters only letters, digits, and hyphen. There are also some /// > restrictions on the length. Labels must be 63 characters or less. /// - https://datatracker.ietf.org/doc/html/rfc1034#section-3.5 -#[cfg(any(feature = "openssl_derived", feature = "rustls"))] +#[cfg(any(feature = "openssl_derived", feature = "rustls-base"))] pub fn replace_leftmost_underscore(sni: &str) -> Option { // wildcard is only leftmost label if let Some((leftmost, rest)) = sni.split_once('.') { @@ -56,7 +56,7 @@ pub fn replace_leftmost_underscore(sni: &str) -> Option { None } -#[cfg(any(feature = "openssl_derived", feature = "rustls"))] +#[cfg(any(feature = "openssl_derived", feature = "rustls-base"))] #[cfg(test)] mod tests { use super::*; diff --git a/pingora-core/src/connectors/tls/rustls/mod.rs b/pingora-core/src/connectors/tls/rustls/mod.rs index 23e3a307..4ab15b93 100644 --- a/pingora-core/src/connectors/tls/rustls/mod.rs +++ b/pingora-core/src/connectors/tls/rustls/mod.rs @@ -62,6 +62,7 @@ impl TlsConnector { Self: Sized, { // rustls 0.23+ requires an explicit CryptoProvider. + #[cfg(feature = "ring")] pingora_rustls::install_default_crypto_provider(); // NOTE: Rustls only supports TLS 1.2 & 1.3 diff --git a/pingora-core/src/lib.rs b/pingora-core/src/lib.rs index 1e1b5d56..18ce0f8d 100644 --- a/pingora-core/src/lib.rs +++ b/pingora-core/src/lib.rs @@ -112,7 +112,7 @@ pub use pingora_boringssl as tls; #[cfg(feature = "openssl")] pub use pingora_openssl as tls; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] pub use pingora_rustls as tls; #[cfg(feature = "s2n")] diff --git a/pingora-core/src/listeners/tls/mod.rs b/pingora-core/src/listeners/tls/mod.rs index c345073e..87ded410 100644 --- a/pingora-core/src/listeners/tls/mod.rs +++ b/pingora-core/src/listeners/tls/mod.rs @@ -18,10 +18,10 @@ mod boringssl_openssl; #[cfg(feature = "openssl_derived")] pub use boringssl_openssl::*; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] mod rustls; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] pub use rustls::*; #[cfg(feature = "s2n")] diff --git a/pingora-core/src/listeners/tls/rustls/mod.rs b/pingora-core/src/listeners/tls/rustls/mod.rs index e7376fc0..65e9e6ca 100644 --- a/pingora-core/src/listeners/tls/rustls/mod.rs +++ b/pingora-core/src/listeners/tls/rustls/mod.rs @@ -49,6 +49,7 @@ impl TlsSettings { /// Todo: Return a result instead of panicking XD pub fn build(self) -> Acceptor { // rustls 0.23+ requires an explicit CryptoProvider. + #[cfg(feature = "ring")] pingora_rustls::install_default_crypto_provider(); let Ok(Some((certs, key))) = load_certs_and_key_files(&self.cert_path, &self.key_path) diff --git a/pingora-core/src/protocols/tls/mod.rs b/pingora-core/src/protocols/tls/mod.rs index 9afdb9df..78da311d 100644 --- a/pingora-core/src/protocols/tls/mod.rs +++ b/pingora-core/src/protocols/tls/mod.rs @@ -23,10 +23,10 @@ mod boringssl_openssl; #[cfg(feature = "openssl_derived")] pub use boringssl_openssl::*; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] mod rustls; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] pub use rustls::*; #[cfg(feature = "s2n")] @@ -174,7 +174,7 @@ impl ALPN { } } - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] pub(crate) fn to_wire_protocols(&self) -> Vec> { match self { ALPN::H1 => vec![b"http/1.1".to_vec()], diff --git a/pingora-core/src/protocols/tls/rustls/stream.rs b/pingora-core/src/protocols/tls/rustls/stream.rs index f2a0ddae..3b16e15c 100644 --- a/pingora-core/src/protocols/tls/rustls/stream.rs +++ b/pingora-core/src/protocols/tls/rustls/stream.rs @@ -28,8 +28,10 @@ use crate::protocols::{ use crate::utils::tls::get_organization_serial_bytes; use pingora_error::ErrorType::{AcceptError, ConnectError, InternalError, TLSHandshakeFailure}; use pingora_error::{OkOrErr, OrErr, Result}; +#[cfg(feature = "ring")] +use pingora_rustls::hash_certificate; +use pingora_rustls::NoDebug; use pingora_rustls::TlsStream as RusTlsStream; -use pingora_rustls::{hash_certificate, NoDebug}; use pingora_rustls::{Accept, Connect, ServerName, TlsConnector}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use x509_parser::nom::AsBytes; @@ -376,10 +378,13 @@ impl SslDigest { .and_then(|proto| proto.as_str()) .unwrap_or_default(); + #[cfg(feature = "ring")] let cert_digest = peer_certificates .and_then(|certs| certs.first()) .map(|cert| hash_certificate(cert)) .unwrap_or_default(); + #[cfg(not(feature = "ring"))] + let cert_digest = Vec::new(); let (organization, serial_number) = peer_certificates .and_then(|certs| certs.first()) diff --git a/pingora-core/src/utils/tls/mod.rs b/pingora-core/src/utils/tls/mod.rs index c345073e..87ded410 100644 --- a/pingora-core/src/utils/tls/mod.rs +++ b/pingora-core/src/utils/tls/mod.rs @@ -18,10 +18,10 @@ mod boringssl_openssl; #[cfg(feature = "openssl_derived")] pub use boringssl_openssl::*; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] mod rustls; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] pub use rustls::*; #[cfg(feature = "s2n")] diff --git a/pingora-load-balancing/Cargo.toml b/pingora-load-balancing/Cargo.toml index d6f5d41e..bad927ae 100644 --- a/pingora-load-balancing/Cargo.toml +++ b/pingora-load-balancing/Cargo.toml @@ -39,6 +39,7 @@ default = [] openssl = ["pingora-core/openssl", "openssl_derived"] boringssl = ["pingora-core/boringssl", "openssl_derived"] rustls = ["pingora-core/rustls", "any_tls"] +rustls-no-ring = ["pingora-core/rustls-no-ring", "any_tls"] s2n = ["pingora-core/s2n", "any_tls"] openssl_derived = ["any_tls"] any_tls = [] diff --git a/pingora-proxy/Cargo.toml b/pingora-proxy/Cargo.toml index e1cc1cbb..109cfbae 100644 --- a/pingora-proxy/Cargo.toml +++ b/pingora-proxy/Cargo.toml @@ -69,6 +69,7 @@ boringssl = [ "openssl_derived", ] rustls = ["pingora-core/rustls", "pingora-cache/rustls", "any_tls"] +rustls-no-ring = ["pingora-core/rustls-no-ring", "pingora-cache/rustls-no-ring", "any_tls"] s2n = ["pingora-core/s2n", "pingora-cache/s2n", "any_tls"] openssl_derived = ["any_tls"] any_tls = [] diff --git a/pingora-rustls/Cargo.toml b/pingora-rustls/Cargo.toml index 51cd00ff..3c843501 100644 --- a/pingora-rustls/Cargo.toml +++ b/pingora-rustls/Cargo.toml @@ -14,11 +14,17 @@ RusTLS async APIs for Pingora. name = "pingora_rustls" path = "src/lib.rs" +[features] +default = ["ring"] +# Use ring as the rustls CryptoProvider. Disable this feature when supplying +# your own provider (e.g. aws-lc-rs/fips, CNG, corecrypto). +ring = ["dep:ring", "rustls/ring"] + [dependencies] log = "0.4.21" pingora-error = { version = "0.8.0", path = "../pingora-error"} -ring = "0.17.12" -rustls = { version = "0.23.12", features = ["ring"] } +ring = { version = "0.17.12", optional = true } +rustls = { version = "0.23.12", default-features = false, features = ["logging", "std", "tls12"] } rustls-native-certs = "0.7.1" rustls-pemfile = "2.1.2" rustls-pki-types = "1.7.0" diff --git a/pingora-rustls/src/lib.rs b/pingora-rustls/src/lib.rs index deb0c88b..745effbf 100644 --- a/pingora-rustls/src/lib.rs +++ b/pingora-rustls/src/lib.rs @@ -38,6 +38,11 @@ pub use rustls::{ /// rustls 0.23+ requires an explicit provider. This function installs `ring` /// as the process-level default. Safe to call multiple times — subsequent /// calls are no-ops. +/// +/// Only available when the `ring` feature is enabled. When using a different +/// CryptoProvider (e.g. `aws-lc-rs`, CNG, corecrypto), install it yourself +/// before creating any TLS configuration. +#[cfg(feature = "ring")] pub fn install_default_crypto_provider() { let _ = CryptoProvider::install_default(rustls::crypto::ring::default_provider()); } @@ -182,6 +187,7 @@ pub fn load_pem_file_private_key(path: &String) -> Result> { .unwrap_or_default()) } +#[cfg(feature = "ring")] pub fn hash_certificate(cert: &CertificateDer) -> Vec { let hash = ring::digest::digest(&ring::digest::SHA256, cert.as_ref()); hash.as_ref().to_vec() diff --git a/pingora/Cargo.toml b/pingora/Cargo.toml index 8e30130a..bd602f1b 100644 --- a/pingora/Cargo.toml +++ b/pingora/Cargo.toml @@ -106,6 +106,14 @@ rustls = [ "pingora-load-balancing?/rustls", "any_tls", ] +## Use rustls without ring — bring your own CryptoProvider +rustls-no-ring = [ + "pingora-core/rustls-no-ring", + "pingora-proxy?/rustls-no-ring", + "pingora-cache?/rustls-no-ring", + "pingora-load-balancing?/rustls-no-ring", + "any_tls", +] #! ### Pingora extensions