fix(ecstore): avoid warm tier init panics (#2144)

This commit is contained in:
安正超
2026-03-12 13:52:49 +08:00
committed by GitHub
parent e2f741d41f
commit aa88b1976a
2 changed files with 99 additions and 14 deletions

View File

@@ -155,6 +155,47 @@ fn load_root_store_from_tls_path() -> Option<rustls::RootCertStore> {
Some(store)
}
fn panic_payload_to_message(payload: Box<dyn std::any::Any + Send>) -> String {
if let Some(message) = payload.downcast_ref::<String>() {
return message.clone();
}
if let Some(message) = payload.downcast_ref::<&'static str>() {
return (*message).to_string();
}
"unknown panic payload".to_string()
}
fn with_rustls_init_guard<T, F>(build: F) -> Result<T, std::io::Error>
where
F: FnOnce() -> Result<T, std::io::Error>,
{
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(build)) {
Ok(result) => result,
Err(payload) => {
let panic_message = panic_payload_to_message(payload);
Err(std::io::Error::other(format!(
"failed to initialize rustls crypto provider: {panic_message}. Ensure exactly one rustls crypto provider feature is enabled (aws-lc-rs or ring), or install one with CryptoProvider::install_default()"
)))
}
}
}
fn build_tls_config() -> Result<rustls::ClientConfig, std::io::Error> {
with_rustls_init_guard(|| {
let config = if let Some(store) = load_root_store_from_tls_path() {
rustls::ClientConfig::builder()
.with_root_certificates(store)
.with_no_client_auth()
} else {
rustls::ClientConfig::builder().with_native_roots()?.with_no_client_auth()
};
Ok(config)
})
}
impl TransitionClient {
pub async fn new(endpoint: &str, opts: Options, tier_type: &str) -> Result<TransitionClient, std::io::Error> {
let clnt = Self::private_new(endpoint, opts, tier_type).await?;
@@ -165,15 +206,8 @@ impl TransitionClient {
async fn private_new(endpoint: &str, opts: Options, tier_type: &str) -> Result<TransitionClient, std::io::Error> {
let endpoint_url = get_endpoint_url(endpoint, opts.secure)?;
let scheme = endpoint_url.scheme();
let client;
let tls = if let Some(store) = load_root_store_from_tls_path() {
rustls::ClientConfig::builder()
.with_root_certificates(store)
.with_no_client_auth()
} else {
rustls::ClientConfig::builder().with_native_roots()?.with_no_client_auth()
};
let tls = build_tls_config()?;
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(tls)
@@ -1278,3 +1312,24 @@ pub struct CreateBucketConfiguration {
#[serde(rename = "LocationConstraint")]
pub location_constraint: String,
}
#[cfg(test)]
mod tests {
use super::{build_tls_config, with_rustls_init_guard};
#[test]
fn rustls_guard_converts_panics_to_io_errors() {
let err = with_rustls_init_guard(|| -> Result<(), std::io::Error> { panic!("missing provider") })
.expect_err("panic should be converted into an io::Error");
assert!(
err.to_string().contains("missing provider"),
"expected panic message to be preserved, got: {err}"
);
}
#[test]
fn build_tls_config_returns_result_without_panicking() {
let outcome = std::panic::catch_unwind(build_tls_config);
assert!(outcome.is_ok(), "TLS config creation should not panic");
}
}

View File

@@ -72,12 +72,10 @@ impl WarmBackendRustFS {
};
let scheme = u.scheme();
let default_port = if scheme == "https" { 443 } else { 80 };
let client = TransitionClient::new(
&format!("{}:{}", u.host_str().expect("err"), u.port().unwrap_or(default_port)),
opts,
"rustfs",
)
.await?;
let host = u
.host_str()
.ok_or_else(|| std::io::Error::other("endpoint URL must include a host"))?;
let client = TransitionClient::new(&format!("{host}:{}", u.port().unwrap_or(default_port)), opts, "rustfs").await?;
let client = Arc::new(client);
let core = TransitionCore(Arc::clone(&client));
@@ -158,3 +156,35 @@ fn optimal_part_size(object_size: i64) -> Result<i64, std::io::Error> {
}
Ok(part_size)
}
#[cfg(test)]
mod tests {
use futures::FutureExt;
use std::panic::AssertUnwindSafe;
use super::*;
fn rustfs_tier(endpoint: &str) -> TierRustFS {
TierRustFS {
endpoint: endpoint.to_string(),
access_key: "access".to_string(),
secret_key: "secret".to_string(),
bucket: "bucket".to_string(),
..Default::default()
}
}
#[tokio::test]
async fn new_returns_error_when_endpoint_has_no_host() {
let conf = rustfs_tier("rustfs://");
let outcome = AssertUnwindSafe(WarmBackendRustFS::new(&conf, "tier")).catch_unwind().await;
let result = outcome.expect("initialization should return an error instead of panicking");
let err = match result {
Ok(_) => panic!("endpoint without host must be rejected"),
Err(err) => err,
};
assert!(err.to_string().contains("host"), "expected host validation error, got: {err}");
}
}