diff --git a/Cargo.lock b/Cargo.lock index dd3f0476c..e6d97d1fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4771,9 +4771,9 @@ dependencies = [ [[package]] name = "libunftp" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270fae0f77279620962f533153fa727a9cf9485dbb79d47eed3086d42b17264" +checksum = "3186f219c64999ae9a2900c78ebfaf4a6b4acc3b2bbf5afe5e027e5ca5978593" dependencies = [ "async-trait", "bitflags 2.10.0", @@ -4787,11 +4787,10 @@ dependencies = [ "libc", "md-5 0.10.6", "moka", - "nix 0.29.0", + "nix 0.30.1", "prometheus", "proxy-protocol", "rustls", - "rustls-pemfile", "slog", "slog-stdlog", "thiserror 2.0.18", @@ -4801,7 +4800,7 @@ dependencies = [ "tracing", "tracing-attributes", "uuid", - "x509-parser 0.17.0", + "x509-parser", ] [[package]] @@ -5141,6 +5140,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -6421,7 +6432,7 @@ dependencies = [ "ring", "rustls-pki-types", "time", - "x509-parser 0.18.1", + "x509-parser", "yasna", ] @@ -10278,23 +10289,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom 7.1.3", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - [[package]] name = "x509-parser" version = "0.18.1" diff --git a/Cargo.toml b/Cargo.toml index 887cbd4de..f04a9a580 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -274,7 +274,7 @@ opentelemetry-semantic-conventions = { version = "0.31.0", features = ["semconv_ opentelemetry-stdout = { version = "0.31.0" } # FTP and SFTP -libunftp = { version = "0.21.0", features = ["experimental"] } +libunftp = { version = "0.22.0", features = ["experimental"] } suppaftp = { version = "8.0.1", features = ["tokio", "tokio-rustls-aws-lc-rs"] } rcgen = "0.14.7" diff --git a/crates/protocols/src/ftps/server.rs b/crates/protocols/src/ftps/server.rs index 21fbbe1c2..ad37098aa 100644 --- a/crates/protocols/src/ftps/server.rs +++ b/crates/protocols/src/ftps/server.rs @@ -17,7 +17,7 @@ use super::driver::FtpsDriver; 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::auth::{AuthenticationError, UserDetail}; +use libunftp::auth::{AuthenticationError, Authenticator, Principal, UserDetail, UserDetailError, UserDetailProvider}; use libunftp::options::FtpsRequired; use std::fmt::{Debug, Display, Formatter}; use std::net::IpAddr; @@ -78,10 +78,11 @@ where info!("Initializing FTPS server on {}", self.config.bind_addr); let storage_clone = self.storage.clone(); - let mut server_builder = libunftp::ServerBuilder::with_authenticator( + let mut server_builder = libunftp::ServerBuilder::with_user_detail_provider( Box::new(move || FtpsDriver::new(storage_clone.clone())), - Arc::new(FtpsAuthenticator::new()), - ); + Arc::new(FtpsUserDetailProvider), + ) + .authenticator(Arc::new(FtpsAuthenticator::new())); // Configure passive ports for data connections if let Some(passive_ports) = &self.config.passive_ports { @@ -195,6 +196,49 @@ where } } +/// FTPS user detail provider implementation +#[derive(Debug)] +pub struct FtpsUserDetailProvider; + +#[async_trait::async_trait] +impl UserDetailProvider for FtpsUserDetailProvider { + type User = FtpsUser; + + async fn provide_user_detail(&self, principal: &Principal) -> Result { + use rustfs_iam::get; + + // Access IAM system + let iam_sys = get().map_err(|e| { + error!("IAM system unavailable during FTPS user detail fetch: {}", e); + UserDetailError::ImplPropagated("Internal authentication service unavailable".to_string(), Some(Box::new(e))) + })?; + + let (user_identity, _is_valid) = iam_sys.check_key(&principal.username).await.map_err(|e| { + error!("IAM check_key failed for {}: {}", principal.username, e); + UserDetailError::ImplPropagated("Authentication verification failed".to_string(), Some(Box::new(e))) + })?; + + let identity = user_identity.ok_or_else(|| { + error!("User identity missing for {}", principal.username); + UserDetailError::UserNotFound { + username: principal.username.clone(), + } + })?; + + let source_ip: IpAddr = DEFAULT_SOURCE_IP.parse().unwrap(); + + let session_context = SessionContext::new(ProtocolPrincipal::new(Arc::new(identity.clone())), Protocol::Ftps, source_ip); + + let ftps_user = FtpsUser { + username: principal.username.clone(), + name: identity.credentials.name.clone(), + session_context, + }; + + Ok(ftps_user) + } +} + /// FTPS authenticator implementation #[derive(Debug, Default)] pub struct FtpsAuthenticator; @@ -207,9 +251,9 @@ impl FtpsAuthenticator { } #[async_trait::async_trait] -impl libunftp::auth::Authenticator for FtpsAuthenticator { +impl Authenticator for FtpsAuthenticator { /// Authenticate FTP user against RustFS IAM system - async fn authenticate(&self, username: &str, creds: &libunftp::auth::Credentials) -> Result { + async fn authenticate(&self, username: &str, creds: &libunftp::auth::Credentials) -> Result { use rustfs_credentials::Credentials as S3Credentials; use rustfs_iam::get; @@ -252,17 +296,9 @@ impl libunftp::auth::Authenticator for FtpsAuthenticator { return Err(AuthenticationError::BadPassword); } - let source_ip: IpAddr = DEFAULT_SOURCE_IP.parse().unwrap(); - - let session_context = SessionContext::new(ProtocolPrincipal::new(Arc::new(identity.clone())), Protocol::Ftps, source_ip); - - let ftps_user = FtpsUser { - username: username.to_string(), - name: identity.credentials.name.clone(), - session_context, - }; - info!("FTPS user '{}' authenticated successfully", username); - Ok(ftps_user) + Ok(Principal { + username: username.to_string(), + }) } }