diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index dc9bfba0..88a551f7 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -16,17 +16,11 @@ use ecstore::new_object_layer_fn; use ecstore::peer::is_reserved_or_invalid_bucket; use ecstore::store::is_valid_object_prefix; use ecstore::store_api::StorageAPI; -use ecstore::utils::crypto::base64_encode; use ecstore::utils::path::path_join; use ecstore::GLOBAL_Endpoints; use futures::{Stream, StreamExt}; use http::{HeaderMap, Uri}; use hyper::StatusCode; -use iam::auth::get_claims_from_token_with_secret; -use iam::error::Error as IamError; -use iam::policy::Policy; -use iam::sys::SESSION_POLICY_NAME; -use iam::{auth, get_global_action_cred}; use madmin::metrics::RealtimeMetrics; use madmin::utils::parse_duration; use matchit::Params; @@ -35,7 +29,6 @@ use s3s::stream::{ByteStream, DynByteStream}; use s3s::{s3_error, Body, S3Error, S3Request, S3Response, S3Result}; use s3s::{S3ErrorCode, StdError}; use serde::{Deserialize, Serialize}; -use serde_json::Value; use serde_urlencoded::from_bytes; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; @@ -56,139 +49,6 @@ pub mod sts; pub mod trace; pub mod user; -// check_key_valid get auth.cred -pub async fn check_key_valid(security_token: Option, ak: &str) -> S3Result<(auth::Credentials, bool)> { - let Some(mut cred) = get_global_action_cred() else { - return Err(S3Error::with_message( - S3ErrorCode::InternalError, - format!("get_global_action_cred {:?}", IamError::IamSysNotInitialized), - )); - }; - - let sys_cred = cred.clone(); - - // warn!("check_key_valid cred {:?}, as: {:?}", &cred, &ak); - if cred.access_key != ak { - let Ok(iam_store) = iam::get() else { - return Err(S3Error::with_message( - S3ErrorCode::InternalError, - format!("check_key_valid {:?}", IamError::IamSysNotInitialized), - )); - }; - - let (u, ok) = iam_store - .check_key(ak) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed1 {}", e)))?; - - if !ok { - if let Some(u) = u { - if u.credentials.status == "off" { - return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); - } - } - - return Err(s3_error!(InvalidRequest, "check key failed")); - } - - let Some(u) = u else { - return Err(s3_error!(InvalidRequest, "check key failed")); - }; - - cred = u.credentials; - } - - // warn!("check_key_valid cred {:?}", &cred); - // warn!("check_key_valid security_token {:?}", &security_token); - - let claims = check_claims_from_token(&security_token.unwrap_or_default(), &cred) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?; - - cred.claims = if !claims.is_empty() { Some(claims) } else { None }; - - let mut owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; - - // permitRootAccess - if let Some(claims) = &cred.claims { - if claims.contains_key(SESSION_POLICY_NAME) { - owner = false - } - } - - Ok((cred, owner)) -} - -pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Result> { - if !token.is_empty() && cred.access_key.is_empty() { - return Err(s3_error!(InvalidRequest, "no access key")); - } - - if token.is_empty() && cred.is_temp() && !cred.is_service_account() { - return Err(s3_error!(InvalidRequest, "invalid token")); - } - - if !token.is_empty() && !cred.is_temp() { - return Err(s3_error!(InvalidRequest, "invalid token")); - } - - if !cred.is_service_account() && cred.is_temp() && token != cred.session_token { - return Err(s3_error!(InvalidRequest, "invalid token")); - } - - if cred.is_temp() && cred.is_expired() { - return Err(s3_error!(InvalidRequest, "invalid access key is temp and expired")); - } - - let Some(sys_cred) = get_global_action_cred() else { - return Err(s3_error!(InternalError, "action cred not init")); - }; - - let mut secret = sys_cred.secret_key; - - // TODO: REPLICATION - - let mut token = token; - - if cred.is_service_account() { - token = cred.session_token.as_str(); - secret = cred.secret_key.clone(); - } - - if !token.is_empty() { - let claims: HashMap = - get_claims_from_token_with_secret(token, &secret).map_err(|_e| s3_error!(InvalidRequest, "invalid token"))?; - return Ok(claims); - } - - Ok(HashMap::new()) -} - -pub fn get_session_token(hds: &HeaderMap) -> Option { - hds.get("x-amz-security-token") - .map(|v| v.to_str().unwrap_or_default().to_string()) -} - -pub fn populate_session_policy(claims: &mut HashMap, policy: &str) -> S3Result<()> { - if !policy.is_empty() { - let session_policy = Policy::parse_config(policy.as_bytes()) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("parse policy err {}", e)))?; - if session_policy.version.is_empty() { - return Err(s3_error!(InvalidRequest, "invalid policy")); - } - - let policy_buf = serde_json::to_vec(&session_policy) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal policy err {}", e)))?; - - if policy_buf.len() > 2048 { - return Err(s3_error!(InvalidRequest, "policy too large")); - } - - claims.insert(SESSION_POLICY_NAME.to_string(), serde_json::Value::String(base64_encode(&policy_buf))); - } - - Ok(()) -} - #[derive(Debug, Serialize, Default)] #[serde(rename_all = "PascalCase", default)] pub struct AccountInfo { diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index fbc2e73d..021e048e 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -1,5 +1,5 @@ -use crate::admin::{handlers::check_key_valid, utils::has_space_be}; -use crate::admin::{handlers::get_session_token, router::Operation}; +use crate::admin::utils::has_space_be; +use crate::{admin::router::Operation, auth::check_key_valid}; use http::HeaderMap; use hyper::StatusCode; use iam::{ @@ -29,7 +29,7 @@ impl Operation for AddServiceAccount { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?; + let (cred, _owner) = check_key_valid(&req.headers, &req_cred.access_key).await?; let mut input = req.input; let body = match input.store_all_unlimited().await { @@ -172,12 +172,6 @@ impl Operation for UpdateServiceAccount { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle UpdateServiceAccount"); - // let Some(req_cred) = req.credentials else { - // return Err(s3_error!(InvalidRequest, "get cred failed")); - // }; - - // let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?; - let query = { if let Some(query) = req.uri.query() { let input: AccessKeyQuery = @@ -363,12 +357,10 @@ impl Operation for ListServiceAccount { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key) - .await - .map_err(|e| { - debug!("check key failed: {e:?}"); - s3_error!(InternalError, "check key failed") - })?; + let (cred, _owner) = check_key_valid(&req.headers, &input_cred.access_key).await.map_err(|e| { + debug!("check key failed: {e:?}"); + s3_error!(InternalError, "check key failed") + })?; let target_account = if let Some(user) = query.user { if user != input_cred.access_key { @@ -423,12 +415,10 @@ impl Operation for DeleteServiceAccount { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (_cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key) - .await - .map_err(|e| { - debug!("check key failed: {e:?}"); - s3_error!(InternalError, "check key failed") - })?; + let (_cred, _owner) = check_key_valid(&req.headers, &input_cred.access_key).await.map_err(|e| { + debug!("check key failed: {e:?}"); + s3_error!(InternalError, "check key failed") + })?; let query = { if let Some(query) = req.uri.query() { diff --git a/rustfs/src/admin/handlers/sts.rs b/rustfs/src/admin/handlers/sts.rs index adb31e7d..cdd54286 100644 --- a/rustfs/src/admin/handlers/sts.rs +++ b/rustfs/src/admin/handlers/sts.rs @@ -1,16 +1,19 @@ -use crate::admin::{ - handlers::{check_key_valid, get_session_token, populate_session_policy}, - router::Operation, +use std::collections::HashMap; + +use crate::{ + admin::router::Operation, + auth::{check_key_valid, get_session_token}, }; -use ecstore::utils::xml; +use ecstore::utils::{crypto::base64_encode, xml}; use http::StatusCode; -use iam::{auth::get_new_credentials_with_metadata, manager::get_token_signing_key}; +use iam::{auth::get_new_credentials_with_metadata, manager::get_token_signing_key, policy::Policy, sys::SESSION_POLICY_NAME}; use matchit::Params; use s3s::{ dto::{AssumeRoleOutput, Credentials, Timestamp}, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, }; use serde::Deserialize; +use serde_json::Value; use serde_urlencoded::from_bytes; use time::{Duration, OffsetDateTime}; use tracing::{info, warn}; @@ -43,7 +46,7 @@ impl Operation for AssumeRoleHandle { return Err(s3_error!(InvalidRequest, "AccessDenied1")); } - let (cred, _owner) = check_key_valid(session_token, &user.access_key).await?; + let (cred, _owner) = check_key_valid(&req.headers, &user.access_key).await?; // // TODO: 判断权限, 不允许sts访问 if cred.is_temp() || cred.is_service_account() { @@ -137,3 +140,24 @@ impl Operation for AssumeRoleHandle { Ok(S3Response::new((StatusCode::OK, Body::from(output)))) } } + +pub fn populate_session_policy(claims: &mut HashMap, policy: &str) -> S3Result<()> { + if !policy.is_empty() { + let session_policy = Policy::parse_config(policy.as_bytes()) + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("parse policy err {}", e)))?; + if session_policy.version.is_empty() { + return Err(s3_error!(InvalidRequest, "invalid policy")); + } + + let policy_buf = serde_json::to_vec(&session_policy) + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal policy err {}", e)))?; + + if policy_buf.len() > 2048 { + return Err(s3_error!(InvalidRequest, "policy too large")); + } + + claims.insert(SESSION_POLICY_NAME.to_string(), serde_json::Value::String(base64_encode(&policy_buf))); + } + + Ok(()) +} diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 6c960987..ae55f97f 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -9,10 +9,9 @@ use serde::Deserialize; use serde_urlencoded::from_bytes; use tracing::warn; -use crate::admin::{ - handlers::{check_key_valid, get_session_token}, - router::Operation, - utils::has_space_be, +use crate::{ + admin::{router::Operation, utils::has_space_be}, + auth::check_key_valid, }; #[derive(Debug, Deserialize, Default)] @@ -40,7 +39,7 @@ impl Operation for AddUser { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key).await?; + let (cred, _owner) = check_key_valid(&req.headers, &input_cred.access_key).await?; let ak = query.access_key.as_deref().unwrap_or_default(); @@ -247,7 +246,7 @@ impl Operation for RemoveUser { return Err(s3_error!(InvalidRequest, "get cred failed")); }; - let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key).await?; + let (cred, _owner) = check_key_valid(&req.headers, &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"))?; diff --git a/rustfs/src/admin/router.rs b/rustfs/src/admin/router.rs index 18193d09..46a6bb9a 100644 --- a/rustfs/src/admin/router.rs +++ b/rustfs/src/admin/router.rs @@ -78,6 +78,14 @@ where return Err(s3_error!(NotImplemented)); } + + // check_access before call + async fn check_access(&self, req: &mut S3Request) -> S3Result<()> { + match req.credentials { + Some(_) => Ok(()), + None => Err(s3_error!(AccessDenied, "Signature is required")), + } + } } #[async_trait::async_trait] diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index 8106ff80..c2e215a6 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -1,8 +1,19 @@ +use std::collections::HashMap; + +use http::HeaderMap; +use iam::auth; +use iam::auth::get_claims_from_token_with_secret; +use iam::error::Error as IamError; +use iam::get_global_action_cred; +use iam::sys::SESSION_POLICY_NAME; use s3s::auth::S3Auth; use s3s::auth::SecretKey; use s3s::auth::SimpleAuth; use s3s::s3_error; +use s3s::S3Error; +use s3s::S3ErrorCode; use s3s::S3Result; +use serde_json::Value; pub struct IAMAuth { simple_auth: SimpleAuth, @@ -35,3 +46,252 @@ impl S3Auth for IAMAuth { Err(s3_error!(UnauthorizedAccess, "Your account is not signed up2")) } } + +// check_key_valid checks the key is valid or not. return the user's credentials and if the user is the owner. +pub async fn check_key_valid(header: &HeaderMap, access_key: &str) -> S3Result<(auth::Credentials, bool)> { + let Some(mut cred) = get_global_action_cred() else { + return Err(S3Error::with_message( + S3ErrorCode::InternalError, + format!("get_global_action_cred {:?}", IamError::IamSysNotInitialized), + )); + }; + + let sys_cred = cred.clone(); + + if cred.access_key != access_key { + let Ok(iam_store) = iam::get() else { + return Err(S3Error::with_message( + S3ErrorCode::InternalError, + format!("check_key_valid {:?}", IamError::IamSysNotInitialized), + )); + }; + + let (u, ok) = iam_store + .check_key(access_key) + .await + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed1 {}", e)))?; + + if !ok { + if let Some(u) = u { + if u.credentials.status == "off" { + return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); + } + } + + return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); + } + + let Some(u) = u else { + return Err(s3_error!(InvalidRequest, "check key failed")); + }; + + cred = u.credentials; + } + + let claims = check_claims_from_token(header, &cred) + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?; + + cred.claims = if !claims.is_empty() { Some(claims) } else { None }; + + let mut owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; + + // permitRootAccess + if let Some(claims) = &cred.claims { + if claims.contains_key(SESSION_POLICY_NAME) { + owner = false + } + } + + Ok((cred, owner)) +} + +pub fn check_claims_from_token(header: &HeaderMap, cred: &auth::Credentials) -> S3Result> { + let token = get_session_token(header).unwrap_or_default(); + + if !token.is_empty() && cred.access_key.is_empty() { + return Err(s3_error!(InvalidRequest, "no access key")); + } + + if token.is_empty() && cred.is_temp() && !cred.is_service_account() { + return Err(s3_error!(InvalidRequest, "invalid token")); + } + + if !token.is_empty() && !cred.is_temp() { + return Err(s3_error!(InvalidRequest, "invalid token")); + } + + if !cred.is_service_account() && cred.is_temp() && token != cred.session_token { + return Err(s3_error!(InvalidRequest, "invalid token")); + } + + if cred.is_temp() && cred.is_expired() { + return Err(s3_error!(InvalidRequest, "invalid access key is temp and expired")); + } + + let Some(sys_cred) = get_global_action_cred() else { + return Err(s3_error!(InternalError, "action cred not init")); + }; + + // TODO: REPLICATION + + let (token, secret) = if cred.is_service_account() { + (cred.session_token.as_str(), cred.secret_key.as_str()) + } else { + (token, sys_cred.secret_key.as_str()) + }; + + if !token.is_empty() { + let claims: HashMap = + get_claims_from_token_with_secret(token, secret).map_err(|_e| s3_error!(InvalidRequest, "invalid token"))?; + return Ok(claims); + } + + Ok(HashMap::new()) +} + +pub fn get_session_token(hds: &HeaderMap) -> Option<&str> { + hds.get("x-amz-security-token").map(|v| v.to_str().unwrap_or_default()) +} + +pub fn get_condition_values(header: &HeaderMap, cred: &auth::Credentials) -> HashMap> { + let username = if cred.is_temp() || cred.is_service_account() { + cred.parent_user.clone() + } else { + cred.access_key.clone() + }; + + let sys_cred = get_global_action_cred().unwrap_or_default(); + + let claims = &cred.claims; + + let principal_type = if !username.is_empty() { + if claims.is_some() { + "AssumedRole" + } else if sys_cred.access_key == username { + "Account" + } else { + "User" + } + } else { + "Anonymous" + }; + + let mut args = HashMap::new(); + args.insert("userid".to_owned(), vec![username.clone()]); + args.insert("username".to_owned(), vec![username]); + args.insert("principaltype".to_owned(), vec![principal_type.to_string()]); + + let mut clone_header = header.clone(); + if let Some(v) = clone_header.get("x-amz-signature-age") { + args.insert("signatureAge".to_string(), vec![v.to_str().unwrap_or("").to_string()]); + clone_header.remove("x-amz-signature-age"); + } + + // TODO: parse_object_tags + // if let Some(_user_tags) = clone_header.get("x-amz-tagging") { + // TODO: parse_object_tags + // if let Ok(tag) = tags::parse_object_tags(user_tags.to_str().unwrap_or("")) { + // let tag_map = tag.to_map(); + // let mut keys = Vec::new(); + // for (k, v) in tag_map { + // args.insert(format!("ExistingObjectTag/{}", k), vec![v.clone()]); + // args.insert(format!("RequestObjectTag/{}", k), vec![v.clone()]); + // keys.push(k); + // } + // args.insert("RequestObjectTagKeys".to_string(), keys); + // } + // } + + for obj_lock in &[ + "x-amz-object-lock-mode", + "x-amz-object-lock-legal-hold", + "x-amz-object-lock-retain-until-date", + ] { + let values = clone_header + .get_all(*obj_lock) + .iter() + .map(|v| v.to_str().unwrap_or("").to_string()) + .collect::>(); + if !values.is_empty() { + args.insert(obj_lock.trim_start_matches("x-amz-").to_string(), values); + } + clone_header.remove(*obj_lock); + } + + for (key, _values) in clone_header.iter() { + if key.as_str().eq_ignore_ascii_case("x-amz-tagging") { + continue; + } + if let Some(existing_values) = args.get_mut(key.as_str()) { + existing_values.extend(clone_header.get_all(key).iter().map(|v| v.to_str().unwrap_or("").to_string())); + } else { + args.insert( + key.as_str().to_string(), + header + .get_all(key) + .iter() + .map(|v| v.to_str().unwrap_or("").to_string()) + .collect(), + ); + } + } + + // TODO: add from url query + // let mut clone_url_values = r + // .uri() + // .query() + // .unwrap_or("") + // .split('&') + // .map(|s| { + // let mut split = s.split('='); + // (split.next().unwrap_or("").to_string(), split.next().unwrap_or("").to_string()) + // }) + // .collect::>(); + + // for obj_lock in &[ + // "x-amz-object-lock-mode", + // "x-amz-object-lock-legal-hold", + // "x-amz-object-lock-retain-until-date", + // ] { + // if let Some(values) = clone_url_values.get(*obj_lock) { + // args.insert(obj_lock.trim_start_matches("x-amz-").to_string(), vec![values.clone()]); + // } + // clone_url_values.remove(*obj_lock); + // } + + // for (key, values) in clone_url_values.iter() { + // if let Some(existing_values) = args.get_mut(key) { + // existing_values.push(values.clone()); + // } else { + // args.insert(key.clone(), vec![values.clone()]); + // } + // } + + if let Some(claims) = &cred.claims { + for (k, v) in claims { + if let Some(v_str) = v.as_str() { + args.insert(k.trim_start_matches("ldap").to_lowercase(), vec![v_str.to_string()]); + } + } + + if let Some(grps_val) = claims.get("groups") { + if let Some(grps_is) = grps_val.as_array() { + let grps = grps_is + .iter() + .filter_map(|g| g.as_str().map(|s| s.to_string())) + .collect::>(); + if !grps.is_empty() { + args.insert("groups".to_string(), grps); + } + } + } + } + + if let Some(groups) = &cred.groups { + if !args.contains_key("groups") { + args.insert("groups".to_string(), groups.clone()); + } + } + + args +} diff --git a/rustfs/src/console.rs b/rustfs/src/console.rs index 79088a94..102a3262 100644 --- a/rustfs/src/console.rs +++ b/rustfs/src/console.rs @@ -132,6 +132,7 @@ struct License { pub(crate) static CONSOLE_CONFIG: OnceLock = OnceLock::new(); +#[allow(clippy::const_is_empty)] pub(crate) fn init_console_cfg(local_ip: Ipv4Addr, port: u16) { CONSOLE_CONFIG.get_or_init(|| { let ver = { diff --git a/rustfs/src/storage/access.rs b/rustfs/src/storage/access.rs index 2ff57c9a..06844c64 100644 --- a/rustfs/src/storage/access.rs +++ b/rustfs/src/storage/access.rs @@ -1,28 +1,55 @@ -use std::str::FromStr; - use super::ecfs::FS; - -use ecstore::bucket::policy::action::Action; -use http::HeaderMap; +use crate::auth::{check_key_valid, get_condition_values}; +use iam::auth; +use iam::error::Error as IamError; +use iam::policy::action::{Action, S3Action}; +use iam::sys::Args; use s3s::access::{S3Access, S3AccessContext}; - -use s3s::auth::Credentials; -use s3s::{dto::*, s3_error, S3Request, S3Result}; +use s3s::{dto::*, s3_error, S3Error, S3ErrorCode, S3Request, S3Result}; +use std::collections::HashMap; use tracing::info; -use uuid::Uuid; #[allow(dead_code)] #[derive(Default, Clone)] -struct ReqInfo { - pub card: Option, - pub action: Option, +pub(crate) struct ReqInfo { + pub cred: auth::Credentials, + pub is_owner: bool, pub bucket: Option, pub object: Option, - pub version_id: Option, + pub version_id: Option, } -async fn authorize_request(_req: &ReqInfo, _hs: &HeaderMap, _action: Action) -> S3Result<()> { - // TODO: globalIAMSys.IsAllowed +pub async fn authorize_request(req: &mut S3Request, actions: Vec) -> S3Result<()> { + let Ok(iam_store) = iam::get() else { + return Err(S3Error::with_message( + S3ErrorCode::InternalError, + format!("check_key_valid {:?}", IamError::IamSysNotInitialized), + )); + }; + + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + + let default_claims = HashMap::new(); + let claims = req_info.cred.claims.as_ref().unwrap_or(&default_claims); + let conditions = get_condition_values(&req.headers, &req_info.cred); + + for action in actions { + let args = &Args { + account: &req_info.cred.access_key, + groups: &req_info.cred.groups, + action, + bucket: req_info.bucket.as_deref().unwrap_or(""), + conditions: &conditions, + is_owner: req_info.is_owner, + object: req_info.object.as_deref().unwrap_or(""), + claims, + deny_only: false, + }; + if !iam_store.is_allowed(args).await { + return Err(s3_error!(AccessDenied, "Access Denied")); + } + } + Ok(()) } @@ -45,31 +72,28 @@ impl S3Access for FS { async fn check(&self, cx: &mut S3AccessContext<'_>) -> S3Result<()> { // 上层验证了 ak/sk info!( - "s3 check path: {:?}, s3_op: {:?}, cred: {:?}", + "s3 check uri: {:?}, method: {:?} path: {:?}, s3_op: {:?}, cred: {:?}, headers:{:?}", + cx.uri(), + cx.method(), cx.s3_path(), cx.s3_op().name(), - cx.credentials() + cx.credentials(), + cx.headers(), + // cx.extensions_mut(), ); - if cx.credentials().is_none() { + let Some(input_cred) = cx.credentials() else { return Err(s3_error!(UnauthorizedAccess, "Signature is required")); }; - // TODO: FIXME: check auth - - let action = match Action::from_str(format!("s3:{}", cx.s3_op().name()).as_str()) { - Ok(res) => Some(res), - Err(_) => None, - }; + let (cred, is_owner) = check_key_valid(cx.headers(), &input_cred.access_key).await?; let req_info = ReqInfo { - card: cx.credentials().cloned(), - action, + cred, + is_owner, ..Default::default() }; - // warn!("req_info {:?}", req_info); - let ext = cx.extensions_mut(); ext.insert(req_info); @@ -82,14 +106,23 @@ impl S3Access for FS { /// /// This method returns `Ok(())` by default. async fn create_bucket(&self, req: &mut S3Request) -> S3Result<()> { - if let Some(req_info) = req.extensions.get_mut::() { - let CreateBucketInput { bucket, .. } = &req.input; - req_info.bucket = Some(bucket.to_owned()); + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); - authorize_request(req_info, &req.headers, Action::CreateBucket).await - } else { - Err(s3_error!(AccessDenied, "AccessDenied")) + authorize_request(req, vec![Action::S3Action(S3Action::CreateBucketAction)]).await?; + + if req.input.object_lock_enabled_for_bucket.is_some_and(|v| v) { + authorize_request( + req, + vec![ + Action::S3Action(S3Action::PutBucketObjectLockConfigurationAction), + Action::S3Action(S3Action::PutBucketVersioningAction), + ], + ) + .await?; } + + Ok(()) } /// Checks whether the AbortMultipartUpload request has accesses to the resources. /// @@ -108,8 +141,32 @@ impl S3Access for FS { /// Checks whether the CopyObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn copy_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn copy_object(&self, req: &mut S3Request) -> S3Result<()> { + { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + let (src_bucket, src_key, version_id) = match &req.input.copy_source { + CopySource::AccessPoint { .. } => return Err(s3_error!(NotImplemented)), + CopySource::Bucket { + ref bucket, + ref key, + version_id, + } => (bucket.to_string(), key.to_string(), version_id.as_ref().map(|v| v.to_string())), + }; + + req_info.bucket = Some(src_bucket); + req_info.object = Some(src_key); + req_info.version_id = version_id; + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectAction)]).await?; + } + + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectAction)]).await } /// Checks whether the CreateMultipartUpload request has accesses to the resources. @@ -122,7 +179,15 @@ impl S3Access for FS { /// Checks whether the DeleteBucket request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket(&self, _req: &mut S3Request) -> S3Result<()> { + async fn delete_bucket(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::DeleteBucketAction)]).await?; + + if req.input.force_delete.is_some_and(|v| v) { + authorize_request(req, vec![Action::S3Action(S3Action::ForceDeleteBucketAction)]).await?; + } Ok(()) } @@ -139,15 +204,21 @@ impl S3Access for FS { /// Checks whether the DeleteBucketCors request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_cors(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_cors(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketCorsAction)]).await } /// Checks whether the DeleteBucketEncryption request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_encryption(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_encryption(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketEncryptionAction)]).await } /// Checks whether the DeleteBucketIntelligentTieringConfiguration request has accesses to the resources. @@ -173,8 +244,11 @@ impl S3Access for FS { /// Checks whether the DeleteBucketLifecycle request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_lifecycle(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_lifecycle(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketLifecycleAction)]).await } /// Checks whether the DeleteBucketMetricsConfiguration request has accesses to the resources. @@ -197,22 +271,31 @@ impl S3Access for FS { /// Checks whether the DeleteBucketPolicy request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_policy(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_policy(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::DeleteBucketPolicyAction)]).await } /// Checks whether the DeleteBucketReplication request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_replication(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_replication(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutReplicationConfigurationAction)]).await } /// Checks whether the DeleteBucketTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_bucket_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_bucket_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketTaggingAction)]).await } /// Checks whether the DeleteBucketWebsite request has accesses to the resources. @@ -225,15 +308,25 @@ impl S3Access for FS { /// Checks whether the DeleteObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_object(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::DeleteObjectAction)]).await } /// Checks whether the DeleteObjectTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn delete_object_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn delete_object_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::DeleteObjectTaggingAction)]).await } /// Checks whether the DeleteObjects request has accesses to the resources. @@ -263,8 +356,11 @@ impl S3Access for FS { /// Checks whether the GetBucketAcl request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_acl(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_acl(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketPolicyAction)]).await } /// Checks whether the GetBucketAnalyticsConfiguration request has accesses to the resources. @@ -280,15 +376,21 @@ impl S3Access for FS { /// Checks whether the GetBucketCors request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_cors(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_cors(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketCorsAction)]).await } /// Checks whether the GetBucketEncryption request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_encryption(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_encryption(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketEncryptionAction)]).await } /// Checks whether the GetBucketIntelligentTieringConfiguration request has accesses to the resources. @@ -316,16 +418,22 @@ impl S3Access for FS { /// This method returns `Ok(())` by default. async fn get_bucket_lifecycle_configuration( &self, - _req: &mut S3Request, + req: &mut S3Request, ) -> S3Result<()> { - Ok(()) + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketLifecycleAction)]).await } /// Checks whether the GetBucketLocation request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_location(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_location(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketLocationAction)]).await } /// Checks whether the GetBucketLogging request has accesses to the resources. @@ -347,9 +455,12 @@ impl S3Access for FS { /// This method returns `Ok(())` by default. async fn get_bucket_notification_configuration( &self, - _req: &mut S3Request, + req: &mut S3Request, ) -> S3Result<()> { - Ok(()) + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketNotificationAction)]).await } /// Checks whether the GetBucketOwnershipControls request has accesses to the resources. @@ -362,22 +473,31 @@ impl S3Access for FS { /// Checks whether the GetBucketPolicy request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_policy(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_policy(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketPolicyAction)]).await } /// Checks whether the GetBucketPolicyStatus request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_policy_status(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_policy_status(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketPolicyStatusAction)]).await } /// Checks whether the GetBucketReplication request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_replication(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_replication(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetReplicationConfigurationAction)]).await } /// Checks whether the GetBucketRequestPayment request has accesses to the resources. @@ -390,15 +510,21 @@ impl S3Access for FS { /// Checks whether the GetBucketTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketTaggingAction)]).await } /// Checks whether the GetBucketVersioning request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_bucket_versioning(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_bucket_versioning(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketVersioningAction)]).await } /// Checks whether the GetBucketWebsite request has accesses to the resources. @@ -411,50 +537,92 @@ impl S3Access for FS { /// Checks whether the GetObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectAction)]).await } /// Checks whether the GetObjectAcl request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_acl(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_acl(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketPolicyAction)]).await } /// Checks whether the GetObjectAttributes request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_attributes(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_attributes(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + let mut actions = Vec::new(); + if req.input.version_id.is_some() { + actions.push(Action::S3Action(S3Action::GetObjectVersionAttributesAction)); + actions.push(Action::S3Action(S3Action::GetObjectVersionAction)); + } else { + actions.push(Action::S3Action(S3Action::GetObjectAttributesAction)); + actions.push(Action::S3Action(S3Action::GetObjectAction)); + } + + authorize_request(req, actions).await } /// Checks whether the GetObjectLegalHold request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_legal_hold(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_legal_hold(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectLegalHoldAction)]).await } /// Checks whether the GetObjectLockConfiguration request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_lock_configuration(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_lock_configuration(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetBucketObjectLockConfigurationAction)]).await } /// Checks whether the GetObjectRetention request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_retention(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_retention(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectRetentionAction)]).await } /// Checks whether the GetObjectTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn get_object_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn get_object_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectTaggingAction)]).await } /// Checks whether the GetObjectTorrent request has accesses to the resources. @@ -474,15 +642,23 @@ impl S3Access for FS { /// Checks whether the HeadBucket request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn head_bucket(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn head_bucket(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::ListBucketAction)]).await } /// Checks whether the HeadObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn head_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn head_object(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectAction)]).await } /// Checks whether the ListBucketAnalyticsConfigurations request has accesses to the resources. @@ -529,14 +705,18 @@ impl S3Access for FS { /// /// This method returns `Ok(())` by default. async fn list_buckets(&self, _req: &mut S3Request) -> S3Result<()> { + // check inside Ok(()) } /// Checks whether the ListMultipartUploads request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn list_multipart_uploads(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn list_multipart_uploads(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::ListBucketMultipartUploadsAction)]).await } /// Checks whether the ListObjectVersions request has accesses to the resources. @@ -549,15 +729,21 @@ impl S3Access for FS { /// Checks whether the ListObjects request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn list_objects(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn list_objects(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::ListBucketAction)]).await } /// Checks whether the ListObjectsV2 request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn list_objects_v2(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn list_objects_v2(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::ListBucketAction)]).await } /// Checks whether the ListParts request has accesses to the resources. @@ -580,8 +766,11 @@ impl S3Access for FS { /// Checks whether the PutBucketAcl request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_acl(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_acl(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketPolicyAction)]).await } /// Checks whether the PutBucketAnalyticsConfiguration request has accesses to the resources. @@ -597,15 +786,21 @@ impl S3Access for FS { /// Checks whether the PutBucketCors request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_cors(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_cors(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketCorsAction)]).await } /// Checks whether the PutBucketEncryption request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_encryption(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_encryption(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketEncryptionAction)]).await } /// Checks whether the PutBucketIntelligentTieringConfiguration request has accesses to the resources. @@ -633,9 +828,12 @@ impl S3Access for FS { /// This method returns `Ok(())` by default. async fn put_bucket_lifecycle_configuration( &self, - _req: &mut S3Request, + req: &mut S3Request, ) -> S3Result<()> { - Ok(()) + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketLifecycleAction)]).await } /// Checks whether the PutBucketLogging request has accesses to the resources. @@ -657,9 +855,12 @@ impl S3Access for FS { /// This method returns `Ok(())` by default. async fn put_bucket_notification_configuration( &self, - _req: &mut S3Request, + req: &mut S3Request, ) -> S3Result<()> { - Ok(()) + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketNotificationAction)]).await } /// Checks whether the PutBucketOwnershipControls request has accesses to the resources. @@ -672,15 +873,21 @@ impl S3Access for FS { /// Checks whether the PutBucketPolicy request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_policy(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_policy(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketPolicyAction)]).await } /// Checks whether the PutBucketReplication request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_replication(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_replication(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutReplicationConfigurationAction)]).await } /// Checks whether the PutBucketRequestPayment request has accesses to the resources. @@ -693,15 +900,21 @@ impl S3Access for FS { /// Checks whether the PutBucketTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketTaggingAction)]).await } /// Checks whether the PutBucketVersioning request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_bucket_versioning(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_bucket_versioning(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketVersioningAction)]).await } /// Checks whether the PutBucketWebsite request has accesses to the resources. @@ -714,43 +927,71 @@ impl S3Access for FS { /// Checks whether the PutObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectAction)]).await } /// Checks whether the PutObjectAcl request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object_acl(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object_acl(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketPolicyAction)]).await } /// Checks whether the PutObjectLegalHold request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object_legal_hold(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object_legal_hold(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectLegalHoldAction)]).await } /// Checks whether the PutObjectLockConfiguration request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object_lock_configuration(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object_lock_configuration(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutBucketObjectLockConfigurationAction)]).await } /// Checks whether the PutObjectRetention request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object_retention(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object_retention(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectRetentionAction)]).await } /// Checks whether the PutObjectTagging request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn put_object_tagging(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn put_object_tagging(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectTaggingAction)]).await } /// Checks whether the PutPublicAccessBlock request has accesses to the resources. @@ -763,22 +1004,35 @@ impl S3Access for FS { /// Checks whether the RestoreObject request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn restore_object(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn restore_object(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + req_info.version_id = req.input.version_id.clone(); + + authorize_request(req, vec![Action::S3Action(S3Action::RestoreObjectAction)]).await } /// Checks whether the SelectObjectContent request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn select_object_content(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn select_object_content(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::GetObjectAction)]).await } /// Checks whether the UploadPart request has accesses to the resources. /// /// This method returns `Ok(())` by default. - async fn upload_part(&self, _req: &mut S3Request) -> S3Result<()> { - Ok(()) + async fn upload_part(&self, req: &mut S3Request) -> S3Result<()> { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + req_info.bucket = Some(req.input.bucket.clone()); + req_info.object = Some(req.input.key.clone()); + + authorize_request(req, vec![Action::S3Action(S3Action::PutObjectAction)]).await } /// Checks whether the UploadPartCopy request has accesses to the resources. diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index f900883b..95ef13cc 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -1,6 +1,8 @@ +use super::access::authorize_request; use super::options::del_opts; use super::options::extract_metadata; use super::options::put_opts; +use crate::storage::access::ReqInfo; use bytes::Bytes; use common::error::Result; use ecstore::bucket::error::BucketMetadataError; @@ -36,6 +38,8 @@ use ecstore::xhttp; use futures::pin_mut; use futures::{Stream, StreamExt}; use http::HeaderMap; +use iam::policy::action::Action; +use iam::policy::action::S3Action; use lazy_static::lazy_static; use log::warn; use s3s::dto::*; @@ -536,14 +540,36 @@ impl S3 for FS { } #[tracing::instrument(level = "debug", skip(self))] - async fn list_buckets(&self, _: S3Request) -> S3Result> { + async fn list_buckets(&self, req: S3Request) -> S3Result> { // mc ls let Some(store) = new_object_layer_fn() else { return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string())); }; - let bucket_infos = store.list_bucket(&BucketOptions::default()).await.map_err(to_s3_error)?; + let mut bucket_infos = store.list_bucket(&BucketOptions::default()).await.map_err(to_s3_error)?; + + let mut req = req; + + if authorize_request(&mut req, vec![Action::S3Action(S3Action::ListAllMyBucketsAction)]) + .await + .is_err() + { + bucket_infos.retain(|info| { + let req_info = req.extensions.get_mut::().expect("ReqInfo not found"); + + req_info.bucket = Some(info.name.clone()); + + futures::executor::block_on(async { + authorize_request(&mut req, vec![Action::S3Action(S3Action::ListBucketAction)]) + .await + .is_ok() + || authorize_request(&mut req, vec![Action::S3Action(S3Action::GetBucketLocationAction)]) + .await + .is_ok() + }) + }); + } let buckets: Vec = bucket_infos .iter() @@ -911,9 +937,9 @@ impl S3 for FS { async fn upload_part_copy(&self, req: S3Request) -> S3Result> { let _input = req.input; - let output = UploadPartCopyOutput { ..Default::default() }; + let _output = UploadPartCopyOutput { ..Default::default() }; - Ok(S3Response::new(output)) + unimplemented!("upload_part_copy"); } #[tracing::instrument(level = "debug", skip(self, req))] diff --git a/rustfs/src/utils.rs b/rustfs/src/utils.rs index 24afba86..1496bd3f 100644 --- a/rustfs/src/utils.rs +++ b/rustfs/src/utils.rs @@ -1,4 +1,3 @@ -use local_ip_address; use std::net::IpAddr; pub(crate) fn get_local_ip() -> Option { diff --git a/scripts/run.sh b/scripts/run.sh index f8587071..bd114184 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -31,4 +31,12 @@ if [ -n "$1" ]; then fi + +# check ./rustfs/static/index.html not exists +if [ ! -f ./rustfs/static/index.html ]; then + echo "Downloading rustfs-console-latest.zip" + # download rustfs-console-latest.zip do not show log + curl -s -L "https://dl.rustfs.com/console/rustfs-console-latest.zip" -o tempfile.zip && unzip -q -o tempfile.zip -d ./rustfs/static && rm tempfile.zip +fi + cargo run --bin rustfs \ No newline at end of file