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.
// 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;
+12 -5
View File
@@ -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" };
+19 -35
View File
@@ -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<Option<Arc<TlsAcceptorHolder>>, 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<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.
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) {