From 9525aad6feceaeb49bdaed8bc1f7aa3ee3a98caa Mon Sep 17 00:00:00 2001 From: udev Date: Tue, 31 Mar 2026 11:28:53 +0530 Subject: [PATCH] fix: grpc use native/webpki trust store (#11083) --- Cargo.lock | 1 + Cargo.toml | 2 +- src/config/src/config.rs | 42 ++++++++++++++++++++++++++++- src/infra/src/client/grpc.rs | 19 +++++++++---- src/service/tls/mod.rs | 52 +++++++++++++++++++----------------- 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d551d24437..55294297c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11169,6 +11169,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", + "rustls-native-certs 0.8.3", "socket2 0.6.2", "sync_wrapper", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 3939069a36..5ed18bfd6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -446,7 +446,7 @@ tokio-rustls = { version = "0.26", default-features = false, features = [ ] } tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = "0.1" -tonic = { version = "0.14", features = ["gzip", "tls-webpki-roots"] } +tonic = { version = "0.14", features = ["gzip", "tls-webpki-roots", "tls-native-roots"] } tonic-prost = "0.14" tracing = "0.1.44" tracing-appender = "0.2.4" diff --git a/src/config/src/config.rs b/src/config/src/config.rs index 896e791737..6b4e4ce798 100644 --- a/src/config/src/config.rs +++ b/src/config/src/config.rs @@ -715,10 +715,11 @@ pub struct Http { pub tls_min_version: String, #[env_config( name = "ZO_HTTP_TLS_ROOT_CERTIFICATES", + parse, default = "webpki", help = "this value must use webpki or native. it means use standard root certificates from webpki-roots or native-roots as a rustls certificate store" )] - pub tls_root_certificates: String, + pub tls_root_certificates: TlsRootCertificates, #[env_config( name = "ZO_HTTP_ACCESS_LOG_FORMAT", default = "", @@ -757,6 +758,45 @@ pub struct Grpc { pub tls_cert_path: String, #[env_config(name = "ZO_GRPC_TLS_KEY_PATH", default = "")] pub tls_key_path: String, + #[env_config( + name = "ZO_GRPC_TLS_ROOT_CERTIFICATES", + parse, + default = "webpki", + help = "this value can be set to webpki or native. Using webpki means client will trust a preset CA bundle. Using native means client will trust the certificates in OS trust store" + )] + pub tls_root_certificates: TlsRootCertificates, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum TlsRootCertificates { + #[default] + Webpki, + Native, +} + +impl std::fmt::Display for TlsRootCertificates { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Webpki => write!(f, "webpki"), + Self::Native => write!(f, "native"), + } + } +} + +impl std::str::FromStr for TlsRootCertificates { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "webpki" => Ok(Self::Webpki), + "native" => Ok(Self::Native), + _ => Err(anyhow::anyhow!( + "Invalid tls_root_certificates value: '{}'. Must be 'webpki' or 'native'", + s + )), + } + } } #[derive(Serialize, PartialEq, Default)] diff --git a/src/infra/src/client/grpc.rs b/src/infra/src/client/grpc.rs index 66a9c107b2..5afea31f4d 100644 --- a/src/infra/src/client/grpc.rs +++ b/src/infra/src/client/grpc.rs @@ -77,11 +77,20 @@ pub async fn create_channel(grpc_addr: &str) -> Result { Status::internal("parse gRPC node error".to_string()) })?; if cfg.grpc.tls_enabled { - let pem = std::fs::read_to_string(&cfg.grpc.tls_cert_path)?; - let cert = Certificate::from_pem(pem); - let tls = ClientTlsConfig::new() - .ca_certificate(cert) - .domain_name(&cfg.grpc.tls_cert_domain); + let mut tls = ClientTlsConfig::new().domain_name(&cfg.grpc.tls_cert_domain); + match cfg.grpc.tls_root_certificates { + config::TlsRootCertificates::Native => { + tls = tls.with_native_roots(); + } + config::TlsRootCertificates::Webpki => { + tls = tls.with_webpki_roots(); + if !cfg.grpc.tls_cert_path.is_empty() { + let pem = std::fs::read_to_string(&cfg.grpc.tls_cert_path)?; + let cert = Certificate::from_pem(pem); + tls = tls.ca_certificates([cert]); + } + } + } channel = channel.tls_config(tls).map_err(|err| { log::error!("gRPC node: {}, tls err: {:?}", &grpc_addr, err); Status::internal("tls gRPC node error".to_string()) diff --git a/src/service/tls/mod.rs b/src/service/tls/mod.rs index 93c25e2661..487362fb21 100644 --- a/src/service/tls/mod.rs +++ b/src/service/tls/mod.rs @@ -62,32 +62,36 @@ pub fn http_tls_config() -> Result { pub fn client_tls_config() -> Result { let cfg = config::get_config(); - let cert_store = if cfg.http.tls_root_certificates.as_str().to_lowercase() == "native" { - // Load native system certificates - let mut cert_store = rustls::RootCertStore::empty(); - let certs = rustls_native_certs::load_native_certs(); - for cert in certs.certs { - cert_store.add(cert)?; + let cert_store = match cfg.http.tls_root_certificates { + config::TlsRootCertificates::Native => { + let mut cert_store = rustls::RootCertStore::empty(); + let native_certs = rustls_native_certs::load_native_certs(); + for cert in native_certs.certs { + cert_store.add(cert)?; + } + if let Some(err) = native_certs.errors.first() { + log::warn!("Failed to load some native certificates: {}", err); + } + cert_store } - if let Some(err) = certs.errors.first() { - log::warn!("Failed to load some native certificates: {}", err); + config::TlsRootCertificates::Webpki => { + let mut cert_store = rustls::RootCertStore::empty(); + cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + if !cfg.http.tls_cert_path.is_empty() { + let cert_file = &mut BufReader::new( + std::fs::File::open(&cfg.http.tls_cert_path).map_err(|e| { + anyhow::anyhow!( + "Failed to open TLS certificate file {}: {}", + &cfg.http.tls_cert_path, + e + ) + })?, + ); + let cert_chain = certs(cert_file); + cert_store.add_parsable_certificates(cert_chain.try_collect::<_, Vec<_>, _>()?); + } + cert_store } - cert_store - } else { - // default use webpki, and add custom ca certificates - let mut cert_store = rustls::RootCertStore::empty(); - cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let cert_file = - &mut BufReader::new(std::fs::File::open(&cfg.http.tls_cert_path).map_err(|e| { - anyhow::anyhow!( - "Failed to open TLS certificate file {}: {}", - &cfg.http.tls_cert_path, - e - ) - })?); - let cert_chain = certs(cert_file); - cert_store.add_parsable_certificates(cert_chain.try_collect::<_, Vec<_>, _>()?); - cert_store }; let mut config = rustls::ClientConfig::builder()