mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 12:57:42 +00:00
refactor(utils): decouple config deps and move sys helpers (#2520)
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
This commit is contained in:
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ where
|
||||
fn build_http_client(args: &WebhookArgs) -> Result<Client, TargetError> {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
+168
-56
@@ -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<PathBuf>,
|
||||
cert_filename: impl Into<String>,
|
||||
key_filename: impl Into<String>,
|
||||
) -> 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<String>) -> 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<String>) -> 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<PathBuf>,
|
||||
client_ca_cert_filename: impl Into<String>,
|
||||
fallback_ca_cert_filename: impl Into<String>,
|
||||
) -> 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<String>) -> 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<String>) -> 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<Vec<Vec<u8>>> {
|
||||
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<Option<Arc<dyn ClientCertVerifier>>> {
|
||||
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<Option<Arc<dyn ClientCertVerifier>>> {
|
||||
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<Arc<dyn
|
||||
}
|
||||
|
||||
/// Locate the mTLS client CA bundle in the specified TLS path
|
||||
fn mtls_ca_bundle_path(tls_path: &str) -> Option<std::path::PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<CertificateDer>, 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<HashMap<String, (Vec<CertificateDer<'static>>, 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<PathBuf>) -> 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());
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<PathBuf, String> {
|
||||
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::*;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
|
||||
@@ -643,7 +643,7 @@ async fn resolve_object_lambda_webhook_config(uri: &Uri) -> S3Result<ObjectLambd
|
||||
}
|
||||
|
||||
fn build_object_lambda_http_client(config: &ObjectLambdaWebhookConfig) -> S3Result<reqwest::Client> {
|
||||
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);
|
||||
|
||||
@@ -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}");
|
||||
|
||||
Reference in New Issue
Block a user