diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index cd5ba113..3e2da8e1 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -82,7 +82,7 @@ tokio-stream.workspace = true tokio-util.workspace = true tonic = { workspace = true } tower.workspace = true -tower-http = { workspace = true, features = ["trace", "compression-full", "cors", "catch-panic", "timeout", "limit", "request-id"] } +tower-http = { workspace = true, features = ["trace", "compression-full", "cors", "catch-panic", "timeout", "limit", "request-id", "add-extension"] } # Serialization and Data Formats bytes = { workspace = true } diff --git a/rustfs/src/admin/auth.rs b/rustfs/src/admin/auth.rs index 63d4ab65..1447c8a7 100644 --- a/rustfs/src/admin/auth.rs +++ b/rustfs/src/admin/auth.rs @@ -30,12 +30,13 @@ pub async fn validate_admin_request( is_owner: bool, deny_only: bool, actions: Vec, + remote_addr: Option, ) -> S3Result<()> { let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")); }; for action in actions { - match check_admin_request_auth(iam_store.clone(), headers, cred, is_owner, deny_only, action).await { + match check_admin_request_auth(iam_store.clone(), headers, cred, is_owner, deny_only, action, remote_addr).await { Ok(_) => return Ok(()), Err(_) => { continue; @@ -53,8 +54,9 @@ async fn check_admin_request_auth( is_owner: bool, deny_only: bool, action: Action, + remote_addr: Option, ) -> S3Result<()> { - let conditions = get_condition_values(headers, cred, None, None); + let conditions = get_condition_values(headers, cred, None, None, remote_addr); if !iam_store .is_allowed(&Args { diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index f1a8b212..5b3f05b0 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -18,6 +18,7 @@ use crate::auth::check_key_valid; use crate::auth::get_condition_values; use crate::auth::get_session_token; use crate::error::ApiError; +use crate::server::RemoteAddr; use bytes::Bytes; use futures::{Stream, StreamExt}; use http::{HeaderMap, HeaderValue, Uri}; @@ -210,7 +211,8 @@ impl Operation for AccountInfoHandler { let claims = cred.claims.as_ref().unwrap_or(&default_claims); let cred_clone = cred.clone(); - let conditions = get_condition_values(&req.headers, &cred_clone, None, None); + let remote_addr = req.extensions.get::().map(|a| a.0); + let conditions = get_condition_values(&req.headers, &cred_clone, None, None, remote_addr); let cred_clone = Arc::new(cred_clone); let conditions = Arc::new(conditions); @@ -405,12 +407,14 @@ impl Operation for ServerInfoHandler { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; + let remote_addr = req.extensions.get::().map(|a| a.0); validate_admin_request( &req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + remote_addr, ) .await?; @@ -451,12 +455,14 @@ impl Operation for StorageInfoHandler { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; + let remote_addr = req.extensions.get::().map(|a| a.0); validate_admin_request( &req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::StorageInfoAdminAction)], + remote_addr, ) .await?; @@ -492,6 +498,7 @@ impl Operation for DataUsageInfoHandler { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; + let remote_addr = req.extensions.get::().map(|a| a.0); validate_admin_request( &req.headers, &cred, @@ -501,6 +508,7 @@ impl Operation for DataUsageInfoHandler { Action::AdminAction(AdminAction::DataUsageInfoAdminAction), Action::S3Action(S3Action::ListBucketAction), ], + remote_addr, ) .await?; diff --git a/rustfs/src/admin/handlers/bucket_meta.rs b/rustfs/src/admin/handlers/bucket_meta.rs index ea553672..8d8317c2 100644 --- a/rustfs/src/admin/handlers/bucket_meta.rs +++ b/rustfs/src/admin/handlers/bucket_meta.rs @@ -20,6 +20,7 @@ use std::{ use crate::{ admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -97,6 +98,7 @@ impl Operation for ExportBucketMetadata { owner, false, vec![Action::AdminAction(AdminAction::ExportBucketMetadataAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -389,6 +391,7 @@ impl Operation for ImportBucketMetadata { owner, false, vec![Action::AdminAction(AdminAction::ImportBucketMetadataAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/group.rs b/rustfs/src/admin/handlers/group.rs index 07ae9baf..e3dd7463 100644 --- a/rustfs/src/admin/handlers/group.rs +++ b/rustfs/src/admin/handlers/group.rs @@ -15,6 +15,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, auth::{check_key_valid, constant_time_eq, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -57,6 +58,7 @@ impl Operation for ListGroups { owner, false, vec![Action::AdminAction(AdminAction::ListGroupsAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -95,6 +97,7 @@ impl Operation for GetGroup { owner, false, vec![Action::AdminAction(AdminAction::GetGroupAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -142,6 +145,7 @@ impl Operation for SetGroupStatus { owner, false, vec![Action::AdminAction(AdminAction::EnableGroupAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -209,6 +213,7 @@ impl Operation for UpdateGroupMembers { owner, false, vec![Action::AdminAction(AdminAction::AddUserToGroupAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/kms.rs b/rustfs/src/admin/handlers/kms.rs index 741508c2..95e52c74 100644 --- a/rustfs/src/admin/handlers/kms.rs +++ b/rustfs/src/admin/handlers/kms.rs @@ -17,6 +17,7 @@ use super::Operation; use crate::admin::auth::validate_admin_request; use crate::auth::{check_key_valid, get_session_token}; +use crate::server::RemoteAddr; use base64::Engine; use hyper::{HeaderMap, StatusCode}; use matchit::Params; @@ -127,6 +128,7 @@ impl Operation for CreateKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], // TODO: Add specific KMS action + req.extensions.get::().map(|a| a.0), ) .await?; @@ -205,6 +207,7 @@ impl Operation for DescribeKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -260,6 +263,7 @@ impl Operation for ListKeysHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -321,6 +325,7 @@ impl Operation for GenerateDataKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -386,6 +391,7 @@ impl Operation for KmsStatusHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -443,6 +449,7 @@ impl Operation for KmsConfigHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -487,6 +494,7 @@ impl Operation for KmsClearCacheHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/kms_dynamic.rs b/rustfs/src/admin/handlers/kms_dynamic.rs index 95bcddb7..de798686 100644 --- a/rustfs/src/admin/handlers/kms_dynamic.rs +++ b/rustfs/src/admin/handlers/kms_dynamic.rs @@ -17,6 +17,7 @@ use super::Operation; use crate::admin::auth::validate_admin_request; use crate::auth::{check_key_valid, get_session_token}; +use crate::server::RemoteAddr; use hyper::StatusCode; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; @@ -98,6 +99,7 @@ impl Operation for ConfigureKmsHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -196,6 +198,7 @@ impl Operation for StartKmsHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -329,6 +332,7 @@ impl Operation for StopKmsHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -394,6 +398,7 @@ impl Operation for GetKmsStatusHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -465,6 +470,7 @@ impl Operation for ReconfigureKmsHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/kms_keys.rs b/rustfs/src/admin/handlers/kms_keys.rs index 661b1ba9..d1be6bcf 100644 --- a/rustfs/src/admin/handlers/kms_keys.rs +++ b/rustfs/src/admin/handlers/kms_keys.rs @@ -17,6 +17,7 @@ use super::Operation; use crate::admin::auth::validate_admin_request; use crate::auth::{check_key_valid, get_session_token}; +use crate::server::RemoteAddr; use hyper::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; @@ -79,6 +80,7 @@ impl Operation for CreateKmsKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -212,6 +214,7 @@ impl Operation for DeleteKmsKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -360,6 +363,7 @@ impl Operation for CancelKmsKeyDeletionHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -488,6 +492,7 @@ impl Operation for ListKmsKeysHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -599,6 +604,7 @@ impl Operation for DescribeKmsKeyHandler { owner, false, vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/policies.rs b/rustfs/src/admin/handlers/policies.rs index 7587c6ae..a33fed81 100644 --- a/rustfs/src/admin/handlers/policies.rs +++ b/rustfs/src/admin/handlers/policies.rs @@ -15,6 +15,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, auth::{check_key_valid, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -60,6 +61,7 @@ impl Operation for ListCannedPolicies { owner, false, vec![Action::AdminAction(AdminAction::ListUserPoliciesAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -118,6 +120,7 @@ impl Operation for AddCannedPolicy { owner, false, vec![Action::AdminAction(AdminAction::CreatePolicyAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -190,6 +193,7 @@ impl Operation for InfoCannedPolicy { owner, false, vec![Action::AdminAction(AdminAction::GetPolicyAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -247,6 +251,7 @@ impl Operation for RemoveCannedPolicy { owner, false, vec![Action::AdminAction(AdminAction::DeletePolicyAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -307,6 +312,7 @@ impl Operation for SetPolicyForUserOrGroup { owner, false, vec![Action::AdminAction(AdminAction::AttachPolicyAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/pools.rs b/rustfs/src/admin/handlers/pools.rs index c7e4017d..c68033d5 100644 --- a/rustfs/src/admin/handlers/pools.rs +++ b/rustfs/src/admin/handlers/pools.rs @@ -26,6 +26,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, error::ApiError, + server::RemoteAddr, }; pub struct ListPools {} @@ -53,6 +54,7 @@ impl Operation for ListPools { Action::AdminAction(AdminAction::ServerInfoAdminAction), Action::AdminAction(AdminAction::DecommissionAdminAction), ], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -119,6 +121,7 @@ impl Operation for StatusPool { Action::AdminAction(AdminAction::ServerInfoAdminAction), Action::AdminAction(AdminAction::DecommissionAdminAction), ], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -194,6 +197,7 @@ impl Operation for StartDecommission { owner, false, vec![Action::AdminAction(AdminAction::DecommissionAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -292,6 +296,7 @@ impl Operation for CancelDecommission { owner, false, vec![Action::AdminAction(AdminAction::DecommissionAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/rebalance.rs b/rustfs/src/admin/handlers/rebalance.rs index 736c8754..d1fb0a8d 100644 --- a/rustfs/src/admin/handlers/rebalance.rs +++ b/rustfs/src/admin/handlers/rebalance.rs @@ -15,6 +15,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -103,6 +104,7 @@ impl Operation for RebalanceStart { owner, false, vec![Action::AdminAction(AdminAction::RebalanceAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -180,6 +182,7 @@ impl Operation for RebalanceStatus { owner, false, vec![Action::AdminAction(AdminAction::RebalanceAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -297,6 +300,7 @@ impl Operation for RebalanceStop { owner, false, vec![Action::AdminAction(AdminAction::RebalanceAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index 76e59a90..34ad9538 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -14,6 +14,7 @@ use crate::admin::utils::has_space_be; use crate::auth::{constant_time_eq, get_condition_values, get_session_token}; +use crate::server::RemoteAddr; use crate::{admin::router::Operation, auth::check_key_valid}; use http::HeaderMap; use hyper::StatusCode; @@ -119,7 +120,13 @@ impl Operation for AddServiceAccount { groups: &cred.groups, action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction), bucket: "", - conditions: &get_condition_values(&req.headers, &cred, None, None), + conditions: &get_condition_values( + &req.headers, + &cred, + None, + None, + req.extensions.get::().map(|a| a.0), + ), is_owner: owner, object: "", claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), @@ -270,7 +277,13 @@ impl Operation for UpdateServiceAccount { groups: &cred.groups, action: Action::AdminAction(AdminAction::UpdateServiceAccountAdminAction), bucket: "", - conditions: &get_condition_values(&req.headers, &cred, None, None), + conditions: &get_condition_values( + &req.headers, + &cred, + None, + None, + req.extensions.get::().map(|a| a.0), + ), is_owner: owner, object: "", claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), @@ -363,7 +376,13 @@ impl Operation for InfoServiceAccount { groups: &cred.groups, action: Action::AdminAction(AdminAction::ListServiceAccountsAdminAction), bucket: "", - conditions: &get_condition_values(&req.headers, &cred, None, None), + conditions: &get_condition_values( + &req.headers, + &cred, + None, + None, + req.extensions.get::().map(|a| a.0), + ), is_owner: owner, object: "", claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), @@ -491,7 +510,13 @@ impl Operation for ListServiceAccount { groups: &cred.groups, action: Action::AdminAction(AdminAction::UpdateServiceAccountAdminAction), bucket: "", - conditions: &get_condition_values(&req.headers, &cred, None, None), + conditions: &get_condition_values( + &req.headers, + &cred, + None, + None, + req.extensions.get::().map(|a| a.0), + ), is_owner: owner, object: "", claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), @@ -589,7 +614,13 @@ impl Operation for DeleteServiceAccount { groups: &cred.groups, action: Action::AdminAction(AdminAction::RemoveServiceAccountAdminAction), bucket: "", - conditions: &get_condition_values(&req.headers, &cred, None, None), + conditions: &get_condition_values( + &req.headers, + &cred, + None, + None, + req.extensions.get::().map(|a| a.0), + ), is_owner: owner, object: "", claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), diff --git a/rustfs/src/admin/handlers/tier.rs b/rustfs/src/admin/handlers/tier.rs index 4fdd8954..cfcf5f5a 100644 --- a/rustfs/src/admin/handlers/tier.rs +++ b/rustfs/src/admin/handlers/tier.rs @@ -16,6 +16,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -90,7 +91,15 @@ impl Operation for AddTier { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::SetTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut input = req.input; let body = match input.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE).await { @@ -218,7 +227,15 @@ impl Operation for EditTier { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::SetTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut input = req.input; let body = match input.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE).await { @@ -293,7 +310,15 @@ impl Operation for ListTiers { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::ListTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut tier_config_mgr = GLOBAL_TierConfigMgr.read().await; let tiers = tier_config_mgr.list_tiers(); @@ -329,7 +354,15 @@ impl Operation for RemoveTier { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::SetTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut force: bool = false; let force_str = query.force.clone().unwrap_or_default(); @@ -392,7 +425,15 @@ impl Operation for VerifyTier { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::ListTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut tier_config_mgr = GLOBAL_TierConfigMgr.write().await; tier_config_mgr.verify(&query.tier.unwrap()).await; @@ -415,7 +456,15 @@ impl Operation for GetTierInfo { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::ListTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let query = { if let Some(query) = req.uri.query() { @@ -467,7 +516,15 @@ impl Operation for ClearTier { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::SetTierAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut force: bool = false; let force_str = query.force; diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 92c39d18..1f47639f 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -15,6 +15,7 @@ use crate::{ admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, auth::{check_key_valid, constant_time_eq, get_session_token}, + server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -124,6 +125,7 @@ impl Operation for AddUser { owner, deny_only, vec![Action::AdminAction(AdminAction::CreateUserAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -176,6 +178,7 @@ impl Operation for SetUserStatus { owner, false, vec![Action::AdminAction(AdminAction::EnableUserAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -220,6 +223,7 @@ impl Operation for ListUsers { owner, false, vec![Action::AdminAction(AdminAction::ListUsersAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -278,6 +282,7 @@ impl Operation for RemoveUser { owner, false, vec![Action::AdminAction(AdminAction::DeleteUserAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -377,6 +382,7 @@ impl Operation for GetUserInfo { owner, deny_only, vec![Action::AdminAction(AdminAction::GetUserAdminAction)], + req.extensions.get::().map(|a| a.0), ) .await?; @@ -426,8 +432,15 @@ impl Operation for ExportIam { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ExportIAMAction)]) - .await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::ExportIAMAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")); @@ -633,8 +646,15 @@ impl Operation for ImportIam { let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ExportIAMAction)]) - .await?; + validate_admin_request( + &req.headers, + &cred, + owner, + false, + vec![Action::AdminAction(AdminAction::ExportIAMAction)], + req.extensions.get::().map(|a| a.0), + ) + .await?; let mut input = req.input; let body = match input.store_all_limited(MAX_IAM_IMPORT_SIZE).await { diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index 97a329f0..03a39361 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -241,6 +241,7 @@ pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str /// * `cred` - User credentials /// * `version_id` - Optional version ID of the object /// * `region` - Optional region/location constraint +/// * `remote_addr` - Optional remote address of the connection /// /// # Returns /// * `HashMap>` - Condition values for policy evaluation @@ -250,6 +251,7 @@ pub fn get_condition_values( cred: &Credentials, version_id: Option<&str>, region: Option<&str>, + remote_addr: Option, ) -> HashMap> { let username = if cred.is_temp() || cred.is_service_account() { cred.parent_user.clone() @@ -297,12 +299,7 @@ pub fn get_condition_values( .unwrap_or(false); // Get remote address from header or use default - let remote_addr = header - .get("x-forwarded-for") - .and_then(|v| v.to_str().ok()) - .and_then(|s| s.split(',').next()) - .or_else(|| header.get("x-real-ip").and_then(|v| v.to_str().ok())) - .unwrap_or("127.0.0.1"); + let remote_addr_s = remote_addr.map(|a| a.ip().to_string()).unwrap_or_default(); let mut args = HashMap::new(); @@ -310,7 +307,7 @@ pub fn get_condition_values( args.insert("CurrentTime".to_owned(), vec![curr_time.format(&Rfc3339).unwrap_or_default()]); args.insert("EpochTime".to_owned(), vec![epoch_time.to_string()]); args.insert("SecureTransport".to_owned(), vec![is_tls.to_string()]); - args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, remote_addr)]); + args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, &remote_addr_s)]); // Add user agent and referer if let Some(user_agent) = header.get("user-agent") { @@ -848,7 +845,7 @@ mod tests { let cred = create_test_credentials(); let headers = HeaderMap::new(); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("userid"), Some(&vec!["test-access-key".to_string()])); assert_eq!(conditions.get("username"), Some(&vec!["test-access-key".to_string()])); @@ -860,7 +857,7 @@ mod tests { let cred = create_temp_credentials(); let headers = HeaderMap::new(); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("userid"), Some(&vec!["parent-user".to_string()])); assert_eq!(conditions.get("username"), Some(&vec!["parent-user".to_string()])); @@ -872,7 +869,7 @@ mod tests { let cred = create_service_account_credentials(); let headers = HeaderMap::new(); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("userid"), Some(&vec!["service-parent".to_string()])); assert_eq!(conditions.get("username"), Some(&vec!["service-parent".to_string()])); @@ -887,7 +884,7 @@ mod tests { headers.insert("x-amz-object-lock-mode", HeaderValue::from_static("GOVERNANCE")); headers.insert("x-amz-object-lock-retain-until-date", HeaderValue::from_static("2024-12-31T23:59:59Z")); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("object-lock-mode"), Some(&vec!["GOVERNANCE".to_string()])); assert_eq!( @@ -902,7 +899,7 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert("x-amz-signature-age", HeaderValue::from_static("300")); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("signatureAge"), Some(&vec!["300".to_string()])); // Verify the header is removed after processing @@ -919,7 +916,7 @@ mod tests { let headers = HeaderMap::new(); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!(conditions.get("username"), Some(&vec!["ldap-user".to_string()])); assert_eq!(conditions.get("groups"), Some(&vec!["group1".to_string(), "group2".to_string()])); @@ -932,7 +929,7 @@ mod tests { let headers = HeaderMap::new(); - let conditions = get_condition_values(&headers, &cred, None, None); + let conditions = get_condition_values(&headers, &cred, None, None, None); assert_eq!( conditions.get("groups"), @@ -1208,4 +1205,159 @@ mod tests { assert!(constant_time_eq(key1, key2)); assert!(!constant_time_eq(key1, key3)); } + + #[test] + fn test_get_condition_values_source_ip() { + let mut headers = HeaderMap::new(); + let cred = Credentials::default(); + + // Case 1: No headers, no remote addr -> empty string + let conditions = get_condition_values(&headers, &cred, None, None, None); + assert_eq!(conditions.get("SourceIp").unwrap()[0], ""); + + // Case 2: No headers, with remote addr -> remote addr + let remote_addr: std::net::SocketAddr = "192.168.0.10:12345".parse().unwrap(); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10"); + + // Case 3: X-Forwarded-For present -> XFF (takes precedence over remote_addr) + headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1")); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.1"); + + // Case 4: X-Forwarded-For with multiple IPs -> First IP + headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.3, 10.0.0.4")); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.3"); + + // Case 5: X-Real-IP present (XFF removed) -> X-Real-IP + headers.remove("x-forwarded-for"); + headers.insert("x-real-ip", HeaderValue::from_static("10.0.0.2")); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.2"); + + // Case 6: Forwarded header present (X-Real-IP removed) -> Forwarded + headers.remove("x-real-ip"); + headers.insert("forwarded", HeaderValue::from_static("for=10.0.0.5;proto=http")); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.5"); + + // Case 7: Forwarded header with quotes and multiple values + headers.insert("forwarded", HeaderValue::from_static("for=\"10.0.0.6\", for=10.0.0.7")); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.6"); + + // Case 8: IPv6 Remote Addr + let remote_addr_v6: std::net::SocketAddr = "[2001:db8::1]:8080".parse().unwrap(); + headers.clear(); + let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr_v6)); + assert_eq!(conditions.get("SourceIp").unwrap()[0], "2001:db8::1"); + } +} + +#[cfg(test)] +mod tests_policy { + use rustfs_policy::policy::action::{Action, S3Action}; + use rustfs_policy::policy::{Args, BucketPolicy, BucketPolicyArgs, Policy}; + use std::collections::HashMap; + + #[tokio::test] + async fn test_iam_policy_source_ip() { + let policy_json = r#"{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::mybucket/*"], + "Condition": { + "IpAddress": { + "aws:SourceIp": "192.168.1.0/24" + } + } + } + ] + }"#; + + let policy: Policy = serde_json::from_str(policy_json).expect("Failed to parse IAM policy"); + + // Case 1: Matching IP + let mut conditions = HashMap::new(); + conditions.insert("SourceIp".to_string(), vec!["192.168.1.10".to_string()]); + + let claims = HashMap::new(); + let args = Args { + account: "test-account", + groups: &None, + action: Action::S3Action(S3Action::GetObjectAction), + bucket: "mybucket", + conditions: &conditions, + is_owner: false, + object: "myobject", + claims: &claims, + deny_only: false, + }; + + assert!(policy.is_allowed(&args).await, "IAM Policy should allow matching IP"); + + // Case 2: Non-matching IP + let mut conditions_fail = HashMap::new(); + conditions_fail.insert("SourceIp".to_string(), vec!["10.0.0.1".to_string()]); + + let args_fail = Args { + conditions: &conditions_fail, + ..args + }; + + assert!(!policy.is_allowed(&args_fail).await, "IAM Policy should deny non-matching IP"); + } + + #[tokio::test] + async fn test_bucket_policy_source_ip() { + let policy_json = r#"{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": ["*"]}, + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::mybucket/*"], + "Condition": { + "IpAddress": { + "aws:SourceIp": "192.168.1.0/24" + } + } + } + ] + }"#; + + let policy: BucketPolicy = serde_json::from_str(policy_json).expect("Failed to parse Bucket policy"); + + // Case 1: Matching IP + let mut conditions = HashMap::new(); + conditions.insert("SourceIp".to_string(), vec!["192.168.1.10".to_string()]); + + let args = BucketPolicyArgs { + account: "test-account", + groups: &None, + action: Action::S3Action(S3Action::GetObjectAction), + bucket: "mybucket", + conditions: &conditions, + is_owner: false, + object: "myobject", + }; + + assert!(policy.is_allowed(&args).await, "Bucket Policy should allow matching IP"); + + // Case 2: Non-matching IP + let mut conditions_fail = HashMap::new(); + conditions_fail.insert("SourceIp".to_string(), vec!["10.0.0.1".to_string()]); + + let args_fail = BucketPolicyArgs { + conditions: &conditions_fail, + ..args + }; + + assert!(!policy.is_allowed(&args_fail).await, "Bucket Policy should deny non-matching IP"); + } } diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs index 10d5011a..efe4c56a 100644 --- a/rustfs/src/server/http.rs +++ b/rustfs/src/server/http.rs @@ -17,7 +17,7 @@ use super::compress::{CompressionConfig, CompressionPredicate}; use crate::admin; use crate::auth::IAMAuth; use crate::config; -use crate::server::{ReadinessGateLayer, ServiceState, ServiceStateManager, hybrid::hybrid, layer::RedirectLayer}; +use crate::server::{ReadinessGateLayer, RemoteAddr, ServiceState, ServiceStateManager, hybrid::hybrid, layer::RedirectLayer}; use crate::storage; use crate::storage::tonic_service::make_server; use bytes::Bytes; @@ -44,6 +44,7 @@ use tokio::net::{TcpListener, TcpStream}; use tokio_rustls::TlsAcceptor; use tonic::{Request, Status, metadata::MetadataValue}; use tower::ServiceBuilder; +use tower_http::add_extension::AddExtensionLayer; use tower_http::catch_panic::CatchPanicLayer; use tower_http::compression::CompressionLayer; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; @@ -528,9 +529,21 @@ fn process_connection( let rpc_service = NodeServiceServer::with_interceptor(make_server(), check_auth); let service = hybrid(s3_service, rpc_service); + let remote_addr = match socket.peer_addr() { + Ok(addr) => Some(RemoteAddr(addr)), + Err(e) => { + tracing::warn!( + error = %e, + "Failed to obtain peer address; policy evaluation may fall back to a default source IP" + ); + None + } + }; + let hybrid_service = ServiceBuilder::new() .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid)) .layer(CatchPanicLayer::new()) + .layer(AddExtensionLayer::new(remote_addr)) // CRITICAL: Insert ReadinessGateLayer before business logic // This stops requests from hitting IAMAuth or Storage if they are not ready. .layer(ReadinessGateLayer::new(readiness)) diff --git a/rustfs/src/server/mod.rs b/rustfs/src/server/mod.rs index 28af0093..c6f72d19 100644 --- a/rustfs/src/server/mod.rs +++ b/rustfs/src/server/mod.rs @@ -36,3 +36,6 @@ pub(crate) use service_state::ServiceState; pub(crate) use service_state::ServiceStateManager; pub(crate) use service_state::ShutdownSignal; pub(crate) use service_state::wait_for_shutdown; + +#[derive(Clone, Copy, Debug)] +pub struct RemoteAddr(pub std::net::SocketAddr); diff --git a/rustfs/src/storage/access.rs b/rustfs/src/storage/access.rs index 736f386e..2a0bd768 100644 --- a/rustfs/src/storage/access.rs +++ b/rustfs/src/storage/access.rs @@ -15,6 +15,7 @@ use super::ecfs::FS; use crate::auth::{check_key_valid, get_condition_values, get_session_token}; use crate::license::license_check; +use crate::server::RemoteAddr; use rustfs_ecstore::bucket::policy_sys::PolicySys; use rustfs_iam::error::Error as IamError; use rustfs_policy::policy::action::{Action, S3Action}; @@ -36,6 +37,7 @@ pub(crate) struct ReqInfo { /// Authorizes the request based on the action and credentials. pub async fn authorize_request(req: &mut S3Request, action: Action) -> S3Result<()> { + let remote_addr = req.extensions.get::().map(|a| a.0); let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); if let Some(cred) = &req_info.cred { @@ -48,7 +50,7 @@ pub async fn authorize_request(req: &mut S3Request, action: Action) -> S3R let default_claims = HashMap::new(); let claims = cred.claims.as_ref().unwrap_or(&default_claims); - let conditions = get_condition_values(&req.headers, cred, req_info.version_id.as_deref(), None); + let conditions = get_condition_values(&req.headers, cred, req_info.version_id.as_deref(), None, remote_addr); if action == Action::S3Action(S3Action::DeleteObjectAction) && req_info.version_id.is_some() @@ -109,6 +111,7 @@ pub async fn authorize_request(req: &mut S3Request, action: Action) -> S3R &rustfs_credentials::Credentials::default(), req_info.version_id.as_deref(), req.region.as_deref(), + remote_addr, ); if action != Action::S3Action(S3Action::ListAllMyBucketsAction) { diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index d9675ff4..a5e9dc3e 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -17,6 +17,7 @@ use crate::config::workload_profiles::{ RustFSBufferConfig, WorkloadProfile, get_global_buffer_config, is_buffer_profile_enabled, }; use crate::error::ApiError; +use crate::server::RemoteAddr; use crate::storage::concurrency::{ CachedGetObject, ConcurrencyManager, GetObjectGuard, get_concurrency_aware_buffer_size, get_concurrency_manager, }; @@ -4689,7 +4690,8 @@ impl S3 for FS { .await .map_err(ApiError::from)?; - let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None); + let remote_addr = req.extensions.get::().map(|a| a.0); + let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None, remote_addr); let read_only = PolicySys::is_allowed(&BucketPolicyArgs { bucket: &bucket,