From 979626c370d808f9832a78d95d81c9e2c4c6dd75 Mon Sep 17 00:00:00 2001 From: houseme Date: Mon, 13 Apr 2026 21:05:03 +0800 Subject: [PATCH] refactor(utils): decouple config deps and move sys helpers (#2520) Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> --- Cargo.lock | 4 +- crates/notify/Cargo.toml | 2 +- crates/protocols/Cargo.toml | 1 + crates/protocols/src/ftps/server.rs | 7 +- crates/protocols/src/webdav/server.rs | 7 +- crates/targets/Cargo.toml | 3 +- crates/targets/src/lib.rs | 2 + crates/{utils => targets}/src/sys/mod.rs | 2 +- .../{utils => targets}/src/sys/user_agent.rs | 1 - crates/targets/src/target/webhook.rs | 2 +- crates/utils/Cargo.toml | 5 +- crates/utils/src/certs.rs | 224 +++++++++++++----- crates/utils/src/compress.rs | 3 - crates/utils/src/dirs.rs | 94 -------- crates/utils/src/lib.rs | 6 - rustfs/src/admin/router.rs | 2 +- rustfs/src/server/tls_material.rs | 36 ++- 17 files changed, 218 insertions(+), 183 deletions(-) rename crates/{utils => targets}/src/sys/mod.rs (95%) rename crates/{utils => targets}/src/sys/user_agent.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 543966b7c..d4a76393f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8372,6 +8372,7 @@ dependencies = [ "percent-encoding", "quick-xml 0.39.2", "regex", + "rustfs-config", "rustfs-credentials", "rustfs-ecstore", "rustfs-iam", @@ -8556,6 +8557,7 @@ dependencies = [ "serde", "serde_json", "snap", + "sysinfo", "thiserror 2.0.18", "tokio", "tracing", @@ -8613,7 +8615,6 @@ dependencies = [ "netif", "rand 0.10.1", "regex", - "rustfs-config", "rustix 1.1.4", "rustls", "rustls-pki-types", @@ -8623,7 +8624,6 @@ dependencies = [ "sha2 0.11.0", "siphasher", "snap", - "sysinfo", "temp-env", "tempfile", "thiserror 2.0.18", diff --git a/crates/notify/Cargo.toml b/crates/notify/Cargo.toml index ce13f6a81..a8e9df2b7 100644 --- a/crates/notify/Cargo.toml +++ b/crates/notify/Cargo.toml @@ -59,7 +59,7 @@ quick-xml = { workspace = true, features = ["serialize", "serde-types", "encodin tokio = { workspace = true, features = ["test-util"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } axum = { workspace = true } -rustfs-utils = { workspace = true, features = ["path", "sys"] } +rustfs-utils = { workspace = true, features = ["path"] } serde_json = { workspace = true } time = { workspace = true } diff --git a/crates/protocols/Cargo.toml b/crates/protocols/Cargo.toml index 23b954181..4ee045164 100644 --- a/crates/protocols/Cargo.toml +++ b/crates/protocols/Cargo.toml @@ -61,6 +61,7 @@ rustfs-iam = { workspace = true } rustfs-credentials = { workspace = true } rustfs-policy = { workspace = true } rustfs-utils = { workspace = true } +rustfs-config = { workspace = true } # Async dependencies tokio = { workspace = true, features = ["fs", "io-util", "sync", "time"] } diff --git a/crates/protocols/src/ftps/server.rs b/crates/protocols/src/ftps/server.rs index 292648f28..92d32b4f8 100644 --- a/crates/protocols/src/ftps/server.rs +++ b/crates/protocols/src/ftps/server.rs @@ -18,6 +18,7 @@ use crate::common::client::s3::StorageBackend; use crate::common::session::{Protocol, ProtocolPrincipal, SessionContext}; use crate::constants::{network::DEFAULT_SOURCE_IP, paths::ROOT_PATH}; use libunftp::options::FtpsRequired; +use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; use std::fmt::{Debug, Display, Formatter}; use std::net::IpAddr; use std::path::Path; @@ -112,8 +113,10 @@ where debug!("Enabling FTPS with multi-certificate support from directory: {}", cert_dir); // Load all certificates from directory - let cert_key_pairs = rustfs_utils::load_all_certs_from_directory(cert_dir) - .map_err(|e| FtpsInitError::InvalidConfig(format!("Failed to load certificates: {}", e)))?; + let cert_key_pairs = rustfs_utils::load_all_certs_from_directory( + rustfs_utils::CertDirectoryLoadOptions::builder(cert_dir, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY).build(), + ) + .map_err(|e| FtpsInitError::InvalidConfig(format!("Failed to load certificates: {}", e)))?; if cert_key_pairs.is_empty() { return Err(FtpsInitError::InvalidConfig("No valid certificates found in directory".into())); diff --git a/crates/protocols/src/webdav/server.rs b/crates/protocols/src/webdav/server.rs index aaee1bc33..0d925cb78 100644 --- a/crates/protocols/src/webdav/server.rs +++ b/crates/protocols/src/webdav/server.rs @@ -24,6 +24,7 @@ use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::{Request, Response, StatusCode}; use hyper_util::rt::TokioIo; +use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; use rustls::ServerConfig; use std::convert::Infallible; use std::net::IpAddr; @@ -66,8 +67,10 @@ where if let Some(cert_dir) = &self.config.cert_dir { debug!("Enabling WebDAV TLS with certificates from: {}", cert_dir); - let cert_key_pairs = rustfs_utils::load_all_certs_from_directory(cert_dir) - .map_err(|e| WebDavInitError::Tls(format!("Failed to load certificates: {}", e)))?; + let cert_key_pairs = rustfs_utils::load_all_certs_from_directory( + rustfs_utils::CertDirectoryLoadOptions::builder(cert_dir, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY).build(), + ) + .map_err(|e| WebDavInitError::Tls(format!("Failed to load certificates: {}", e)))?; if cert_key_pairs.is_empty() { return Err(WebDavInitError::InvalidConfig("No valid certificates found".into())); diff --git a/crates/targets/Cargo.toml b/crates/targets/Cargo.toml index 5c423af31..7dcf53d0b 100644 --- a/crates/targets/Cargo.toml +++ b/crates/targets/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.rs/rustfs-target/latest/rustfs_target/" [dependencies] rustfs-config = { workspace = true, features = ["notify", "constants", "audit"] } -rustfs-utils = { workspace = true, features = ["sys", "notify", "tls"] } +rustfs-utils = { workspace = true, features = ["notify", "tls"] } rustfs-s3-common = { workspace = true } async-trait = { workspace = true } hyper-rustls = { workspace = true } @@ -29,6 +29,7 @@ tracing = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } uuid = { workspace = true, features = ["v4", "serde"] } +sysinfo = { workspace = true, features = ["multithread"] } [dev-dependencies] criterion = { workspace = true } diff --git a/crates/targets/src/lib.rs b/crates/targets/src/lib.rs index cd2fadfdb..015159caa 100644 --- a/crates/targets/src/lib.rs +++ b/crates/targets/src/lib.rs @@ -16,12 +16,14 @@ pub mod arn; mod check; pub mod error; pub mod store; +pub mod sys; pub mod target; pub use check::{check_mqtt_broker_available, check_mqtt_broker_available_with_tls}; pub use error::{StoreError, TargetError}; pub use rustfs_s3_common::EventName; use serde::{Deserialize, Serialize}; +pub use sys::user_agent::*; pub use target::Target; /// Represents a log of events for sending to targets diff --git a/crates/utils/src/sys/mod.rs b/crates/targets/src/sys/mod.rs similarity index 95% rename from crates/utils/src/sys/mod.rs rename to crates/targets/src/sys/mod.rs index 492617c20..d49ddfb14 100644 --- a/crates/utils/src/sys/mod.rs +++ b/crates/targets/src/sys/mod.rs @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub(crate) mod user_agent; +pub mod user_agent; diff --git a/crates/utils/src/sys/user_agent.rs b/crates/targets/src/sys/user_agent.rs similarity index 99% rename from crates/utils/src/sys/user_agent.rs rename to crates/targets/src/sys/user_agent.rs index 28ed7dd60..bbe3204a6 100644 --- a/crates/utils/src/sys/user_agent.rs +++ b/crates/targets/src/sys/user_agent.rs @@ -200,7 +200,6 @@ mod tests { let ua1 = UserAgent::new(ServiceType::Basis); let ua2 = UserAgent::new(ServiceType::Basis); assert_eq!(ua1.os_platform, ua2.os_platform); - // Ensure they point to the same static memory assert!(std::ptr::eq(ua1.os_platform.as_ptr(), ua2.os_platform.as_ptr())); } } diff --git a/crates/targets/src/target/webhook.rs b/crates/targets/src/target/webhook.rs index a34b16e10..d247d2f7c 100644 --- a/crates/targets/src/target/webhook.rs +++ b/crates/targets/src/target/webhook.rs @@ -179,7 +179,7 @@ where fn build_http_client(args: &WebhookArgs) -> Result { let mut client_builder = Client::builder() .timeout(Duration::from_secs(30)) - .user_agent(rustfs_utils::get_user_agent(rustfs_utils::ServiceType::Basis)); + .user_agent(crate::get_user_agent(crate::ServiceType::Basis)); // 1. Configure server certificate verification if args.skip_tls_verify { diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index c4f6742d1..387ffdc27 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -46,7 +46,6 @@ netif = { workspace = true, optional = true } rand = { workspace = true, optional = true } regex = { workspace = true, optional = true } rustix = { workspace = true, optional = true } -rustfs-config = { workspace = true, features = ["constants"] } rustls = { workspace = true, optional = true } rustls-pki-types = { workspace = true, optional = true } s3s = { workspace = true, optional = true } @@ -56,7 +55,6 @@ sha2 = { workspace = true, optional = true } convert_case = { workspace = true, optional = true } siphasher = { workspace = true, optional = true } snap = { workspace = true, optional = true } -sysinfo = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } tokio = { workspace = true, optional = true, features = ["io-util", "macros"] } @@ -91,7 +89,6 @@ crypto = ["dep:base64-simd", "dep:hex-simd", "dep:hmac", "dep:hyper", "dep:sha1" hash = ["dep:highway", "dep:md-5", "dep:sha2", "dep:blake2", "dep:serde", "dep:siphasher", "dep:hex-simd", "dep:crc-fast"] os = ["dep:rustix", "dep:tempfile", "dep:windows"] # operating system utilities integration = [] # integration test features -sys = ["dep:sysinfo"] # system information features http = ["dep:convert_case", "dep:http", "dep:regex"] obj = ["http"] # object storage features -full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "sys", "notify", "http", "obj"] # all features +full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "notify", "http", "obj"] # all features diff --git a/crates/utils/src/certs.rs b/crates/utils/src/certs.rs index ff81431fb..1366064ee 100644 --- a/crates/utils/src/certs.rs +++ b/crates/utils/src/certs.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::get_env_bool; -use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; use rustls::RootCertStore; use rustls::server::{ ClientHello, ResolvesServerCert, ResolvesServerCertUsingSni, WebPkiClientVerifier, danger::ClientCertVerifier, @@ -22,11 +20,139 @@ use rustls::sign::CertifiedKey; use rustls_pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}; use std::collections::HashMap; use std::io::Error; -use std::path::Path; +use std::path::PathBuf; use std::sync::Arc; use std::{fs, io}; use tracing::{debug, warn}; +/// Options for loading certificate/key pairs from a directory tree. +#[derive(Debug, Clone)] +pub struct CertDirectoryLoadOptions { + dir_path: PathBuf, + cert_filename: String, + key_filename: String, +} + +impl CertDirectoryLoadOptions { + /// Create a builder with explicit certificate and private key filenames. + pub fn builder( + dir_path: impl Into, + cert_filename: impl Into, + key_filename: impl Into, + ) -> CertDirectoryLoadOptionsBuilder { + CertDirectoryLoadOptionsBuilder { + dir_path: dir_path.into(), + cert_filename: cert_filename.into(), + key_filename: key_filename.into(), + } + } + + fn validate(&self) -> io::Result<()> { + if self.cert_filename.is_empty() { + return Err(certs_error("certificate filename cannot be empty".to_string())); + } + if self.key_filename.is_empty() { + return Err(certs_error("private key filename cannot be empty".to_string())); + } + Ok(()) + } +} + +/// Builder for [`CertDirectoryLoadOptions`]. +#[derive(Debug, Clone)] +pub struct CertDirectoryLoadOptionsBuilder { + dir_path: PathBuf, + cert_filename: String, + key_filename: String, +} + +impl CertDirectoryLoadOptionsBuilder { + /// Override the certificate filename searched in the directory. + pub fn cert_filename(mut self, cert_filename: impl Into) -> Self { + self.cert_filename = cert_filename.into(); + self + } + + /// Override the private key filename searched in the directory. + pub fn key_filename(mut self, key_filename: impl Into) -> Self { + self.key_filename = key_filename.into(); + self + } + + /// Build the load options value. + pub fn build(self) -> CertDirectoryLoadOptions { + CertDirectoryLoadOptions { + dir_path: self.dir_path, + cert_filename: self.cert_filename, + key_filename: self.key_filename, + } + } +} + +/// Options for building an mTLS WebPki client verifier. +#[derive(Debug, Clone)] +pub struct WebPkiClientVerifierOptions { + tls_path: PathBuf, + enabled: bool, + client_ca_cert_filename: String, + fallback_ca_cert_filename: String, +} + +impl WebPkiClientVerifierOptions { + /// Create a builder with explicit CA bundle filenames. + pub fn builder( + tls_path: impl Into, + client_ca_cert_filename: impl Into, + fallback_ca_cert_filename: impl Into, + ) -> WebPkiClientVerifierOptionsBuilder { + WebPkiClientVerifierOptionsBuilder { + tls_path: tls_path.into(), + enabled: false, + client_ca_cert_filename: client_ca_cert_filename.into(), + fallback_ca_cert_filename: fallback_ca_cert_filename.into(), + } + } +} + +/// Builder for [`WebPkiClientVerifierOptions`]. +#[derive(Debug, Clone)] +pub struct WebPkiClientVerifierOptionsBuilder { + tls_path: PathBuf, + enabled: bool, + client_ca_cert_filename: String, + fallback_ca_cert_filename: String, +} + +impl WebPkiClientVerifierOptionsBuilder { + /// Set whether mTLS verification should be enabled. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Override the preferred client CA bundle filename. + pub fn client_ca_cert_filename(mut self, client_ca_cert_filename: impl Into) -> Self { + self.client_ca_cert_filename = client_ca_cert_filename.into(); + self + } + + /// Override the fallback CA bundle filename. + pub fn fallback_ca_cert_filename(mut self, fallback_ca_cert_filename: impl Into) -> Self { + self.fallback_ca_cert_filename = fallback_ca_cert_filename.into(); + self + } + + /// Build the verifier options value. + pub fn build(self) -> WebPkiClientVerifierOptions { + WebPkiClientVerifierOptions { + tls_path: self.tls_path, + enabled: self.enabled, + client_ca_cert_filename: self.client_ca_cert_filename, + fallback_ca_cert_filename: self.fallback_ca_cert_filename, + } + } +} + /// Load public certificate from file. /// This function loads a public certificate from the specified file. /// @@ -72,24 +198,28 @@ pub fn load_cert_bundle_der_bytes(path: &str) -> io::Result>> { Ok(certs.into_iter().map(|c| c.to_vec()).collect()) } -/// Builds a WebPkiClientVerifier for mTLS if enabled via environment variable. +/// Builds a WebPkiClientVerifier for mTLS when enabled by the caller. /// /// # Arguments -/// * `tls_path` - Directory containing client CA certificates +/// * `options` - mTLS verifier options, including the TLS directory and CA bundle filenames /// /// # Returns /// * `Ok(Some(verifier))` if mTLS is enabled and CA certs are found /// * `Ok(None)` if mTLS is disabled /// * `Err` if mTLS is enabled but configuration is invalid -pub fn build_webpki_client_verifier(tls_path: &str) -> io::Result>> { - if !get_env_bool(rustfs_config::ENV_SERVER_MTLS_ENABLE, rustfs_config::DEFAULT_SERVER_MTLS_ENABLE) { +pub fn build_webpki_client_verifier(options: WebPkiClientVerifierOptions) -> io::Result>> { + if !options.enabled { return Ok(None); } - let ca_path = mtls_ca_bundle_path(tls_path).ok_or_else(|| { + let tls_path = &options.tls_path; + let ca_path = mtls_ca_bundle_path(&options).ok_or_else(|| { Error::other(format!( - "RUSTFS_SERVER_MTLS_ENABLE=true but missing {}/client_ca.crt (or fallback {}/ca.crt)", - tls_path, tls_path + "mTLS is enabled but missing {}/{} (or fallback {}/{})", + tls_path.display(), + options.client_ca_cert_filename, + tls_path.display(), + options.fallback_ca_cert_filename )) })?; @@ -114,14 +244,12 @@ pub fn build_webpki_client_verifier(tls_path: &str) -> io::Result Option { - use std::path::Path; - - let p1 = Path::new(tls_path).join(rustfs_config::RUSTFS_CLIENT_CA_CERT_FILENAME); +fn mtls_ca_bundle_path(options: &WebPkiClientVerifierOptions) -> Option { + let p1 = options.tls_path.join(&options.client_ca_cert_filename); if p1.exists() { return Some(p1); } - let p2 = Path::new(tls_path).join(rustfs_config::RUSTFS_CA_CERT); + let p2 = options.tls_path.join(&options.fallback_ca_cert_filename); if p2.exists() { return Some(p2); } @@ -162,30 +290,33 @@ pub fn certs_error(err: String) -> Error { /// Load all certificates and private keys in the directory /// This function loads all certificate and private key pairs from the specified directory. -/// It looks for files named `rustfs_cert.pem` and `rustfs_key.pem` in each subdirectory. +/// It looks for files named `options.cert_filename` and `options.key_filename` in each subdirectory. /// The root directory can also contain a default certificate/private key pair. /// /// # Arguments -/// * `dir_path` - A string slice that holds the path to the directory containing the certificates and private keys. +/// * `options` - Directory and filename options for discovering certificates and private keys. /// /// # Returns /// * An io::Result containing a HashMap where the keys are domain names (or "default" for the root certificate) and the values are tuples of (Vec, PrivateKeyDer). If no valid certificate/private key pairs are found, an io::Error is returned. /// pub fn load_all_certs_from_directory( - dir_path: &str, + options: CertDirectoryLoadOptions, ) -> io::Result>, PrivateKeyDer<'static>)>> { + options.validate()?; + let mut cert_key_pairs = HashMap::new(); - let dir = Path::new(dir_path); + let dir = options.dir_path.as_path(); if !dir.exists() || !dir.is_dir() { return Err(certs_error(format!( - "The certificate directory does not exist or is not a directory: {dir_path}" + "The certificate directory does not exist or is not a directory: {}", + dir.display() ))); } // 1. First check whether there is a certificate/private key pair in the root directory - let root_cert_path = dir.join(RUSTFS_TLS_CERT); - let root_key_path = dir.join(RUSTFS_TLS_KEY); + let root_cert_path = dir.join(&options.cert_filename); + let root_key_path = dir.join(&options.key_filename); if root_cert_path.exists() && root_key_path.exists() { debug!("find the root directory certificate: {:?}", root_cert_path); @@ -218,8 +349,8 @@ pub fn load_all_certs_from_directory( .ok_or_else(|| certs_error(format!("invalid domain name directory:{path:?}")))?; // find certificate and private key files - let cert_path = path.join(RUSTFS_TLS_CERT); // e.g., rustfs_cert.pem - let key_path = path.join(RUSTFS_TLS_KEY); // e.g., rustfs_key.pem + let cert_path = path.join(&options.cert_filename); // e.g., rustfs_cert.pem + let key_path = path.join(&options.key_filename); // e.g., rustfs_key.pem if cert_path.exists() && key_path.exists() { debug!("find the domain name certificate: {} in {:?}", domain_name, cert_path); @@ -253,7 +384,8 @@ pub fn load_all_certs_from_directory( if cert_key_pairs.is_empty() { return Err(certs_error(format!( - "No valid certificate/private key pair found in directory {dir_path}" + "No valid certificate/private key pair found in directory {}", + dir.display() ))); } @@ -334,15 +466,6 @@ pub fn create_multi_cert_resolver( }) } -/// Checks if TLS key logging is enabled. -/// -/// # Returns -/// * A boolean indicating whether TLS key logging is enabled based on the `RUSTFS_TLS_KEYLOG` environment variable. -/// -pub fn tls_key_log() -> bool { - get_env_bool(rustfs_config::ENV_TLS_KEYLOG, rustfs_config::DEFAULT_TLS_KEYLOG) -} - #[cfg(test)] mod tests { use super::*; @@ -350,6 +473,10 @@ mod tests { use std::io::ErrorKind; use tempfile::TempDir; + fn default_load_options(path: impl Into) -> CertDirectoryLoadOptions { + CertDirectoryLoadOptions::builder(path, "rustfs_cert.pem", "rustfs_key.pem").build() + } + #[test] fn test_certs_error_function() { let error_msg = "Test error message"; @@ -433,7 +560,7 @@ mod tests { #[test] fn test_load_all_certs_from_directory_not_exists() { - let result = load_all_certs_from_directory("/non/existent/directory"); + let result = load_all_certs_from_directory(default_load_options("/non/existent/directory")); assert!(result.is_err()); let error = result.unwrap_err(); @@ -444,7 +571,7 @@ mod tests { fn test_load_all_certs_from_directory_empty() { let temp_dir = TempDir::new().unwrap(); - let result = load_all_certs_from_directory(temp_dir.path().to_str().unwrap()); + let result = load_all_certs_from_directory(default_load_options(temp_dir.path())); assert!(result.is_err()); let error = result.unwrap_err(); @@ -457,7 +584,7 @@ mod tests { let file_path = temp_dir.path().join("not_a_directory.txt"); fs::write(&file_path, "content").unwrap(); - let result = load_all_certs_from_directory(file_path.to_str().unwrap()); + let result = load_all_certs_from_directory(default_load_options(&file_path)); assert!(result.is_err()); let error = result.unwrap_err(); @@ -523,27 +650,12 @@ mod tests { ]; for path in path_cases { - let result = load_all_certs_from_directory(path); + let result = load_all_certs_from_directory(default_load_options(path)); // All should fail since these are not valid cert directories assert!(result.is_err()); } } - #[test] - fn test_filename_constants_consistency() { - // Test that the constants match expected values - assert_eq!(RUSTFS_TLS_CERT, "rustfs_cert.pem"); - assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem"); - - // Test that constants are not empty - assert!(!RUSTFS_TLS_CERT.is_empty()); - assert!(!RUSTFS_TLS_KEY.is_empty()); - - // Test that constants have proper extensions - assert!(RUSTFS_TLS_CERT.ends_with(".pem")); - assert!(RUSTFS_TLS_KEY.ends_with(".pem")); - } - #[test] fn test_directory_structure_validation() { let temp_dir = TempDir::new().unwrap(); @@ -553,7 +665,7 @@ mod tests { fs::create_dir(&sub_dir).unwrap(); // Should fail because no certificates found - let result = load_all_certs_from_directory(temp_dir.path().to_str().unwrap()); + let result = load_all_certs_from_directory(default_load_options(temp_dir.path())); assert!(result.is_err()); assert!( result @@ -571,7 +683,7 @@ mod tests { let unicode_dir = temp_dir.path().join("test_directory"); fs::create_dir(&unicode_dir).unwrap(); - let result = load_all_certs_from_directory(unicode_dir.to_str().unwrap()); + let result = load_all_certs_from_directory(default_load_options(&unicode_dir)); assert!(result.is_err()); assert!( result @@ -593,7 +705,7 @@ mod tests { .map(|_| { let path = Arc::clone(&dir_path); thread::spawn(move || { - let result = load_all_certs_from_directory(&path); + let result = load_all_certs_from_directory(default_load_options(path.as_str())); // All should fail since directory is empty assert!(result.is_err()); }) diff --git a/crates/utils/src/compress.rs b/crates/utils/src/compress.rs index a2686ef5c..c492b841b 100644 --- a/crates/utils/src/compress.rs +++ b/crates/utils/src/compress.rs @@ -238,9 +238,6 @@ mod tests { use std::time::Instant; let data = vec![42u8; 1024 * 100]; // 100KB of repetitive data - // let mut data = vec![0u8; 1024 * 1024]; - // rand::thread_rng().fill(&mut data[..]); - let start = Instant::now(); let mut times = Vec::new(); diff --git a/crates/utils/src/dirs.rs b/crates/utils/src/dirs.rs index bba272e6e..81b8bc14e 100644 --- a/crates/utils/src/dirs.rs +++ b/crates/utils/src/dirs.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rustfs_config::{DEFAULT_LOG_DIR, DEFAULT_LOG_FILENAME}; use std::env; -use std::fs; use std::path::{Path, PathBuf}; use tracing::debug; @@ -61,98 +59,6 @@ pub fn get_project_root() -> Result { Err("The project root directory cannot be obtained. Please check the running environment and project structure.".to_string()) } -/// Get the log directory as a string -/// This function will try to find a writable log directory in the following order: -/// -/// 1. Environment variables are specified -/// 2. System temporary directory -/// 3. User home directory -/// 4. Current working directory -/// 5. Relative path -/// -/// # Arguments -/// * `key` - The environment variable key to check for log directory -/// -/// # Returns -/// * `String` - The log directory path as a string -/// -pub fn get_log_directory_to_string(key: &str) -> String { - get_log_directory(key).to_string_lossy().to_string() -} - -/// Get the log directory -/// This function will try to find a writable log directory in the following order: -/// -/// 1. Environment variables are specified -/// 2. System temporary directory -/// 3. User home directory -/// 4. Current working directory -/// 5. Relative path -/// -/// # Arguments -/// * `key` - The environment variable key to check for log directory -/// -/// # Returns -/// * `PathBuf` - The log directory path -/// -pub fn get_log_directory(key: &str) -> PathBuf { - // Environment variables are specified - if let Ok(log_dir) = env::var(key) { - let path = PathBuf::from(log_dir); - if ensure_directory_writable(&path) { - return path; - } - } - - // System temporary directory - if let Ok(mut temp_dir) = env::temp_dir().canonicalize() { - temp_dir.push(DEFAULT_LOG_FILENAME); - temp_dir.push(DEFAULT_LOG_DIR); - if ensure_directory_writable(&temp_dir) { - return temp_dir; - } - } - - // User home directory - if let Ok(home_dir) = env::var("HOME").or_else(|_| env::var("USERPROFILE")) { - let mut path = PathBuf::from(home_dir); - path.push(format!(".{DEFAULT_LOG_FILENAME}")); - path.push(DEFAULT_LOG_DIR); - if ensure_directory_writable(&path) { - return path; - } - } - - // Current working directory - if let Ok(current_dir) = env::current_dir() { - let mut path = current_dir; - path.push(DEFAULT_LOG_DIR); - if ensure_directory_writable(&path) { - return path; - } - } - - // Relative path - PathBuf::from(DEFAULT_LOG_DIR) -} - -fn ensure_directory_writable(path: &PathBuf) -> bool { - // Try creating a catalog - if fs::create_dir_all(path).is_err() { - return false; - } - - // Check write permissions - let test_file = path.join(".write_test"); - match fs::write(&test_file, "test") { - Ok(_) => { - let _ = fs::remove_file(&test_file); - true - } - Err(_) => false, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 1bb007fa7..722b81b43 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -73,12 +73,6 @@ pub use compress::*; #[cfg(feature = "notify")] mod notify; -#[cfg(feature = "sys")] -pub mod sys; - -#[cfg(feature = "sys")] -pub use sys::user_agent::*; - #[cfg(feature = "notify")] pub use notify::*; diff --git a/rustfs/src/admin/router.rs b/rustfs/src/admin/router.rs index f25441187..a6493f503 100644 --- a/rustfs/src/admin/router.rs +++ b/rustfs/src/admin/router.rs @@ -643,7 +643,7 @@ async fn resolve_object_lambda_webhook_config(uri: &Uri) -> S3Result S3Result { - let mut builder = reqwest::Client::builder().user_agent(rustfs_utils::get_user_agent(rustfs_utils::ServiceType::Basis)); + let mut builder = reqwest::Client::builder().user_agent(rustfs_targets::get_user_agent(rustfs_targets::ServiceType::Basis)); if let Some(timeout) = config.response_header_timeout { builder = builder.timeout(timeout); diff --git a/rustfs/src/server/tls_material.rs b/rustfs/src/server/tls_material.rs index 47b3e9425..29048a08b 100644 --- a/rustfs/src/server/tls_material.rs +++ b/rustfs/src/server/tls_material.rs @@ -24,9 +24,10 @@ use rustfs_common::{MtlsIdentityPem, set_global_mtls_identity, set_global_root_cert}; use rustfs_config::{ - DEFAULT_TLS_RELOAD_ENABLE, DEFAULT_TLS_RELOAD_INTERVAL, DEFAULT_TRUST_LEAF_CERT_AS_CA, DEFAULT_TRUST_SYSTEM_CA, - ENV_MTLS_CLIENT_CERT, ENV_MTLS_CLIENT_KEY, ENV_TLS_RELOAD_ENABLE, ENV_TLS_RELOAD_INTERVAL, ENV_TRUST_LEAF_CERT_AS_CA, - ENV_TRUST_SYSTEM_CA, RUSTFS_CA_CERT, RUSTFS_CLIENT_CERT_FILENAME, RUSTFS_CLIENT_KEY_FILENAME, RUSTFS_PUBLIC_CERT, + DEFAULT_SERVER_MTLS_ENABLE, DEFAULT_TLS_KEYLOG, DEFAULT_TLS_RELOAD_ENABLE, DEFAULT_TLS_RELOAD_INTERVAL, + DEFAULT_TRUST_LEAF_CERT_AS_CA, DEFAULT_TRUST_SYSTEM_CA, ENV_MTLS_CLIENT_CERT, ENV_MTLS_CLIENT_KEY, ENV_SERVER_MTLS_ENABLE, + ENV_TLS_KEYLOG, ENV_TLS_RELOAD_ENABLE, ENV_TLS_RELOAD_INTERVAL, ENV_TRUST_LEAF_CERT_AS_CA, ENV_TRUST_SYSTEM_CA, + RUSTFS_CA_CERT, RUSTFS_CLIENT_CA_CERT_FILENAME, RUSTFS_CLIENT_CERT_FILENAME, RUSTFS_CLIENT_KEY_FILENAME, RUSTFS_PUBLIC_CERT, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY, }; use rustfs_utils::{get_env_bool, get_env_opt_str}; @@ -113,11 +114,17 @@ impl TlsMaterialSnapshot { return Ok(None); } - let mtls_verifier = rustfs_utils::build_webpki_client_verifier(tls_path) - .map_err(|e| TlsMaterialError::Io(format!("build mTLS verifier: {e}")))?; + let mtls_verifier = rustfs_utils::build_webpki_client_verifier( + rustfs_utils::WebPkiClientVerifierOptions::builder(tls_path, RUSTFS_CLIENT_CA_CERT_FILENAME, RUSTFS_CA_CERT) + .enabled(get_env_bool(ENV_SERVER_MTLS_ENABLE, DEFAULT_SERVER_MTLS_ENABLE)) + .build(), + ) + .map_err(|e| TlsMaterialError::Io(format!("build mTLS verifier: {e}")))?; // Try multi-cert (SNI) first - match rustfs_utils::load_all_certs_from_directory(tls_path) { + 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(resolver) => { let config = build_server_config(ServerCertSource::Resolver(Arc::new(resolver)), mtls_verifier)?; @@ -210,13 +217,22 @@ fn build_server_config( config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; config.session_storage = rustls::server::ServerSessionMemoryCache::new(10000); - if rustfs_utils::tls_key_log() { + if tls_key_log() { config.key_log = Arc::new(rustls::KeyLogFile::new()); } Ok(config) } +/// Checks if TLS key logging is enabled. +/// +/// # Returns +/// * A boolean indicating whether TLS key logging is enabled based on the `RUSTFS_TLS_KEYLOG` environment variable. +/// +fn tls_key_log() -> bool { + get_env_bool(ENV_TLS_KEYLOG, DEFAULT_TLS_KEYLOG) +} + // ── Outbound Material Loading ── /// Load root CA certificates and mTLS identity for outbound connections. @@ -266,7 +282,11 @@ async fn has_server_certificates(tls_path: &str) -> bool { return false; } // Check for multi-cert directory structure OR single cert files - if rustfs_utils::load_all_certs_from_directory(tls_path).is_ok_and(|p| !p.is_empty()) { + 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}");