diff --git a/iam/src/auth/credentials.rs b/iam/src/auth/credentials.rs index 8d0c8048b..3f860587c 100644 --- a/iam/src/auth/credentials.rs +++ b/iam/src/auth/credentials.rs @@ -177,22 +177,17 @@ pub fn generate_credentials() -> Result<(String, String)> { Ok((ak, sk)) } -pub fn get_new_credentials_with_metadata( - claims: &T, - token_secret: &str, - exp: Option, -) -> Result { +pub fn get_new_credentials_with_metadata(claims: &HashMap, token_secret: &str) -> Result { let (ak, sk) = generate_credentials()?; - create_new_credentials_with_metadata(&ak, &sk, claims, token_secret, exp) + create_new_credentials_with_metadata(&ak, &sk, claims, token_secret) } -pub fn create_new_credentials_with_metadata( +pub fn create_new_credentials_with_metadata( ak: &str, sk: &str, - claims: &T, + claims: &HashMap, token_secret: &str, - exp: Option, ) -> Result { if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN { return Err(Error::new(IamError::InvalidAccessKeyLength)); @@ -202,6 +197,18 @@ pub fn create_new_credentials_with_metadata( return Err(Error::new(IamError::InvalidAccessKeyLength)); } + let expiration = { + if let Some(v) = claims.get("exp") { + if let Some(expiry) = v.as_i64() { + Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_local()?.offset())) + } else { + None + } + } else { + None + } + }; + let token = utils::generate_jwt(claims, token_secret)?; Ok(Credentials { @@ -209,7 +216,7 @@ pub fn create_new_credentials_with_metadata( secret_key: sk.to_owned(), session_token: token, status: ACCOUNT_ON.to_owned(), - expiration: exp.map(|v| OffsetDateTime::now_utc().saturating_add(Duration::seconds(v as i64))), + expiration, ..Default::default() }) } diff --git a/iam/src/manager.rs b/iam/src/manager.rs index 70207567d..7cabb1aff 100644 --- a/iam/src/manager.rs +++ b/iam/src/manager.rs @@ -773,7 +773,14 @@ where } pub async fn set_temp_user(&self, access_key: &str, cred: &Credentials, policy_name: Option<&str>) -> Result { - if access_key.is_empty() || cred.is_temp() || cred.is_expired() || cred.parent_user.is_empty() { + if access_key.is_empty() || !cred.is_temp() || cred.is_expired() || cred.parent_user.is_empty() { + error!( + "set temp user invalid argument, access_key: {}, is_temp: {}, is_expired: {}, parent_user_empty: {}", + access_key, + cred.is_temp(), + cred.is_expired(), + cred.parent_user.is_empty() + ); return Err(Error::new(IamError::InvalidArgument)); } diff --git a/iam/src/sys.rs b/iam/src/sys.rs index 0babd8f46..57d01c887 100644 --- a/iam/src/sys.rs +++ b/iam/src/sys.rs @@ -210,14 +210,14 @@ impl IamSys { Vec::new() }; - let mut m = HashMap::new(); - m.insert("parent".to_owned(), parent_user.to_owned()); + let mut m: HashMap = HashMap::new(); + m.insert("parent".to_owned(), serde_json::Value::String(parent_user.to_owned())); if !policy_buf.is_empty() { - m.insert(SESSION_POLICY_NAME.to_owned(), base64_encode(&policy_buf)); - m.insert(iam_policy_claim_name_sa(), EMBEDDED_POLICY_TYPE.to_owned()); + m.insert(SESSION_POLICY_NAME.to_owned(), serde_json::Value::String(base64_encode(&policy_buf))); + m.insert(iam_policy_claim_name_sa(), serde_json::Value::String(EMBEDDED_POLICY_TYPE.to_owned())); } else { - m.insert(iam_policy_claim_name_sa(), INHERITED_POLICY_TYPE.to_owned()); + m.insert(iam_policy_claim_name_sa(), serde_json::Value::String(INHERITED_POLICY_TYPE.to_owned())); } if let Some(claims) = opts.claims { @@ -234,7 +234,7 @@ impl IamSys { generate_credentials()? }; - let mut cred = create_new_credentials_with_metadata(&access_key, &secret_key, &m, &secret_key, None)?; + let mut cred = create_new_credentials_with_metadata(&access_key, &secret_key, &m, &secret_key)?; cred.parent_user = parent_user.to_owned(); cred.groups = Some(groups); cred.status = ACCOUNT_ON.to_owned(); @@ -671,7 +671,7 @@ pub struct NewServiceAccountOpts { pub description: Option, pub expiration: Option, pub allow_site_replicator_account: bool, - pub claims: Option>, + pub claims: Option>, } pub struct UpdateServiceAccountOpts { diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index 762736f2d..24f67a295 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -16,14 +16,17 @@ 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::utils::xml; use ecstore::GLOBAL_Endpoints; use futures::{Stream, StreamExt}; use http::{HeaderMap, Uri}; use hyper::StatusCode; -use iam::auth::{create_new_credentials_with_metadata, get_claims_from_token_with_secret}; +use iam::auth::{get_claims_from_token_with_secret, get_new_credentials_with_metadata}; 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; @@ -36,6 +39,7 @@ use s3s::{ }; 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; @@ -70,49 +74,6 @@ pub struct AssumeRoleRequest { pub external_id: String, } -// #[derive(Debug, Serialize, Default)] -// #[serde(rename_all = "PascalCase", default)] -// pub struct AssumeRoleResponse { -// #[serde(rename = "AssumeRoleResult")] -// pub result: AssumeRoleResult, -// } - -// #[derive(Debug, Serialize, Default)] -// #[serde(rename_all = "PascalCase", default)] -// pub struct AssumeRoleResult { -// pub credentials: Credentials, -// } - -// #[derive(Debug, Serialize, Default)] -// #[serde(rename_all = "PascalCase", default)] -// pub struct Credentials { -// #[serde(rename = "AccessKeyId")] -// pub access_key: String, -// #[serde(rename = "SecretAccessKey")] -// pub secret_key: String, -// pub status: String, -// pub expiration: usize, -// pub session_token: String, -// pub parent_user: String, -// } - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct STSClaims { - pub parent: String, - pub exp: usize, - pub access_key: String, -} - -impl STSClaims { - pub fn to_map(&self) -> HashMap { - let mut m = HashMap::new(); - m.insert("parent".to_string(), self.parent.clone()); - m.insert("exp".to_string(), self.exp.to_string()); - m.insert("access_key".to_string(), self.access_key.clone()); - m - } -} - fn get_token_signing_key() -> Option { if let Some(s) = get_global_action_cred() { Some(s.secret_key.clone()) @@ -122,7 +83,7 @@ fn get_token_signing_key() -> Option { } // check_key_valid get auth.cred -pub async fn check_key_valid(token: Option, ak: &str) -> S3Result<(auth::Credentials, bool)> { +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, @@ -145,92 +106,83 @@ pub async fn check_key_valid(token: Option, ak: &str) -> S3Result<(auth: .await .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?; - // if !ok { - // if u.credentials.status == "off" { - // return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); - // } + 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")); - // } + return Err(s3_error!(InvalidRequest, "check key failed")); + } - // match iam_store - // .check_key(ak) - // .await - // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))? - // { - // (Some(u), true) => { - // cred = u.credentials; - // } - // (Some(u), false) => { - // if u.credentials.status == "off" { - // return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); - // } + let Some(u) = u else { + return Err(s3_error!(InvalidRequest, "check key failed")); + }; - // return Err(s3_error!(InvalidRequest, "check key failed")); - // } - // _ => { - // return Err(s3_error!(InvalidRequest, "check key failed")); - // } - // } + cred = u.credentials; } - unimplemented!() + 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)))?; - // if let Some(st) = token { - // let claims = check_claims_from_token(&st, &cred) - // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?; - // cred.claims = Some(claims.to_map()); - // } + cred.claims = if !claims.is_empty() { Some(claims) } else { None }; - // let owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; + let mut owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; - // // permitRootAccess - // // SessionPolicyName - // Ok((cred, owner)) + // 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 { - unimplemented!() - // if !token.is_empty() && cred.access_key.is_empty() { - // return Err(s3_error!(InvalidRequest, "no access key")); - // } +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() && !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 !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_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")); - // } + if cred.is_temp() || cred.is_expired() { + return Err(s3_error!(InvalidRequest, "invalid access key")); + } - // let Some(sys_cred) = get_global_action_cred() else { - // return Err(s3_error!(InternalError, "action cred not init")); - // }; + 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; + let mut secret = sys_cred.secret_key; - // let mut token = token; + // TODO: REPLICATION - // if cred.is_service_account() { - // token = cred.session_token.as_str(); - // secret = cred.secret_key.clone(); - // } + let mut token = token; - // 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); - // } + if cred.is_service_account() { + token = cred.session_token.as_str(); + secret = cred.secret_key.clone(); + } - // Ok(STSClaims::default()) + 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 { @@ -238,93 +190,130 @@ pub fn get_session_token(hds: &HeaderMap) -> Option { .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(()) +} + pub struct AssumeRoleHandle {} #[async_trait::async_trait] impl Operation for AssumeRoleHandle { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { - unimplemented!() - // warn!("handle AssumeRoleHandle"); + warn!("handle AssumeRoleHandle"); - // let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) }; + let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) }; + + let session_token = get_session_token(&req.headers); + if session_token.is_some() { + return Err(s3_error!(InvalidRequest, "AccessDenied1")); + } + + let (cred, _owner) = check_key_valid(session_token, &user.access_key).await?; // // TODO: 判断权限, 不允许sts访问 + if cred.is_temp() || cred.is_service_account() { + return Err(s3_error!(InvalidRequest, "AccessDenied")); + } - // let mut input = req.input; + let mut input = req.input; - // let Some(bytes) = input.take_bytes() else { - // return Err(s3_error!(InvalidRequest, "get body failed")); - // }; - // let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?; + let Some(bytes) = input.take_bytes() else { + return Err(s3_error!(InvalidRequest, "get body failed")); + }; + let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?; - // if body.action.as_str() != ASSUME_ROLE_ACTION { - // return Err(s3_error!(InvalidArgument, "not suport action")); - // } + if body.action.as_str() != ASSUME_ROLE_ACTION { + return Err(s3_error!(InvalidArgument, "not suport action")); + } - // if body.version.as_str() != ASSUME_ROLE_VERSION { - // return Err(s3_error!(InvalidArgument, "not suport version")); - // } + if body.version.as_str() != ASSUME_ROLE_VERSION { + return Err(s3_error!(InvalidArgument, "not suport version")); + } + + let mut claims = cred.claims.unwrap_or_default(); + + populate_session_policy(&mut claims, &body.policy)?; + + let exp = { + if body.duration_seconds > 0 { + body.duration_seconds + } else { + 3600 + } + }; + + claims.insert( + "exp".to_string(), + serde_json::Value::Number(serde_json::Number::from(OffsetDateTime::now_utc().unix_timestamp() + exp as i64)), + ); + + claims.insert("parent".to_string(), serde_json::Value::String(cred.access_key.clone())); // warn!("AssumeRole get cred {:?}", &user); // warn!("AssumeRole get body {:?}", &body); - // let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) }; + let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) }; - // if let Err(_err) = iam_store.policy_db_get(&user.access_key, None).await { - // return Err(s3_error!(InvalidArgument, "invalid policy arg")); - // } + if let Err(_err) = iam_store + .policy_db_get(&cred.access_key, &cred.groups.unwrap_or_default()) + .await + { + return Err(s3_error!(InvalidArgument, "invalid policy arg")); + } - // let Some(secret) = get_token_signing_key() else { - // return Err(s3_error!(InvalidArgument, "sk not init")); - // }; + let Some(secret) = get_token_signing_key() else { + return Err(s3_error!(InvalidArgument, "global active sk not init")); + }; - // let exp = { - // if body.duration_seconds > 0 { - // body.duration_seconds - // } else { - // 3600 - // } - // }; + info!("AssumeRole get claims {:?}", &claims); - // let mut claims = STSClaims { - // parent: user.access_key.clone(), - // exp, - // ..Default::default() - // }; + let mut new_cred = get_new_credentials_with_metadata(&claims, &secret) + .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get new cred failed {}", e)))?; - // let ak = iam::utils::gen_access_key(20).unwrap_or_default(); - // let sk = iam::utils::gen_secret_key(32).unwrap_or_default(); + new_cred.parent_user = cred.access_key.clone(); - // claims.access_key = ak.clone(); + info!("AssumeRole get new_cred {:?}", &new_cred); - // let mut cred = match create_new_credentials_with_metadata(&ak, &sk, &claims, &secret, Some(exp)) { - // Ok(res) => res, - // Err(_er) => return Err(s3_error!(InvalidRequest, "")), - // }; + if let Err(_err) = iam_store.set_temp_user(&new_cred.access_key, &new_cred, None).await { + return Err(s3_error!(InternalError, "set_temp_user failed")); + } - // cred.parent_user = user.access_key.clone(); + // TODO: globalSiteReplicationSys - // if let Err(err) = iam_store.set_temp_user(&cred.access_key, &cred, None).await { - // error!("set_temp_user err {:?}", err); - // return Err(s3_error!(InternalError, "set_temp_user failed")); - // } + let resp = AssumeRoleOutput { + credentials: Some(Credentials { + access_key_id: new_cred.access_key, + expiration: Timestamp::from( + new_cred + .expiration + .unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))), + ), + secret_access_key: new_cred.secret_key, + session_token: new_cred.session_token, + }), + ..Default::default() + }; - // let resp = AssumeRoleOutput { - // credentials: Some(Credentials { - // access_key_id: cred.access_key, - // expiration: Timestamp::from( - // cred.expiration - // .unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))), - // ), - // secret_access_key: cred.secret_key, - // session_token: cred.session_token, - // }), - // ..Default::default() - // }; + // getAssumeRoleCredentials + let output = xml::serialize::(&resp).unwrap(); - // // getAssumeRoleCredentials - // let output = xml::serialize::(&resp).unwrap(); - - // Ok(S3Response::new((StatusCode::OK, Body::from(output)))) + Ok(S3Response::new((StatusCode::OK, Body::from(output)))) } } diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index 5e48b839e..d74081107 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -1,4 +1,3 @@ -use iam::cache::CacheInner; use log::warn; use s3s::auth::S3Auth; use s3s::auth::SecretKey;