fix(server): fail fast when configured TLS parsing fails (#2939)

This commit is contained in:
houseme
2026-05-13 12:16:39 +08:00
committed by GitHub
parent 17eff5a97b
commit d72b8cb88e
3 changed files with 32 additions and 41 deletions
+1 -1
View File
@@ -225,7 +225,7 @@ async fn async_main() -> Result<()> {
// Initialize TLS outbound material (root CAs, mTLS identity) if configured. // Initialize TLS outbound material (root CAs, mTLS identity) if configured.
// Server-side TLS acceptor is built separately inside start_http_server() // Server-side TLS acceptor is built separately inside start_http_server()
// using the same TlsMaterialSnapshot loading logic. // 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 { match rustfs::server::tls_material::TlsMaterialSnapshot::load(tls_path).await {
Ok(snapshot) => { Ok(snapshot) => {
snapshot.apply_outbound().await; snapshot.apply_outbound().await;
+12 -5
View File
@@ -215,17 +215,24 @@ pub async fn start_http_server(
TcpListener::from_std(socket.into())? 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. // Load TLS materials and build server acceptor.
// Note: outbound material (root CAs, mTLS identity) is already applied in main.rs. // Note: outbound material (root CAs, mTLS identity) is already applied in main.rs.
let tls_snapshot = TlsMaterialSnapshot::load(tls_path) let tls_snapshot = TlsMaterialSnapshot::load(tls_path)
.await .await
.map_err(|e| Error::other(e.to_string()))?; .map_err(|e| Error::other(e.to_string()))?;
let tls_acceptor = tls_snapshot let tls_acceptor = tls_snapshot.build_tls_acceptor(tls_path).await.map_err(|e| {
.build_tls_acceptor(tls_path) if tls_path_configured {
.await Error::other(format!(
.map_err(|e| Error::other(e.to_string()))?; "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 tls_enabled = tls_acceptor.is_some();
let protocol = if tls_enabled { "https" } else { "http" }; let protocol = if tls_enabled { "https" } else { "http" };
+19 -35
View File
@@ -66,8 +66,6 @@ pub struct OutboundTlsMaterial {
pub struct TlsMaterialSnapshot { pub struct TlsMaterialSnapshot {
/// Material for outbound client connections. /// Material for outbound client connections.
pub outbound: OutboundTlsMaterial, pub outbound: OutboundTlsMaterial,
/// Whether any server certificates were found.
pub has_server_certs: bool,
} }
impl TlsMaterialSnapshot { impl TlsMaterialSnapshot {
@@ -86,13 +84,7 @@ impl TlsMaterialSnapshot {
// Load outbound material (root CAs + mTLS identity) // Load outbound material (root CAs + mTLS identity)
let outbound = load_outbound_material(&tls_dir).await?; let outbound = load_outbound_material(&tls_dir).await?;
// Check if server certs exist (actual loading happens in build_tls_acceptor) Ok(Self { outbound })
let has_server_certs = has_server_certificates(tls_path).await;
Ok(Self {
outbound,
has_server_certs,
})
} }
/// Apply outbound material to global state (root CAs, mTLS identity). /// 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. /// handling both multi-cert (SNI resolver) and single-cert fallback.
/// Returns `None` if no TLS certificates are available. /// Returns `None` if no TLS certificates are available.
pub(crate) async fn build_tls_acceptor(&self, tls_path: &str) -> Result<Option<Arc<TlsAcceptorHolder>>, TlsMaterialError> { pub(crate) async fn build_tls_acceptor(&self, tls_path: &str) -> Result<Option<Arc<TlsAcceptorHolder>>, TlsMaterialError> {
if tls_path.is_empty() || !self.has_server_certs { if tls_path.is_empty() {
return Ok(None); return Ok(None);
} }
@@ -122,21 +114,25 @@ impl TlsMaterialSnapshot {
.map_err(|e| TlsMaterialError::Io(format!("build mTLS verifier: {e}")))?; .map_err(|e| TlsMaterialError::Io(format!("build mTLS verifier: {e}")))?;
// Try multi-cert (SNI) first // 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(), 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) => { Ok(resolver) => {
let config = build_server_config(ServerCertSource::Resolver(Arc::new(resolver)), mtls_verifier)?; let config = build_server_config(ServerCertSource::Resolver(Arc::new(resolver)), mtls_verifier)?;
info!("Created TLS acceptor with SNI resolver"); info!("Created TLS acceptor with SNI resolver");
let acceptor = Arc::new(TlsAcceptor::from(Arc::new(config))); let acceptor = Arc::new(TlsAcceptor::from(Arc::new(config)));
return Ok(Some(Arc::new(TlsAcceptorHolder::new(acceptor)))); 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(e) => {
Err(_) => debug!("load_all_certs_from_directory failed, trying single-cert fallback"), debug!("load_all_certs_from_directory failed, trying single-cert fallback");
} Some(e.to_string())
}
};
// Fallback: single cert // Fallback: single cert
let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}"); let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}");
@@ -151,6 +147,13 @@ impl TlsMaterialSnapshot {
return Ok(Some(Arc::new(TlsAcceptorHolder::new(acceptor)))); 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"); debug!("No valid TLS certificates found, starting with HTTP");
Ok(None) Ok(None)
} }
@@ -161,7 +164,6 @@ impl TlsMaterialSnapshot {
root_ca_pem: Vec::new(), root_ca_pem: Vec::new(),
mtls_identity: None, mtls_identity: None,
}, },
has_server_certs: false,
} }
} }
} }
@@ -276,24 +278,6 @@ async fn load_outbound_material(tls_dir: &Path) -> Result<OutboundTlsMaterial, T
}) })
} }
/// Quick check whether server certificate files exist in the TLS directory.
async fn has_server_certificates(tls_path: &str) -> 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. /// Load mTLS client identity from the TLS directory.
async fn load_mtls_identity(tls_dir: &Path) -> Result<Option<MtlsIdentityPem>, TlsMaterialError> { async fn load_mtls_identity(tls_dir: &Path) -> Result<Option<MtlsIdentityPem>, TlsMaterialError> {
let client_cert_path = match get_env_opt_str(ENV_MTLS_CLIENT_CERT) { let client_cert_path = match get_env_opt_str(ENV_MTLS_CLIENT_CERT) {