diff --git a/rustfs/src/admin/auth.rs b/rustfs/src/admin/auth.rs new file mode 100644 index 000000000..7cc139aea --- /dev/null +++ b/rustfs/src/admin/auth.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use http::HeaderMap; +use rustfs_iam::store::object::ObjectStore; +use rustfs_iam::sys::IamSys; +use rustfs_policy::auth; +use rustfs_policy::policy::Args; +use rustfs_policy::policy::action::Action; +use s3s::S3Result; +use s3s::s3_error; + +use crate::auth::get_condition_values; + +pub async fn validate_admin_request( + headers: &HeaderMap, + cred: &auth::Credentials, + is_owner: bool, + deny_only: bool, + actions: Vec, +) -> 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 { + Ok(_) => return Ok(()), + Err(_) => { + continue; + } + } + } + + Err(s3_error!(AccessDenied, "Access Denied")) +} + +async fn check_admin_request_auth( + iam_store: Arc>, + headers: &HeaderMap, + cred: &auth::Credentials, + is_owner: bool, + deny_only: bool, + action: Action, +) -> S3Result<()> { + let conditions = get_condition_values(headers, cred); + + if !iam_store + .is_allowed(&Args { + account: &cred.access_key, + groups: &cred.groups, + action, + conditions: &conditions, + is_owner, + claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), + deny_only, + bucket: "", + object: "", + }) + .await + { + return Err(s3_error!(AccessDenied, "Access Denied")); + } + + Ok(()) +} diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index c00e1db02..0ac9cf3e7 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -13,6 +13,7 @@ // limitations under the License. use super::router::Operation; +use crate::admin::auth::validate_admin_request; use crate::auth::check_key_valid; use crate::auth::get_condition_values; use crate::auth::get_session_token; @@ -45,6 +46,7 @@ use rustfs_madmin::utils::parse_duration; use rustfs_policy::policy::Args; use rustfs_policy::policy::BucketPolicy; use rustfs_policy::policy::action::Action; +use rustfs_policy::policy::action::AdminAction; use rustfs_policy::policy::action::S3Action; use rustfs_policy::policy::default::DEFAULT_POLICIES; use rustfs_utils::path::path_join; @@ -300,7 +302,23 @@ pub struct ServerInfoHandler {} #[async_trait::async_trait] impl Operation for ServerInfoHandler { - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ServerInfoAdminAction)], + ) + .await?; + let info = get_server_info(true).await; let data = serde_json::to_vec(&info) @@ -328,9 +346,25 @@ pub struct StorageInfoHandler {} #[async_trait::async_trait] impl Operation for StorageInfoHandler { - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle StorageInfoHandler"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::StorageInfoAdminAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string())); }; @@ -353,9 +387,25 @@ pub struct DataUsageInfoHandler {} #[async_trait::async_trait] impl Operation for DataUsageInfoHandler { - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle DataUsageInfoHandler"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::DataUsageInfoAdminAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string())); }; diff --git a/rustfs/src/admin/handlers/bucket_meta.rs b/rustfs/src/admin/handlers/bucket_meta.rs index d05fc570d..f8c4bfd73 100644 --- a/rustfs/src/admin/handlers/bucket_meta.rs +++ b/rustfs/src/admin/handlers/bucket_meta.rs @@ -18,7 +18,7 @@ use std::{ }; use crate::{ - admin::router::Operation, + admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, }; @@ -44,7 +44,10 @@ use rustfs_ecstore::{ bucket::utils::{deserialize, serialize}, store_api::MakeBucketOptions, }; -use rustfs_policy::policy::BucketPolicy; +use rustfs_policy::policy::{ + BucketPolicy, + action::{Action, AdminAction}, +}; use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf}; use s3s::{ Body, S3Request, S3Response, S3Result, @@ -85,9 +88,18 @@ impl Operation for ExportBucketMetadata { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (_cred, _owner) = + 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::ExportBucketMetadataAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(s3_error!(InvalidRequest, "object store not init")); }; @@ -369,9 +381,18 @@ impl Operation for ImportBucketMetadata { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (_cred, _owner) = + 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::ImportBucketMetadataAction)], + ) + .await?; + let mut input = req.input; let body = match input.store_all_unlimited().await { Ok(b) => b, diff --git a/rustfs/src/admin/handlers/group.rs b/rustfs/src/admin/handlers/group.rs index b2c052fa5..8a86b7a38 100644 --- a/rustfs/src/admin/handlers/group.rs +++ b/rustfs/src/admin/handlers/group.rs @@ -17,6 +17,7 @@ use matchit::Params; use rustfs_ecstore::global::get_global_action_cred; use rustfs_iam::error::{is_err_no_such_group, is_err_no_such_user}; use rustfs_madmin::GroupAddRemove; +use rustfs_policy::policy::action::{Action, AdminAction}; use s3s::{ Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::{CONTENT_LENGTH, CONTENT_TYPE}, @@ -26,7 +27,10 @@ use serde::Deserialize; use serde_urlencoded::from_bytes; use tracing::warn; -use crate::admin::{router::Operation, utils::has_space_be}; +use crate::{ + admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, + auth::{check_key_valid, get_session_token}, +}; #[derive(Debug, Deserialize, Default)] pub struct GroupQuery { @@ -37,9 +41,25 @@ pub struct GroupQuery { pub struct ListGroups {} #[async_trait::async_trait] impl Operation for ListGroups { - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle ListGroups"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ListGroupsAdminAction)], + ) + .await?; + let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")) }; let groups = iam_store.list_groups().await.map_err(|e| { @@ -62,6 +82,22 @@ impl Operation for GetGroup { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle GetGroup"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::GetGroupAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: GroupQuery = @@ -93,6 +129,22 @@ impl Operation for SetGroupStatus { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle SetGroupStatus"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::EnableGroupAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: GroupQuery = @@ -144,6 +196,22 @@ impl Operation for UpdateGroupMembers { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle UpdateGroupMembers"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::AddUserToGroupAdminAction)], + ) + .await?; + let mut input = req.input; let body = match input.store_all_unlimited().await { Ok(b) => b, diff --git a/rustfs/src/admin/handlers/policies.rs b/rustfs/src/admin/handlers/policies.rs index 2325cd03f..a65e7ee5e 100644 --- a/rustfs/src/admin/handlers/policies.rs +++ b/rustfs/src/admin/handlers/policies.rs @@ -12,13 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::admin::{router::Operation, utils::has_space_be}; +use crate::{ + admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, + auth::{check_key_valid, get_session_token}, +}; use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_ecstore::global::get_global_action_cred; use rustfs_iam::error::is_err_no_such_user; use rustfs_iam::store::MappedPolicy; -use rustfs_policy::policy::Policy; +use rustfs_policy::policy::{ + Policy, + action::{Action, AdminAction}, +}; use s3s::{ Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::{CONTENT_LENGTH, CONTENT_TYPE}, @@ -40,6 +46,22 @@ impl Operation for ListCannedPolicies { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle ListCannedPolicies"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ListUserPoliciesAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: BucketQuery = @@ -82,6 +104,22 @@ impl Operation for AddCannedPolicy { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle AddCannedPolicy"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::CreatePolicyAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: PolicyNameQuery = @@ -138,6 +176,22 @@ impl Operation for InfoCannedPolicy { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle InfoCannedPolicy"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::GetPolicyAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: PolicyNameQuery = @@ -179,6 +233,22 @@ impl Operation for RemoveCannedPolicy { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle RemoveCannedPolicy"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::DeletePolicyAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: PolicyNameQuery = @@ -223,6 +293,22 @@ impl Operation for SetPolicyForUserOrGroup { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle SetPolicyForUserOrGroup"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::AttachPolicyAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: SetPolicyForUserOrGroupQuery = @@ -233,10 +319,6 @@ impl Operation for SetPolicyForUserOrGroup { } }; - if query.policy_name.is_empty() { - return Err(s3_error!(InvalidArgument, "policy name is empty")); - } - if query.user_or_group.is_empty() { return Err(s3_error!(InvalidArgument, "user or group is empty")); } diff --git a/rustfs/src/admin/handlers/pools.rs b/rustfs/src/admin/handlers/pools.rs index 6de19960b..a1b60aab0 100644 --- a/rustfs/src/admin/handlers/pools.rs +++ b/rustfs/src/admin/handlers/pools.rs @@ -15,13 +15,18 @@ use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_ecstore::{GLOBAL_Endpoints, new_object_layer_fn}; +use rustfs_policy::policy::action::{Action, AdminAction}; use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error}; use serde::Deserialize; use serde_urlencoded::from_bytes; use tokio::sync::broadcast; use tracing::warn; -use crate::{admin::router::Operation, error::ApiError}; +use crate::{ + admin::{auth::validate_admin_request, router::Operation}, + auth::{check_key_valid, get_session_token}, + error::ApiError, +}; pub struct ListPools {} @@ -29,9 +34,28 @@ pub struct ListPools {} impl Operation for ListPools { // GET //pools/list #[tracing::instrument(skip_all)] - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle ListPools"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ServerInfoAdminAction), + Action::AdminAction(AdminAction::DecommissionAdminAction), + ], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string())); }; @@ -79,6 +103,25 @@ impl Operation for StatusPool { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle StatusPool"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ServerInfoAdminAction), + Action::AdminAction(AdminAction::DecommissionAdminAction), + ], + ) + .await?; + let Some(endpoints) = GLOBAL_Endpoints.get() else { return Err(s3_error!(NotImplemented)); }; @@ -138,6 +181,22 @@ impl Operation for StartDecommission { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle StartDecommission"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::DecommissionAdminAction)], + ) + .await?; + let Some(endpoints) = GLOBAL_Endpoints.get() else { return Err(s3_error!(NotImplemented)); }; @@ -221,6 +280,22 @@ impl Operation for CancelDecommission { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle CancelDecommission"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::DecommissionAdminAction)], + ) + .await?; + let Some(endpoints) = GLOBAL_Endpoints.get() else { return Err(s3_error!(NotImplemented)); }; diff --git a/rustfs/src/admin/handlers/rebalance.rs b/rustfs/src/admin/handlers/rebalance.rs index b3105fa5b..ca5b60f57 100644 --- a/rustfs/src/admin/handlers/rebalance.rs +++ b/rustfs/src/admin/handlers/rebalance.rs @@ -22,6 +22,7 @@ use rustfs_ecstore::{ rebalance::{DiskStat, RebalSaveOpt}, store_api::BucketOptions, }; +use rustfs_policy::policy::action::{Action, AdminAction}; use s3s::{ Body, S3Request, S3Response, S3Result, header::{CONTENT_LENGTH, CONTENT_TYPE}, @@ -32,7 +33,10 @@ use std::time::Duration; use time::OffsetDateTime; use tracing::warn; -use crate::admin::router::Operation; +use crate::{ + admin::{auth::validate_admin_request, router::Operation}, + auth::{check_key_valid, get_session_token}, +}; use rustfs_ecstore::rebalance::RebalanceMeta; #[derive(Debug, Clone, Deserialize, Serialize)] @@ -84,9 +88,25 @@ pub struct RebalanceStart {} #[async_trait::async_trait] impl Operation for RebalanceStart { #[tracing::instrument(skip_all)] - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle RebalanceStart"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::RebalanceAdminAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(s3_error!(InternalError, "Not init")); }; @@ -145,9 +165,25 @@ pub struct RebalanceStatus {} #[async_trait::async_trait] impl Operation for RebalanceStatus { #[tracing::instrument(skip_all)] - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle RebalanceStatus"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::RebalanceAdminAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(s3_error!(InternalError, "Not init")); }; @@ -246,9 +282,25 @@ pub struct RebalanceStop {} #[async_trait::async_trait] impl Operation for RebalanceStop { #[tracing::instrument(skip_all)] - async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle RebalanceStop"); + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::RebalanceAdminAction)], + ) + .await?; + let Some(store) = new_object_layer_fn() else { return Err(s3_error!(InternalError, "Not init")); }; diff --git a/rustfs/src/admin/handlers/tier.rs b/rustfs/src/admin/handlers/tier.rs index e9648c3ec..00fd61e66 100644 --- a/rustfs/src/admin/handlers/tier.rs +++ b/rustfs/src/admin/handlers/tier.rs @@ -16,6 +16,7 @@ use http::{HeaderMap, StatusCode}; //use iam::get_global_action_cred; use matchit::Params; +use rustfs_policy::policy::action::{Action, AdminAction}; use s3s::{ Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::{CONTENT_LENGTH, CONTENT_TYPE}, @@ -26,7 +27,7 @@ use time::OffsetDateTime; use tracing::{debug, warn}; use crate::{ - admin::router::Operation, + admin::{auth::validate_admin_request, router::Operation}, auth::{check_key_valid, get_session_token}, }; @@ -88,9 +89,11 @@ impl Operation for AddTier { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = + 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?; + let mut input = req.input; let body = match input.store_all_unlimited().await { Ok(b) => b, @@ -196,9 +199,11 @@ impl Operation for EditTier { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = + 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?; + let mut input = req.input; let body = match input.store_all_unlimited().await { Ok(b) => b, @@ -265,6 +270,15 @@ impl Operation for ListTiers { } }; + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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?; + let mut tier_config_mgr = GLOBAL_TierConfigMgr.read().await; let tiers = tier_config_mgr.list_tiers(); @@ -296,11 +310,10 @@ impl Operation for RemoveTier { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = + let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - //let sys_cred = get_global_action_cred() - // .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?; + validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?; let mut force: bool = false; let force_str = query.force.clone().unwrap_or_default(); @@ -360,11 +373,10 @@ impl Operation for VerifyTier { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = + let (cred, owner) = check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - //let sys_cred = get_global_action_cred() - // .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?; + validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?; let mut tier_config_mgr = GLOBAL_TierConfigMgr.write().await; tier_config_mgr.verify(&query.tier.unwrap()).await; @@ -380,6 +392,15 @@ pub struct GetTierInfo {} #[async_trait::async_trait] impl Operation for GetTierInfo { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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?; + let query = { if let Some(query) = req.uri.query() { let input: AddTierQuery = @@ -423,12 +444,14 @@ impl Operation for ClearTier { } }; - //let Some(input_cred) = req.credentials else { - // return Err(s3_error!(InvalidRequest, "get cred failed")); - //}; + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; - //let (cred, _owner) = - // check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; + 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?; 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 a1bd02146..5f26f31b0 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -13,8 +13,8 @@ // limitations under the License. use crate::{ - admin::{router::Operation, utils::has_space_be}, - auth::{check_key_valid, get_condition_values, get_session_token}, + admin::{auth::validate_admin_request, router::Operation, utils::has_space_be}, + auth::{check_key_valid, get_session_token}, }; use http::{HeaderMap, StatusCode}; use matchit::Params; @@ -27,10 +27,7 @@ use rustfs_madmin::{ AccountStatus, AddOrUpdateUserReq, IAMEntities, IAMErrEntities, IAMErrEntity, IAMErrPolicyEntity, user::{ImportIAMResult, SRSessionPolicy, SRSvcAccCreate}, }; -use rustfs_policy::policy::{ - Args, - action::{Action, AdminAction}, -}; +use rustfs_policy::policy::action::{Action, AdminAction}; use rustfs_utils::path::path_join_buf; use s3s::{ @@ -121,23 +118,14 @@ impl Operation for AddUser { } let deny_only = ak == cred.access_key; - let conditions = get_condition_values(&req.headers, &cred); - if !iam_store - .is_allowed(&Args { - account: &cred.access_key, - groups: &cred.groups, - action: Action::AdminAction(AdminAction::CreateUserAdminAction), - bucket: "", - conditions: &conditions, - is_owner: owner, - object: "", - claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), - deny_only, - }) - .await - { - return Err(s3_error!(AccessDenied, "access denied")); - } + validate_admin_request( + &req.headers, + &cred, + owner, + deny_only, + vec![Action::AdminAction(AdminAction::CreateUserAdminAction)], + ) + .await?; iam_store .create_user(ak, &args) @@ -179,6 +167,18 @@ impl Operation for SetUserStatus { return Err(s3_error!(InvalidArgument, "can't change status of self")); } + 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::EnableUserAdminAction)], + ) + .await?; + let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default()) .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?; @@ -207,6 +207,22 @@ pub struct ListUsers {} #[async_trait::async_trait] impl Operation for ListUsers { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::ListUsersAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: BucketQuery = @@ -238,13 +254,6 @@ impl Operation for ListUsers { let data = serde_json::to_vec(&users) .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {e}")))?; - // let Some(input_cred) = req.credentials else { - // return Err(s3_error!(InvalidRequest, "get cred failed")); - // }; - - // let body = encrypt_data(input_cred.secret_key.expose().as_bytes(), &data) - // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("encrypt_data err {}", e)))?; - let mut header = HeaderMap::new(); header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); @@ -256,6 +265,22 @@ pub struct RemoveUser {} #[async_trait::async_trait] impl Operation for RemoveUser { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { + let Some(input_cred) = req.credentials else { + return Err(s3_error!(InvalidRequest, "get cred failed")); + }; + + 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::DeleteUserAdminAction)], + ) + .await?; + let query = { if let Some(query) = req.uri.query() { let input: AddUserQuery = @@ -272,6 +297,13 @@ impl Operation for RemoveUser { return Err(s3_error!(InvalidArgument, "access key is empty")); } + let sys_cred = get_global_action_cred() + .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?; + + if ak == sys_cred.access_key || ak == cred.access_key || cred.parent_user == ak { + return Err(s3_error!(InvalidArgument, "can't remove self")); + } + let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")); }; @@ -285,22 +317,12 @@ impl Operation for RemoveUser { return Err(s3_error!(InvalidArgument, "can't remove temp user")); } - let Some(input_cred) = req.credentials else { - return Err(s3_error!(InvalidRequest, "get cred failed")); - }; - - let (cred, _owner) = - check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; - - let sys_cred = get_global_action_cred() - .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?; - - if ak == sys_cred.access_key || ak == cred.access_key { - warn!( - "can't remove self or system access key {}, {}, {}", - ak, sys_cred.access_key, cred.access_key - ); - return Err(s3_error!(InvalidArgument, "can't remove self")); + let (is_service_account, _) = iam_store + .is_service_account(ak) + .await + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_service_account err {e}")))?; + if is_service_account { + return Err(s3_error!(InvalidArgument, "can't remove service account")); } iam_store @@ -308,6 +330,8 @@ impl Operation for RemoveUser { .await .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {e}")))?; + // TODO: IAMChangeHook + let mut header = HeaderMap::new(); header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); header.insert(CONTENT_LENGTH, "0".parse().unwrap()); @@ -347,23 +371,14 @@ impl Operation for GetUserInfo { check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?; let deny_only = ak == cred.access_key; - let conditions = get_condition_values(&req.headers, &cred); - if !iam_store - .is_allowed(&Args { - account: &cred.access_key, - groups: &cred.groups, - action: Action::AdminAction(AdminAction::GetUserAdminAction), - bucket: "", - conditions: &conditions, - is_owner: owner, - object: "", - claims: cred.claims.as_ref().unwrap_or(&HashMap::new()), - deny_only, - }) - .await - { - return Err(s3_error!(AccessDenied, "access denied")); - } + validate_admin_request( + &req.headers, + &cred, + owner, + deny_only, + vec![Action::AdminAction(AdminAction::GetUserAdminAction)], + ) + .await?; let info = iam_store .get_user_info(ak) @@ -408,9 +423,12 @@ impl Operation for ExportIam { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (_cred, _owner) = + 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?; + let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")); }; @@ -612,9 +630,12 @@ impl Operation for ImportIam { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (_cred, _owner) = + 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?; + let mut input = req.input; let body = match input.store_all_unlimited().await { Ok(b) => b, diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs index 2f1c87afb..9ce6a2f30 100644 --- a/rustfs/src/admin/mod.rs +++ b/rustfs/src/admin/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod auth; pub mod console; pub mod handlers; pub mod router;