From d72b8cb88e2c1ca48d0eef88d6284aef998da8b1 Mon Sep 17 00:00:00 2001 From: houseme Date: Wed, 13 May 2026 12:16:39 +0800 Subject: [PATCH] fix(server): fail fast when configured TLS parsing fails (#2939) --- rustfs/src/main.rs | 2 +- rustfs/src/server/http.rs | 17 +++++++--- rustfs/src/server/tls_material.rs | 54 +++++++++++-------------------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index c4c64efd6..86a39b676 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -225,7 +225,7 @@ async fn async_main() -> Result<()> { // Initialize TLS outbound material (root CAs, mTLS identity) if configured. // Server-side TLS acceptor is built separately inside start_http_server() // using the same TlsMaterialSnapshot loading logic. - if let Some(tls_path) = &config.tls_path { + if let Some(tls_path) = config.tls_path.as_deref().map(str::trim).filter(|path| !path.is_empty()) { match rustfs::server::tls_material::TlsMaterialSnapshot::load(tls_path).await { Ok(snapshot) => { snapshot.apply_outbound().await; diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs index ef046f40f..01c6bd760 100644 --- a/rustfs/src/server/http.rs +++ b/rustfs/src/server/http.rs @@ -215,17 +215,24 @@ pub async fn start_http_server( TcpListener::from_std(socket.into())? }; - let tls_path = config.tls_path.as_deref().unwrap_or_default(); + let tls_path = config.tls_path.as_deref().map(str::trim).unwrap_or_default(); + let tls_path_configured = !tls_path.is_empty(); // Load TLS materials and build server acceptor. // Note: outbound material (root CAs, mTLS identity) is already applied in main.rs. let tls_snapshot = TlsMaterialSnapshot::load(tls_path) .await .map_err(|e| Error::other(e.to_string()))?; - let tls_acceptor = tls_snapshot - .build_tls_acceptor(tls_path) - .await - .map_err(|e| Error::other(e.to_string()))?; + let tls_acceptor = tls_snapshot.build_tls_acceptor(tls_path).await.map_err(|e| { + if tls_path_configured { + Error::other(format!( + "TLS is explicitly configured via RUSTFS_TLS_PATH/tls_path='{}' but TLS acceptor initialization failed: {}", + tls_path, e + )) + } else { + Error::other(e.to_string()) + } + })?; let tls_enabled = tls_acceptor.is_some(); let protocol = if tls_enabled { "https" } else { "http" }; diff --git a/rustfs/src/server/tls_material.rs b/rustfs/src/server/tls_material.rs index 29048a08b..0053705ac 100644 --- a/rustfs/src/server/tls_material.rs +++ b/rustfs/src/server/tls_material.rs @@ -66,8 +66,6 @@ pub struct OutboundTlsMaterial { pub struct TlsMaterialSnapshot { /// Material for outbound client connections. pub outbound: OutboundTlsMaterial, - /// Whether any server certificates were found. - pub has_server_certs: bool, } impl TlsMaterialSnapshot { @@ -86,13 +84,7 @@ impl TlsMaterialSnapshot { // Load outbound material (root CAs + mTLS identity) let outbound = load_outbound_material(&tls_dir).await?; - // Check if server certs exist (actual loading happens in build_tls_acceptor) - let has_server_certs = has_server_certificates(tls_path).await; - - Ok(Self { - outbound, - has_server_certs, - }) + Ok(Self { outbound }) } /// Apply outbound material to global state (root CAs, mTLS identity). @@ -110,7 +102,7 @@ impl TlsMaterialSnapshot { /// handling both multi-cert (SNI resolver) and single-cert fallback. /// Returns `None` if no TLS certificates are available. pub(crate) async fn build_tls_acceptor(&self, tls_path: &str) -> Result>, TlsMaterialError> { - if tls_path.is_empty() || !self.has_server_certs { + if tls_path.is_empty() { return Ok(None); } @@ -122,21 +114,25 @@ impl TlsMaterialSnapshot { .map_err(|e| TlsMaterialError::Io(format!("build mTLS verifier: {e}")))?; // Try multi-cert (SNI) first - match rustfs_utils::load_all_certs_from_directory( + let multi_cert_error = match rustfs_utils::load_all_certs_from_directory( rustfs_utils::CertDirectoryLoadOptions::builder(tls_path, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY).build(), ) { - Ok(cert_key_pairs) if !cert_key_pairs.is_empty() => match rustfs_utils::create_multi_cert_resolver(cert_key_pairs) { + Ok(cert_key_pairs) => match rustfs_utils::create_multi_cert_resolver(cert_key_pairs) { Ok(resolver) => { let config = build_server_config(ServerCertSource::Resolver(Arc::new(resolver)), mtls_verifier)?; info!("Created TLS acceptor with SNI resolver"); let acceptor = Arc::new(TlsAcceptor::from(Arc::new(config))); return Ok(Some(Arc::new(TlsAcceptorHolder::new(acceptor)))); } - Err(e) => warn!("Failed to build multi-cert resolver: {}, falling back to single-cert", e), + Err(e) => { + return Err(TlsMaterialError::Parse(format!("failed to build multi-cert resolver: {e}"))); + } }, - Ok(_) => debug!("No valid multi-cert directory structure found"), - Err(_) => debug!("load_all_certs_from_directory failed, trying single-cert fallback"), - } + Err(e) => { + debug!("load_all_certs_from_directory failed, trying single-cert fallback"); + Some(e.to_string()) + } + }; // Fallback: single cert let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}"); @@ -151,6 +147,13 @@ impl TlsMaterialSnapshot { return Ok(Some(Arc::new(TlsAcceptorHolder::new(acceptor)))); } + if let Some(err) = multi_cert_error { + return Err(TlsMaterialError::Io(format!( + "failed to discover TLS certificates under '{}': {}", + tls_path, err + ))); + } + debug!("No valid TLS certificates found, starting with HTTP"); Ok(None) } @@ -161,7 +164,6 @@ impl TlsMaterialSnapshot { root_ca_pem: Vec::new(), mtls_identity: None, }, - has_server_certs: false, } } } @@ -276,24 +278,6 @@ async fn load_outbound_material(tls_dir: &Path) -> Result bool { - if tokio::fs::metadata(tls_path).await.is_err() { - return false; - } - // Check for multi-cert directory structure OR single cert files - if rustfs_utils::load_all_certs_from_directory( - rustfs_utils::CertDirectoryLoadOptions::builder(tls_path, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY).build(), - ) - .is_ok_and(|p| !p.is_empty()) - { - return true; - } - let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}"); - let cert_path = format!("{tls_path}/{RUSTFS_TLS_CERT}"); - tokio::try_join!(tokio::fs::metadata(&key_path), tokio::fs::metadata(&cert_path)).is_ok() -} - /// Load mTLS client identity from the TLS directory. async fn load_mtls_identity(tls_dir: &Path) -> Result, TlsMaterialError> { let client_cert_path = match get_env_opt_str(ENV_MTLS_CLIENT_CERT) {