From b29b15f3b58c6928756f00e826402742479aa786 Mon Sep 17 00:00:00 2001 From: weisd Date: Tue, 14 Jan 2025 22:03:45 +0800 Subject: [PATCH] rewrite iam --- Cargo.lock | 3 + ecstore/src/config/common.rs | 26 +- ecstore/src/store_list_objects.rs | 1 + ecstore/src/utils/path.rs | 6 +- iam/Cargo.toml | 2 + iam/src/arn.rs | 94 +- iam/src/auth/credentials.rs | 320 ++-- iam/src/cache.rs | 74 +- iam/src/error.rs | 71 +- iam/src/format.rs | 20 +- iam/src/handler.rs | 256 +-- iam/src/lib.rs | 107 +- iam/src/manager.rs | 1556 +++++++++++++++--- iam/src/policy.rs | 86 +- iam/src/policy/action.rs | 41 +- iam/src/policy/doc.rs | 47 + iam/src/policy/effect.rs | 8 +- iam/src/policy/function.rs | 19 +- iam/src/policy/function/addr.rs | 7 +- iam/src/policy/function/binary.rs | 10 +- iam/src/policy/function/bool_null.rs | 5 +- iam/src/policy/function/condition.rs | 38 +- iam/src/policy/function/date.rs | 7 +- iam/src/policy/function/func.rs | 4 +- iam/src/policy/function/key.rs | 14 +- iam/src/policy/function/key_name.rs | 18 +- iam/src/policy/function/number.rs | 7 +- iam/src/policy/function/string.rs | 8 +- iam/src/policy/id.rs | 11 +- iam/src/policy/policy.rs | 95 +- iam/src/policy/resource.rs | 42 +- iam/src/policy/statement.rs | 35 +- iam/src/policy/utils.rs | 8 +- iam/src/service_type.rs | 2 +- iam/src/store.rs | 155 +- iam/src/store/object.rs | 1218 ++++++++++---- iam/src/sys.rs | 754 +++++++++ iam/src/utils.rs | 8 +- iam/tests/policy_is_allowed.rs | 8 +- madmin/Cargo.toml | 1 + madmin/src/group.rs | 11 + madmin/src/lib.rs | 2 + madmin/src/policy.rs | 14 + madmin/src/service_commands.rs | 2 + madmin/src/user.rs | 2 +- rustfs/src/admin/handlers.rs | 267 +-- rustfs/src/admin/handlers/service_account.rs | 396 +++-- rustfs/src/admin/handlers/user.rs | 290 ++-- rustfs/src/auth.rs | 6 +- scripts/run.sh | 4 +- 50 files changed, 4529 insertions(+), 1657 deletions(-) create mode 100644 iam/src/sys.rs create mode 100644 madmin/src/policy.rs diff --git a/Cargo.lock b/Cargo.lock index 12c3b0aa..d74aa938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,9 +1242,11 @@ dependencies = [ "ipnetwork", "itertools 0.14.0", "jsonwebtoken", + "lazy_static", "log", "madmin", "rand", + "regex", "serde", "serde_json", "strum", @@ -1642,6 +1644,7 @@ dependencies = [ "humantime", "hyper", "serde", + "serde_json", "time", "tracing", ] diff --git a/ecstore/src/config/common.rs b/ecstore/src/config/common.rs index b209840c..14c386cd 100644 --- a/ecstore/src/config/common.rs +++ b/ecstore/src/config/common.rs @@ -33,7 +33,7 @@ pub async fn read_config(api: Arc, file: &str) -> Result( +pub async fn read_config_with_metadata( api: Arc, file: &str, opts: &ObjectOptions, @@ -72,6 +72,30 @@ pub async fn save_config(api: Arc, file: &str, data: &[u8]) -> .await } +pub async fn delete_config(api: Arc, file: &str) -> Result<()> { + match api + .delete_object( + RUSTFS_META_BUCKET, + file, + ObjectOptions { + delete_prefix: true, + delete_prefix_object: true, + ..Default::default() + }, + ) + .await + { + Ok(_) => Ok(()), + Err(err) => { + if is_err_object_not_found(&err) { + Err(Error::new(ConfigError::NotFound)) + } else { + Err(err) + } + } + } +} + async fn save_config_with_opts(api: Arc, file: &str, data: &[u8], opts: &ObjectOptions) -> Result<()> { let _ = api .put_object( diff --git a/ecstore/src/store_list_objects.rs b/ecstore/src/store_list_objects.rs index 30efc68c..58b8a740 100644 --- a/ecstore/src/store_list_objects.rs +++ b/ecstore/src/store_list_objects.rs @@ -672,6 +672,7 @@ impl ECStore { Ok(Vec::new()) } + #[allow(unused_assignments)] pub async fn walk( self: Arc, rx: B_Receiver, diff --git a/ecstore/src/utils/path.rs b/ecstore/src/utils/path.rs index 745aef67..0e38eac2 100644 --- a/ecstore/src/utils/path.rs +++ b/ecstore/src/utils/path.rs @@ -71,7 +71,7 @@ pub fn path_join(elem: &[PathBuf]) -> PathBuf { } pub fn path_join_buf(elements: &[&str]) -> String { - let trailing_slash = !elements.is_empty() && elements.last().unwrap().ends_with('/'); + let trailing_slash = !elements.is_empty() && elements.last().unwrap().ends_with(SLASH_SEPARATOR); let mut dst = String::new(); let mut added = 0; @@ -79,7 +79,7 @@ pub fn path_join_buf(elements: &[&str]) -> String { for e in elements { if added > 0 || !e.is_empty() { if added > 0 { - dst.push('/'); + dst.push_str(SLASH_SEPARATOR); } dst.push_str(e); added += e.len(); @@ -91,7 +91,7 @@ pub fn path_join_buf(elements: &[&str]) -> String { let clean_path = cpath.to_string_lossy(); if trailing_slash { - return format!("{}/", clean_path); + return format!("{}{}", clean_path, SLASH_SEPARATOR); } clean_path.to_string() } diff --git a/iam/Cargo.toml b/iam/Cargo.toml index 4b6628fc..5c53fbb6 100644 --- a/iam/Cargo.toml +++ b/iam/Cargo.toml @@ -29,6 +29,8 @@ base64-simd = "0.8.0" jsonwebtoken = "9.3.0" tracing.workspace = true madmin.workspace = true +lazy_static.workspace = true +regex = "1.11.1" [dev-dependencies] test-case.workspace = true diff --git a/iam/src/arn.rs b/iam/src/arn.rs index f4d1b3ac..c633fadc 100644 --- a/iam/src/arn.rs +++ b/iam/src/arn.rs @@ -1,18 +1,90 @@ -use std::str::FromStr; +use ecstore::error::{Error, Result}; +use regex::Regex; -#[derive(PartialEq, Eq, Hash)] +const ARN_PREFIX_ARN: &str = "arn"; +const ARN_PARTITION_RUSTFS: &str = "rustfs"; +const ARN_SERVICE_IAM: &str = "iam"; +const ARN_RESOURCE_TYPE_ROLE: &str = "role"; + +#[derive(Debug, PartialEq, Eq, Hash)] pub struct ARN { - partition: String, - service: String, - region: String, - resource_type: String, - resource_id: String, + pub partition: String, + pub service: String, + pub region: String, + pub resource_type: String, + pub resource_id: String, } -impl FromStr for ARN { - type Err = String; +impl ARN { + pub fn new_iam_role_arn(resource_id: &str, server_region: &str) -> Result { + let valid_resource_id_regex = Regex::new(r"^[A-Za-z0-9_/\.-]+$").unwrap(); + if !valid_resource_id_regex.is_match(resource_id) { + return Err(Error::msg("ARN resource ID invalid")); + } + Ok(ARN { + partition: ARN_PARTITION_RUSTFS.to_string(), + service: ARN_SERVICE_IAM.to_string(), + region: server_region.to_string(), + resource_type: ARN_RESOURCE_TYPE_ROLE.to_string(), + resource_id: resource_id.to_string(), + }) + } - fn from_str(s: &str) -> Result { - todo!() + pub fn parse(arn_str: &str) -> Result { + let ps: Vec<&str> = arn_str.split(':').collect(); + if ps.len() != 6 || ps[0] != ARN_PREFIX_ARN { + return Err(Error::msg("ARN format invalid")); + } + + if ps[1] != ARN_PARTITION_RUSTFS { + return Err(Error::msg("ARN partition invalid")); + } + + if ps[2] != ARN_SERVICE_IAM { + return Err(Error::msg("ARN service invalid")); + } + + if !ps[4].is_empty() { + return Err(Error::msg("ARN account-id invalid")); + } + + let res: Vec<&str> = ps[5].splitn(2, '/').collect(); + if res.len() != 2 { + return Err(Error::msg("ARN resource invalid")); + } + + if res[0] != ARN_RESOURCE_TYPE_ROLE { + return Err(Error::msg("ARN resource type invalid")); + } + + let valid_resource_id_regex = Regex::new(r"^[A-Za-z0-9_/\.-]+$").unwrap(); + if !valid_resource_id_regex.is_match(res[1]) { + return Err(Error::msg("ARN resource ID invalid")); + } + + Ok(ARN { + partition: ARN_PARTITION_RUSTFS.to_string(), + service: ARN_SERVICE_IAM.to_string(), + region: ps[3].to_string(), + resource_type: ARN_RESOURCE_TYPE_ROLE.to_string(), + resource_id: res[1].to_string(), + }) + } +} + +impl std::fmt::Display for ARN { + #[allow(clippy::write_literal)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}:{}:{}:{}:{}:{}/{}", + ARN_PREFIX_ARN, + self.partition, + self.service, + self.region, + "", // account-id is always empty in this implementation + self.resource_type, + self.resource_id + ) } } diff --git a/iam/src/auth/credentials.rs b/iam/src/auth/credentials.rs index 18adcac7..8d0c8048 100644 --- a/iam/src/auth/credentials.rs +++ b/iam/src/auth/credentials.rs @@ -1,14 +1,14 @@ -use crate::policy::{Policy, Validator}; -use crate::service_type::ServiceType; +use crate::error::Error as IamError; +use crate::policy::Policy; +use crate::sys::Validator; +use crate::utils; use crate::utils::extract_claims; -use crate::{utils, Error}; +use ecstore::error::{Error, Result}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use std::cell::LazyCell; use std::collections::HashMap; -use time::format_description::BorrowedFormatItem; -use time::{Date, Duration, OffsetDateTime}; +use time::{Duration, OffsetDateTime}; const ACCESS_KEY_MIN_LEN: usize = 3; const ACCESS_KEY_MAX_LEN: usize = 20; @@ -35,78 +35,78 @@ pub fn is_secret_key_valid(secret_key: &str) -> bool { secret_key.len() >= SECRET_KEY_MIN_LEN } -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] -struct CredentialHeader { - access_key: String, - scop: CredentialHeaderScope, -} +// #[cfg_attr(test, derive(PartialEq, Eq, Debug))] +// struct CredentialHeader { +// access_key: String, +// scop: CredentialHeaderScope, +// } -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] -struct CredentialHeaderScope { - date: Date, - region: String, - service: ServiceType, - request: String, -} +// #[cfg_attr(test, derive(PartialEq, Eq, Debug))] +// struct CredentialHeaderScope { +// date: Date, +// region: String, +// service: ServiceType, +// request: String, +// } -impl TryFrom<&str> for CredentialHeader { - type Error = Error; - fn try_from(value: &str) -> Result { - let mut elem = value.trim().splitn(2, '='); - let (Some(h), Some(cred_elems)) = (elem.next(), elem.next()) else { - return Err(Error::ErrCredMalformed); - }; +// impl TryFrom<&str> for CredentialHeader { +// type Error = Error; +// fn try_from(value: &str) -> Result { +// let mut elem = value.trim().splitn(2, '='); +// let (Some(h), Some(cred_elems)) = (elem.next(), elem.next()) else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - if h != "Credential" { - return Err(Error::ErrCredMalformed); - } +// if h != "Credential" { +// return Err(Error::new(IamError::ErrCredMalformed)); +// } - let mut cred_elems = cred_elems.trim().rsplitn(5, '/'); +// let mut cred_elems = cred_elems.trim().rsplitn(5, '/'); - let Some(request) = cred_elems.next() else { - return Err(Error::ErrCredMalformed); - }; +// let Some(request) = cred_elems.next() else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - let Some(service) = cred_elems.next() else { - return Err(Error::ErrCredMalformed); - }; +// let Some(service) = cred_elems.next() else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - let Some(region) = cred_elems.next() else { - return Err(Error::ErrCredMalformed); - }; +// let Some(region) = cred_elems.next() else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - let Some(date) = cred_elems.next() else { - return Err(Error::ErrCredMalformed); - }; +// let Some(date) = cred_elems.next() else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - let Some(ak) = cred_elems.next() else { - return Err(Error::ErrCredMalformed); - }; +// let Some(ak) = cred_elems.next() else { +// return Err(Error::new(IamError::ErrCredMalformed)); +// }; - if ak.len() < 3 { - return Err(Error::ErrCredMalformed); - } +// if ak.len() < 3 { +// return Err(Error::new(IamError::ErrCredMalformed)); +// } - if request != "aws4_request" { - return Err(Error::ErrCredMalformed); - } +// if request != "aws4_request" { +// return Err(Error::new(IamError::ErrCredMalformed)); +// } - Ok(CredentialHeader { - access_key: ak.to_owned(), - scop: CredentialHeaderScope { - date: { - const FORMATTER: LazyCell>> = - LazyCell::new(|| time::format_description::parse("[year][month][day]").unwrap()); +// Ok(CredentialHeader { +// access_key: ak.to_owned(), +// scop: CredentialHeaderScope { +// date: { +// const FORMATTER: LazyCell>> = +// LazyCell::new(|| time::format_description::parse("[year][month][day]").unwrap()); - Date::parse(date, &FORMATTER).map_err(|_| Error::ErrCredMalformed)? - }, - region: region.to_owned(), - service: service.try_into()?, - request: request.to_owned(), - }, - }) - } -} +// Date::parse(date, &FORMATTER).map_err(|_| Error::new(IamError::ErrCredMalformed))? +// }, +// region: region.to_owned(), +// service: service.try_into()?, +// request: request.to_owned(), +// }, +// }) +// } +// } #[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct Credentials { @@ -117,20 +117,20 @@ pub struct Credentials { pub status: String, pub parent_user: String, pub groups: Option>, - pub claims: Option>, + pub claims: Option>, pub name: Option, pub description: Option, } impl Credentials { - pub fn new(elem: &str) -> crate::Result { - let header: CredentialHeader = elem.try_into()?; - Self::check_key_value(header) - } + // pub fn new(elem: &str) -> Result { + // let header: CredentialHeader = elem.try_into()?; + // Self::check_key_value(header) + // } - pub fn check_key_value(_header: CredentialHeader) -> crate::Result { - todo!() - } + // pub fn check_key_value(_header: CredentialHeader) -> Result { + // todo!() + // } pub fn is_expired(&self) -> bool { if self.expiration.is_none() { @@ -171,13 +171,18 @@ impl Credentials { } } +pub fn generate_credentials() -> Result<(String, String)> { + let ak = utils::gen_access_key(20)?; + let sk = utils::gen_secret_key(40)?; + Ok((ak, sk)) +} + pub fn get_new_credentials_with_metadata( claims: &T, token_secret: &str, exp: Option, -) -> crate::Result { - let ak = utils::gen_access_key(20).unwrap_or_default(); - let sk = utils::gen_secret_key(32).unwrap_or_default(); +) -> Result { + let (ak, sk) = generate_credentials()?; create_new_credentials_with_metadata(&ak, &sk, claims, token_secret, exp) } @@ -188,16 +193,16 @@ pub fn create_new_credentials_with_metadata( claims: &T, token_secret: &str, exp: Option, -) -> crate::Result { +) -> Result { if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN { - return Err(Error::InvalidAccessKeyLength); + return Err(Error::new(IamError::InvalidAccessKeyLength)); } if sk.len() < SECRET_KEY_MIN_LEN || sk.len() > SECRET_KEY_MAX_LEN { - return Err(Error::InvalidAccessKeyLength); + return Err(Error::new(IamError::InvalidAccessKeyLength)); } - let token = utils::generate_jwt(claims, token_secret).map_err(Error::JWTError)?; + let token = utils::generate_jwt(claims, token_secret)?; Ok(Credentials { access_key: ak.to_owned(), @@ -209,12 +214,17 @@ pub fn create_new_credentials_with_metadata( }) } -pub fn get_claims_from_token_with_secret(token: &str, secret: &str) -> crate::Result { - let ms = extract_claims::(token, secret).map_err(Error::JWTError)?; +pub fn get_claims_from_token_with_secret(token: &str, secret: &str) -> Result { + let ms = extract_claims::(token, secret)?; // TODO SessionPolicyName Ok(ms.claims) } +pub fn jwt_sign(claims: &T, token_secret: &str) -> Result { + let token = utils::generate_jwt(claims, token_secret)?; + Ok(token) +} + #[derive(Default)] pub struct CredentialsBuilder { session_policy: Option, @@ -284,30 +294,30 @@ impl CredentialsBuilder { self } - pub fn try_build(self) -> crate::Result { + pub fn try_build(self) -> Result { self.try_into() } } impl TryFrom for Credentials { - type Error = crate::Error; + type Error = Error; fn try_from(mut value: CredentialsBuilder) -> Result { if value.parent_user.is_empty() { - return Err(Error::InvalidArgument); + return Err(Error::new(IamError::InvalidArgument)); } if (value.access_key.is_empty() && !value.secret_key.is_empty()) || (!value.access_key.is_empty() && value.secret_key.is_empty()) { - return Err(Error::StringError("Either ak or sk is empty".into())); + return Err(Error::msg("Either ak or sk is empty")); } if value.parent_user == value.access_key.as_str() { - return Err(Error::InvalidArgument); + return Err(Error::new(IamError::InvalidArgument)); } if value.access_key == "site-replicator-0" && !value.allow_site_replicator_account { - return Err(Error::InvalidArgument); + return Err(Error::new(IamError::InvalidArgument)); } let mut claim = serde_json::json!({ @@ -316,9 +326,9 @@ impl TryFrom for Credentials { if let Some(p) = value.session_policy { p.is_valid()?; - let policy_buf = serde_json::to_vec(&p).map_err(|_| Error::InvalidArgument)?; + let policy_buf = serde_json::to_vec(&p).map_err(|_| Error::new(IamError::InvalidArgument))?; if policy_buf.len() > 4096 { - return Err(crate::Error::StringError("session policy is too large".into())); + return Err(Error::msg("session policy is too large")); } claim["sessionPolicy"] = serde_json::json!(base64_simd::STANDARD.encode_to_string(&policy_buf)); claim["sa-policy"] = serde_json::json!("embedded-policy"); @@ -355,21 +365,21 @@ impl TryFrom for Credentials { }; if !value.secret_key.is_empty() { - let session_token = crypto::jwt_encode(value.access_key.as_bytes(), &claim) - .map_err(|_| crate::Error::StringError("session policy is too large".into()))?; + let session_token = + crypto::jwt_encode(value.access_key.as_bytes(), &claim).map_err(|_| Error::msg("session policy is too large"))?; cred.session_token = session_token; // cred.expiration = Some( // OffsetDateTime::from_unix_timestamp( // claim // .get("exp") // .and_then(|x| x.as_i64()) - // .ok_or(crate::Error::StringError("invalid exp".into()))?, + // .ok_or(Error::StringError("invalid exp".into()))?, // ) - // .map_err(|_| crate::Error::StringError("invalie timestamp".into()))?, + // .map_err(|_| Error::StringError("invalie timestamp".into()))?, // ); } else { // cred.expiration = - // Some(OffsetDateTime::from_unix_timestamp(0).map_err(|_| crate::Error::StringError("invalie timestamp".into()))?); + // Some(OffsetDateTime::from_unix_timestamp(0).map_err(|_| Error::StringError("invalie timestamp".into()))?); } cred.expiration = value.expiration; @@ -380,66 +390,66 @@ impl TryFrom for Credentials { } } -#[cfg(test)] -#[allow(non_snake_case)] -mod tests { - use test_case::test_case; - use time::Date; +// #[cfg(test)] +// #[allow(non_snake_case)] +// mod tests { +// use test_case::test_case; +// use time::Date; - use super::CredentialHeader; - use super::CredentialHeaderScope; - use crate::service_type::ServiceType; +// use super::CredentialHeader; +// use super::CredentialHeaderScope; +// use crate::service_type::ServiceType; - #[test_case( - "Credential=aaaaaaaaaaaaaaaaaaaa/20241127/us-east-1/s3/aws4_request" => - CredentialHeader{ - access_key: "aaaaaaaaaaaaaaaaaaaa".into(), - scop: CredentialHeaderScope { - date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), - region: "us-east-1".to_owned(), - service: ServiceType::S3, - request: "aws4_request".into(), - } - }; - "1")] - #[test_case( - "Credential=aaaaaaaaaaa/aaaaaaaaa/20241127/us-east-1/s3/aws4_request" => - CredentialHeader{ - access_key: "aaaaaaaaaaa/aaaaaaaaa".into(), - scop: CredentialHeaderScope { - date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), - region: "us-east-1".to_owned(), - service: ServiceType::S3, - request: "aws4_request".into(), - } - }; - "2")] - #[test_case( - "Credential=aaaaaaaaaaa/aaaaaaaaa/20241127/us-east-1/sts/aws4_request" => - CredentialHeader{ - access_key: "aaaaaaaaaaa/aaaaaaaaa".into(), - scop: CredentialHeaderScope { - date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), - region: "us-east-1".to_owned(), - service: ServiceType::STS, - request: "aws4_request".into(), - } - }; - "3")] - fn test_CredentialHeader_from_str_successful(input: &str) -> CredentialHeader { - CredentialHeader::try_from(input).unwrap() - } +// #[test_case( +// "Credential=aaaaaaaaaaaaaaaaaaaa/20241127/us-east-1/s3/aws4_request" => +// CredentialHeader{ +// access_key: "aaaaaaaaaaaaaaaaaaaa".into(), +// scop: CredentialHeaderScope { +// date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), +// region: "us-east-1".to_owned(), +// service: ServiceType::S3, +// request: "aws4_request".into(), +// } +// }; +// "1")] +// #[test_case( +// "Credential=aaaaaaaaaaa/aaaaaaaaa/20241127/us-east-1/s3/aws4_request" => +// CredentialHeader{ +// access_key: "aaaaaaaaaaa/aaaaaaaaa".into(), +// scop: CredentialHeaderScope { +// date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), +// region: "us-east-1".to_owned(), +// service: ServiceType::S3, +// request: "aws4_request".into(), +// } +// }; +// "2")] +// #[test_case( +// "Credential=aaaaaaaaaaa/aaaaaaaaa/20241127/us-east-1/sts/aws4_request" => +// CredentialHeader{ +// access_key: "aaaaaaaaaaa/aaaaaaaaa".into(), +// scop: CredentialHeaderScope { +// date: Date::from_calendar_date(2024, time::Month::November, 27).unwrap(), +// region: "us-east-1".to_owned(), +// service: ServiceType::STS, +// request: "aws4_request".into(), +// } +// }; +// "3")] +// fn test_CredentialHeader_from_str_successful(input: &str) -> CredentialHeader { +// CredentialHeader::try_from(input).unwrap() +// } - #[test_case("Credential")] - #[test_case("Cred=")] - #[test_case("Credential=abc")] - #[test_case("Credential=a/20241127/us-east-1/s3/aws4_request")] - #[test_case("Credential=aa/20241127/us-east-1/s3/aws4_request")] - #[test_case("Credential=aaaa/20241127/us-east-1/asa/aws4_request")] - #[test_case("Credential=aaaa/20241127/us-east-1/sts/aws4a_request")] - fn test_CredentialHeader_from_str_failed(input: &str) { - if CredentialHeader::try_from(input).is_ok() { - unreachable!() - } - } -} +// #[test_case("Credential")] +// #[test_case("Cred=")] +// #[test_case("Credential=abc")] +// #[test_case("Credential=a/20241127/us-east-1/s3/aws4_request")] +// #[test_case("Credential=aa/20241127/us-east-1/s3/aws4_request")] +// #[test_case("Credential=aaaa/20241127/us-east-1/asa/aws4_request")] +// #[test_case("Credential=aaaa/20241127/us-east-1/sts/aws4a_request")] +// fn test_credential_header_from_str_failed(input: &str) { +// if CredentialHeader::try_from(input).is_ok() { +// unreachable!() +// } +// } +// } diff --git a/iam/src/cache.rs b/iam/src/cache.rs index c0b3487d..b4394445 100644 --- a/iam/src/cache.rs +++ b/iam/src/cache.rs @@ -11,8 +11,9 @@ use time::OffsetDateTime; use crate::{ auth::UserIdentity, - policy::{Args, GroupInfo, MappedPolicy, Policy, PolicyDoc}, - Error, + policy::PolicyDoc, + store::{GroupInfo, MappedPolicy}, + sys::Args, }; pub struct Cache { @@ -85,6 +86,21 @@ impl Cache { map.remove(key); }) } + + pub fn build_user_group_memberships(&self) { + let groups = self.groups.load(); + let mut user_group_memeberships = HashMap::new(); + for (group_name, group) in groups.iter() { + for user_name in &group.members { + user_group_memeberships + .entry(user_name.clone()) + .or_insert_with(HashSet::new) + .insert(group_name.clone()); + } + } + self.user_group_memeberships + .store(Arc::new(CacheEntity::new(user_group_memeberships))); + } } impl CacheInner { @@ -93,37 +109,37 @@ impl CacheInner { self.users.get(user_name).or_else(|| self.sts_accounts.get(user_name)) } - fn get_policy(&self, _name: &str, _groups: &[String]) -> crate::Result> { - todo!() - } + // fn get_policy(&self, _name: &str, _groups: &[String]) -> crate::Result> { + // todo!() + // } - /// 如果是临时用户,返回Ok(Some(partent_name))) - /// 如果不是临时用户,返回Ok(None) - fn is_temp_user(&self, user_name: &str) -> crate::Result> { - let user = self - .get_user(user_name) - .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; + // /// 如果是临时用户,返回Ok(Some(partent_name))) + // /// 如果不是临时用户,返回Ok(None) + // fn is_temp_user(&self, user_name: &str) -> crate::Result> { + // let user = self + // .get_user(user_name) + // .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; - if user.credentials.is_temp() { - Ok(Some(&user.credentials.parent_user)) - } else { - Ok(None) - } - } + // if user.credentials.is_temp() { + // Ok(Some(&user.credentials.parent_user)) + // } else { + // Ok(None) + // } + // } - /// 如果是临时用户,返回Ok(Some(partent_name))) - /// 如果不是临时用户,返回Ok(None) - fn is_service_account(&self, user_name: &str) -> crate::Result> { - let user = self - .get_user(user_name) - .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; + // /// 如果是临时用户,返回Ok(Some(partent_name))) + // /// 如果不是临时用户,返回Ok(None) + // fn is_service_account(&self, user_name: &str) -> crate::Result> { + // let user = self + // .get_user(user_name) + // .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; - if user.credentials.is_service_account() { - Ok(Some(&user.credentials.parent_user)) - } else { - Ok(None) - } - } + // if user.credentials.is_service_account() { + // Ok(Some(&user.credentials.parent_user)) + // } else { + // Ok(None) + // } + // } // todo pub fn is_allowed_sts(&self, _args: &Args, _parent: &str) -> bool { diff --git a/iam/src/error.rs b/iam/src/error.rs index 7a443f82..0244f96a 100644 --- a/iam/src/error.rs +++ b/iam/src/error.rs @@ -17,9 +17,24 @@ pub enum Error { #[error("user '{0}' does not exist")] NoSuchUser(String), + #[error("account '{0}' does not exist")] + NoSuchAccount(String), + + #[error("service account '{0}' does not exist")] + NoSuchServiceAccount(String), + + #[error("temp account '{0}' does not exist")] + NoSuchTempAccount(String), + #[error("group '{0}' does not exist")] NoSuchGroup(String), + #[error("policy does not exist")] + NoSuchPolicy, + + #[error("policy in use")] + PolicyInUse, + #[error("group not empty")] GroupNotEmpty, @@ -47,6 +62,9 @@ pub enum Error { #[error("access key contains reserved characters =,")] ContainsReservedChars, + #[error("group name contains reserved characters =,")] + GroupNameContainsReservedChars, + #[error("jwt err {0}")] JWTError(jsonwebtoken::errors::Error), @@ -60,10 +78,57 @@ pub enum Error { InvalidAccessKey, #[error("action not allowed")] IAMActionNotAllowed, + + #[error("no secret key with access key")] + NoSecretKeyWithAccessKey, + + #[error("no access key with secret key")] + NoAccessKeyWithSecretKey, + + #[error("policy too large")] + PolicyTooLarge, } -pub type Result = std::result::Result; +// pub fn is_err_no_such_user(e: &Error) -> bool { +// matches!(e, Error::NoSuchUser(_)) +// } -pub fn is_err_no_such_user(e: &Error) -> bool { - matches!(e, Error::NoSuchUser(_)) +pub fn is_err_no_such_policy(err: &ecstore::error::Error) -> bool { + if let Some(e) = err.downcast_ref::() { + matches!(e, Error::NoSuchPolicy) + } else { + false + } +} + +pub fn is_err_no_such_user(err: &ecstore::error::Error) -> bool { + if let Some(e) = err.downcast_ref::() { + matches!(e, Error::NoSuchUser(_)) + } else { + false + } +} + +pub fn is_err_no_such_account(err: &ecstore::error::Error) -> bool { + if let Some(e) = err.downcast_ref::() { + matches!(e, Error::NoSuchAccount(_)) + } else { + false + } +} + +pub fn is_err_no_such_temp_account(err: &ecstore::error::Error) -> bool { + if let Some(e) = err.downcast_ref::() { + matches!(e, Error::NoSuchTempAccount(_)) + } else { + false + } +} + +pub fn is_err_no_such_group(err: &ecstore::error::Error) -> bool { + if let Some(e) = err.downcast_ref::() { + matches!(e, Error::NoSuchGroup(_)) + } else { + false + } } diff --git a/iam/src/format.rs b/iam/src/format.rs index 619db78e..4c95f0b6 100644 --- a/iam/src/format.rs +++ b/iam/src/format.rs @@ -1,17 +1,17 @@ use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Default)] pub struct Format { pub version: i32, } -impl Format { - pub const PATH: &str = "config/iam/config/format.json"; - pub const DEFAULT_VERSION: i32 = 1; +// impl Format { +// pub const PATH: &str = "config/iam/config/format.json"; +// pub const DEFAULT_VERSION: i32 = 1; - pub fn new() -> Self { - Self { - version: Self::DEFAULT_VERSION, - } - } -} +// pub fn new() -> Self { +// Self { +// version: Self::DEFAULT_VERSION, +// } +// } +// } diff --git a/iam/src/handler.rs b/iam/src/handler.rs index 50557f41..1166c687 100644 --- a/iam/src/handler.rs +++ b/iam/src/handler.rs @@ -1,154 +1,154 @@ -use std::{borrow::Cow, collections::HashMap}; +// use std::{borrow::Cow, collections::HashMap}; -use log::{info, warn}; +// use log::{info, warn}; -use crate::{ - arn::ARN, - auth::UserIdentity, - cache::CacheInner, - policy::{utils::get_values_from_claims, Args, Policy}, - store::Store, - Error, -}; +// use crate::{ +// arn::ARN, +// auth::UserIdentity, +// cache::CacheInner, +// policy::{utils::get_values_from_claims, Args, Policy}, +// store::Store, +// Error, +// }; -pub(crate) struct Handler<'m, T> { - cache: CacheInner, - api: &'m T, - roles: &'m HashMap>, -} +// pub(crate) struct Handler<'m, T> { +// cache: CacheInner, +// api: &'m T, +// roles: &'m HashMap>, +// } -impl<'m, T> Handler<'m, T> { - pub fn new(cache: CacheInner, api: &'m T, roles: &'m HashMap>) -> Self { - Self { cache, api, roles } - } -} +// impl<'m, T> Handler<'m, T> { +// pub fn new(cache: CacheInner, api: &'m T, roles: &'m HashMap>) -> Self { +// Self { cache, api, roles } +// } +// } -impl<'m, T> Handler<'m, T> -where - T: Store, -{ - #[inline] - fn get_user<'a>(&self, user_name: &'a str) -> Option<&UserIdentity> { - self.cache - .users - .get(user_name) - .or_else(|| self.cache.sts_accounts.get(user_name)) - } +// impl<'m, T> Handler<'m, T> +// where +// T: Store, +// { +// #[inline] +// fn get_user<'a>(&self, user_name: &'a str) -> Option<&UserIdentity> { +// self.cache +// .users +// .get(user_name) +// .or_else(|| self.cache.sts_accounts.get(user_name)) +// } - async fn get_policy(&self, name: &str, _groups: &[String]) -> crate::Result> { - if name.is_empty() { - return Err(Error::InvalidArgument); - } +// async fn get_policy(&self, name: &str, _groups: &[String]) -> crate::Result> { +// if name.is_empty() { +// return Err(Error::InvalidArgument); +// } - todo!() - // self.api.policy_db_get(name, groups) - } +// todo!() +// // self.api.policy_db_get(name, groups) +// } - /// 如果是临时用户,返回Ok(Some(partent_name))) - /// 如果不是临时用户,返回Ok(None) - fn is_temp_user<'a>(&self, user_name: &'a str) -> crate::Result> { - let user = self - .get_user(user_name) - .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; +// /// 如果是临时用户,返回Ok(Some(partent_name))) +// /// 如果不是临时用户,返回Ok(None) +// fn is_temp_user<'a>(&self, user_name: &'a str) -> crate::Result> { +// let user = self +// .get_user(user_name) +// .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; - if user.credentials.is_temp() { - Ok(Some(&user.credentials.parent_user)) - } else { - Ok(None) - } - } +// if user.credentials.is_temp() { +// Ok(Some(&user.credentials.parent_user)) +// } else { +// Ok(None) +// } +// } - /// 如果是临时用户,返回Ok(Some(partent_name))) - /// 如果不是临时用户,返回Ok(None) - fn is_service_account<'a>(&self, user_name: &'a str) -> crate::Result> { - let user = self - .get_user(user_name) - .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; +// /// 如果是临时用户,返回Ok(Some(partent_name))) +// /// 如果不是临时用户,返回Ok(None) +// fn is_service_account<'a>(&self, user_name: &'a str) -> crate::Result> { +// let user = self +// .get_user(user_name) +// .ok_or_else(|| Error::NoSuchUser(user_name.to_owned()))?; - if user.credentials.is_service_account() { - Ok(Some(&user.credentials.parent_user)) - } else { - Ok(None) - } - } +// if user.credentials.is_service_account() { +// Ok(Some(&user.credentials.parent_user)) +// } else { +// Ok(None) +// } +// } - // todo - pub fn is_allowed_sts(&self, args: &Args, parent: &str) -> bool { - warn!("unimplement is_allowed_sts"); - false - } +// // todo +// pub fn is_allowed_sts(&self, args: &Args, parent: &str) -> bool { +// warn!("unimplement is_allowed_sts"); +// false +// } - // todo - pub async fn is_allowed_service_account<'a>(&self, args: &Args<'a>, parent: &str) -> bool { - let Some(p) = args.claims.get(parent) else { - return false; - }; +// // todo +// pub async fn is_allowed_service_account<'a>(&self, args: &Args<'a>, parent: &str) -> bool { +// let Some(p) = args.claims.get(parent) else { +// return false; +// }; - if let Some(parent_in_chaim) = p.as_str() { - if parent_in_chaim != parent { - return false; - } - } else { - return false; - } +// if let Some(parent_in_chaim) = p.as_str() { +// if parent_in_chaim != parent { +// return false; +// } +// } else { +// return false; +// } - let is_owner_derived = parent == "rustfsadmin"; // todo ,使用全局变量 - let role_arn = args.get_role_arn(); - let mut svc_policies = None; +// let is_owner_derived = parent == "rustfsadmin"; // todo ,使用全局变量 +// let role_arn = args.get_role_arn(); +// let mut svc_policies = None; - if is_owner_derived { - } else if let Some(x) = role_arn { - let Ok(arn) = x.parse::() else { - info!("error parsing role ARN {x}"); - return false; - }; +// if is_owner_derived { +// } else if let Some(x) = role_arn { +// let Ok(arn) = x.parse::() else { +// info!("error parsing role ARN {x}"); +// return false; +// }; - svc_policies = self.roles.get(&arn).map(|x| Cow::from(x)); - } else { - let Ok(mut p) = self.get_policy(parent, &args.groups[..]).await else { return false }; - if p.is_empty() { - // todo iamPolicyClaimNameOpenID - let (p1, _) = get_values_from_claims(&args.claims, ""); - p = p1; - } - svc_policies = Some(Cow::Owned(p)); - } +// svc_policies = self.roles.get(&arn).map(|x| Cow::from(x)); +// } else { +// let Ok(mut p) = self.get_policy(parent, &args.groups[..]).await else { return false }; +// if p.is_empty() { +// // todo iamPolicyClaimNameOpenID +// let (p1, _) = get_values_from_claims(&args.claims, ""); +// p = p1; +// } +// svc_policies = Some(Cow::Owned(p)); +// } - if is_owner_derived && svc_policies.as_ref().map(|x| x.as_ref().len()).unwrap_or_default() == 0 { - return false; - } +// if is_owner_derived && svc_policies.as_ref().map(|x| x.as_ref().len()).unwrap_or_default() == 0 { +// return false; +// } - false - } +// false +// } - pub async fn get_combined_policy(&self, _policies: &[String]) -> Policy { - todo!() - } +// pub async fn get_combined_policy(&self, _policies: &[String]) -> Policy { +// todo!() +// } - pub async fn is_allowed<'a>(&self, args: Args<'a>) -> bool { - if args.is_owner { - return true; - } +// pub async fn is_allowed<'a>(&self, args: Args<'a>) -> bool { +// if args.is_owner { +// return true; +// } - match self.is_temp_user(&args.account) { - Ok(Some(parent)) => return self.is_allowed_sts(&args, parent), - Err(_) => return false, - _ => {} - } +// match self.is_temp_user(&args.account) { +// Ok(Some(parent)) => return self.is_allowed_sts(&args, parent), +// Err(_) => return false, +// _ => {} +// } - match self.is_service_account(&args.account) { - Ok(Some(parent)) => return self.is_allowed_service_account(&args, parent).await, - Err(_) => return false, - _ => {} - } +// match self.is_service_account(&args.account) { +// Ok(Some(parent)) => return self.is_allowed_service_account(&args, parent).await, +// Err(_) => return false, +// _ => {} +// } - let Ok(policies) = self.get_policy(&args.account, &args.groups).await else { return false }; +// let Ok(policies) = self.get_policy(&args.account, &args.groups).await else { return false }; - if policies.is_empty() { - return false; - } +// if policies.is_empty() { +// return false; +// } - let policy = self.get_combined_policy(&policies[..]).await; - policy.is_allowed(&args) - } -} +// let policy = self.get_combined_policy(&policies[..]).await; +// policy.is_allowed(&args) +// } +// } diff --git a/iam/src/lib.rs b/iam/src/lib.rs index dd4ab6f9..65484724 100644 --- a/iam/src/lib.rs +++ b/iam/src/lib.rs @@ -1,15 +1,12 @@ -use auth::{contains_reserved_chars, is_access_key_valid, is_secret_key_valid, Credentials, UserIdentity}; +use auth::Credentials; +use ecstore::error::{Error, Result}; use ecstore::store::ECStore; +use error::Error as IamError; use log::debug; -use madmin::AccountStatus; use manager::IamCache; -use policy::{Args, Policy, UserType}; -use std::{ - collections::HashMap, - sync::{Arc, OnceLock}, -}; +use std::sync::{Arc, OnceLock}; use store::object::ObjectStore; -use time::OffsetDateTime; +use sys::IamSys; pub mod cache; mod format; @@ -24,9 +21,9 @@ pub mod service_type; pub mod store; pub mod utils; -pub use error::{Error, Result}; +pub mod sys; -static IAM_SYS: OnceLock>> = OnceLock::new(); +static IAM_SYS: OnceLock>> = OnceLock::new(); static GLOBAL_ACTIVE_CRED: OnceLock = OnceLock::new(); @@ -53,96 +50,26 @@ pub fn init_global_action_cred(ak: Option, sk: Option) -> Result secret_key: sk, ..Default::default() }) - .map_err(|_e| Error::CredNotInitialized) + .unwrap(); + Ok(()) } pub fn get_global_action_cred() -> Option { GLOBAL_ACTIVE_CRED.get().cloned() } -pub async fn init_iam_sys(ecstore: Arc) -> crate::Result<()> { +pub async fn init_iam_sys(ecstore: Arc) -> Result<()> { debug!("init iam system"); let s = IamCache::new(ObjectStore::new(ecstore)).await; - IAM_SYS.get_or_init(move || s); + + IAM_SYS.get_or_init(move || IamSys::new(s).into()); Ok(()) } #[inline] -pub fn get() -> crate::Result>> { - IAM_SYS.get().map(Arc::clone).ok_or(Error::IamSysNotInitialized) -} - -pub async fn is_allowed(args: Args<'_>) -> crate::Result { - Ok(get()?.is_allowed(args).await) -} - -pub async fn get_service_account(ak: &str) -> crate::Result<(Credentials, Option)> { - let (mut sa, policy) = get()?.get_service_account(ak).await?; - - sa.credentials.secret_key.clear(); - sa.credentials.access_key.clear(); - - Ok((sa.credentials, policy)) -} - -pub async fn add_service_account(cred: Credentials) -> crate::Result { - get()?.add_service_account(cred).await -} - -pub async fn check_key(ak: &str) -> crate::Result<(Option, bool)> { - if let Some(sys_cred) = get_global_action_cred() { - if sys_cred.access_key == ak { - return Ok((Some(UserIdentity::new(sys_cred)), true)); - } - } - get()?.check_key(ak).await -} - -pub async fn list_users() -> crate::Result> { - get()?.get_users().await -} - -pub async fn get_user(ak: &str) -> crate::Result<(Option, bool)> { - get()?.check_key(ak).await -} - -pub async fn create_user(ak: &str, sk: &str, status: &str) -> crate::Result { - if !is_access_key_valid(ak) { - return Err(Error::InvalidAccessKeyLength); - } - - if contains_reserved_chars(ak) { - return Err(Error::ContainsReservedChars); - } - - if !is_secret_key_valid(sk) { - return Err(Error::InvalidSecretKeyLength); - } - get()?.add_user(ak, sk, status).await - // notify -} - -pub async fn delete_user(ak: &str, _notify: bool) -> crate::Result<()> { - get()?.delete_user(ak, UserType::Reg).await - // TODO NOTIFY -} - -pub async fn is_temp_user(ak: &str) -> crate::Result<(bool, String)> { - get()?.is_temp_user(ak).await -} - -pub async fn get_user_info(ak: &str) -> crate::Result { - get()?.get_user_info(ak).await -} - -pub async fn set_user_status(ak: &str, status: AccountStatus) -> crate::Result { - get()?.set_user_status(ak, status).await -} - -pub async fn list_service_accounts(ak: &str) -> crate::Result> { - get()?.list_service_accounts(ak).await -} - -pub async fn remove_users_from_group(group: &str, members: Vec) -> crate::Result { - get()?.remove_users_from_group(group, members).await +pub fn get() -> Result>> { + IAM_SYS + .get() + .map(Arc::clone) + .ok_or(Error::new(IamError::IamSysNotInitialized)) } diff --git a/iam/src/manager.rs b/iam/src/manager.rs index 89a790cc..70207567 100644 --- a/iam/src/manager.rs +++ b/iam/src/manager.rs @@ -1,15 +1,33 @@ +use crate::{ + arn::ARN, + auth::{self, get_claims_from_token_with_secret, is_secret_key_valid, jwt_sign, Credentials, UserIdentity}, + cache::{Cache, CacheEntity}, + error::{is_err_no_such_group, is_err_no_such_policy, is_err_no_such_user, Error as IamError}, + format::Format, + policy::{Policy, PolicyDoc, DEFAULT_POLICIES}, + store::{object::IAM_CONFIG_PREFIX, GroupInfo, MappedPolicy, Store, UserType}, + sys::{ + iam_policy_claim_name_sa, UpdateServiceAccountOpts, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, + MAX_SVCSESSION_POLICY_SIZE, SESSION_POLICY_NAME, SESSION_POLICY_NAME_EXTRACTED, STATUS_DISABLED, STATUS_ENABLED, + }, +}; +use ecstore::utils::{crypto::base64_encode, path::path_join_buf}; +use ecstore::{ + config::error::is_err_config_not_found, + error::{Error, Result}, +}; +use log::{debug, warn}; +use madmin::{AccountStatus, GroupDesc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, sync::{ atomic::{AtomicBool, AtomicI64, Ordering}, Arc, }, time::Duration, }; - -use ecstore::store_err::is_err_object_not_found; -use log::{debug, warn}; -use madmin::AccountStatus; use time::OffsetDateTime; use tokio::{ select, @@ -18,17 +36,27 @@ use tokio::{ mpsc::{Receiver, Sender}, }, }; +use tracing::error; -use crate::{ - arn::ARN, - auth::{self, Credentials, UserIdentity}, - cache::Cache, - format::Format, - handler::Handler, - policy::{Args, Policy, UserType}, - store::Store, - Error, -}; +const IAM_FORMAT_FILE: &str = "format.json"; +const IAM_FORMAT_VERSION_1: i32 = 1; + +#[derive(Serialize, Deserialize)] +struct IAMFormat { + version: i32, +} + +impl IAMFormat { + fn new_version_1() -> Self { + IAMFormat { + version: IAM_FORMAT_VERSION_1, + } + } +} + +fn get_iam_format_file_path() -> String { + path_join_buf(&[&IAM_CONFIG_PREFIX, IAM_FORMAT_FILE]) +} pub struct IamCache { pub cache: Cache, @@ -59,7 +87,7 @@ where sys } - async fn init(self: Arc, reciver: Receiver) -> crate::Result<()> { + async fn init(self: Arc, reciver: Receiver) -> Result<()> { self.clone().save_iam_formatter().await?; self.clone().load().await?; @@ -72,14 +100,19 @@ where loop { select! { _ = ticker.tick() => { - s.clone().load().await.unwrap(); + if let Err(err) =s.clone().load().await{ + error!("iam load err {:?}", err); + } }, i = reciver.recv() => { match i { Some(t) => { let last = s.last_timestamp.load(Ordering::Relaxed); if last <= t { - s.clone().load().await.unwrap(); + + if let Err(err) =s.clone().load().await{ + error!("iam load err {:?}", err); + } ticker.reset(); } }, @@ -98,7 +131,7 @@ where self.send_chan.send(OffsetDateTime::now_utc().unix_timestamp()).await.unwrap(); } - async fn load(self: Arc) -> crate::Result<()> { + async fn load(self: Arc) -> Result<()> { debug!("load iam to cache"); self.api.load_all(&self.cache).await?; self.last_timestamp @@ -106,26 +139,295 @@ where Ok(()) } - pub async fn list_all_iam_config_items(&self) -> crate::Result>> { - todo!() - } - // todo, 判断是否存在,是否可以重试 #[tracing::instrument(level = "debug", skip(self))] - async fn save_iam_formatter(self: Arc) -> crate::Result<()> { - match self.api.load_iam_config::(Format::PATH).await { - Ok((format, _)) if format.version >= 1 => return Ok(()), - Err(Error::EcstoreError(e)) if !is_err_object_not_found(&e) => { - return Err(Error::EcstoreError(e)); + async fn save_iam_formatter(self: Arc) -> Result<()> { + let path = get_iam_format_file_path(); + + let iam_fmt: Format = match self.api.load_iam_config(&path).await { + Ok(v) => v, + Err(err) => { + if !is_err_config_not_found(&err) { + return Err(err); + } + + Format::default() } - _ => {} + }; + + if iam_fmt.version >= IAM_FORMAT_VERSION_1 { + return Ok(()); } - self.api.save_iam_config(Format::new(), Format::PATH).await?; + self.api.save_iam_config(IAMFormat::new_version_1(), path).await + } + pub async fn get_user(&self, access_key: &str) -> Option { + self.cache + .users + .load() + .get(access_key) + .cloned() + .or_else(|| self.cache.sts_accounts.load().get(access_key).cloned()) + } + + pub async fn get_mapped_policy(&self, name: &str, is_group: bool) -> Option { + if is_group { + self.cache.group_policies.load().get(name).cloned() + } else { + self.cache.user_policies.load().get(name).cloned() + } + } + + pub async fn get_policy(&self, name: &str) -> Result { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + let policies = MappedPolicy::new(name).to_slice(); + + let mut to_merge = Vec::new(); + for policy in policies { + if policy.is_empty() { + continue; + } + + let v = self + .cache + .policy_docs + .load() + .get(&policy) + .cloned() + .ok_or(Error::new(IamError::NoSuchPolicy))?; + + to_merge.push(v.policy); + } + + if to_merge.is_empty() { + return Err(Error::new(IamError::NoSuchPolicy)); + } + + Ok(Policy::merge_policies(to_merge)) + } + + pub async fn get_policy_doc(&self, name: &str) -> Result { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + self.cache + .policy_docs + .load() + .get(name) + .cloned() + .ok_or(Error::new(IamError::NoSuchPolicy)) + } + + pub async fn delete_policy(&self, name: &str, is_from_notify: bool) -> Result<()> { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + if is_from_notify { + let user_policy_cache = self.cache.user_policies.load(); + let group_policy_cache = self.cache.group_policies.load(); + let users_cache = self.cache.users.load(); + + let mut users = Vec::new(); + user_policy_cache.iter().for_each(|(k, v)| { + if !users_cache.contains_key(k) { + Cache::delete(&self.cache.user_policies, k, OffsetDateTime::now_utc()); + return; + } + + if v.policy_set().contains(name) { + users.push(k.to_owned()); + } + }); + + let mut groups = Vec::new(); + group_policy_cache.iter().for_each(|(k, v)| { + if v.policy_set().contains(name) { + groups.push(k.to_owned()); + } + }); + + if !users.is_empty() || !groups.is_empty() { + return Err(IamError::PolicyInUse.into()); + } + + if let Err(err) = self.api.delete_policy_doc(name).await { + if !is_err_no_such_policy(&err) { + Cache::delete(&self.cache.policy_docs, name, OffsetDateTime::now_utc()); + return Ok(()); + } + + return Err(err); + } + } + + Cache::delete(&self.cache.policy_docs, name, OffsetDateTime::now_utc()); + Ok(()) } - pub async fn list_service_accounts(&self, access_key: &str) -> crate::Result> { + pub async fn set_policy(&self, name: &str, policy: Policy) -> Result { + if name.is_empty() || policy.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + let policy_doc = self + .cache + .policy_docs + .load() + .get(name) + .map(|v| { + let mut p = v.clone(); + p.update(policy.clone()); + p + }) + .unwrap_or(PolicyDoc::new(policy)); + + self.api.save_policy_doc(name, policy_doc.clone()).await?; + + let now = OffsetDateTime::now_utc(); + + Cache::add_or_update(&self.cache.policy_docs, name, &policy_doc, now); + + Ok(now) + } + + pub async fn list_polices(&self, bucket_name: &str) -> Result> { + let mut m = HashMap::new(); + + self.api.load_policy_docs(&mut m).await?; + set_default_canned_policies(&mut m); + + let cache = CacheEntity::new(m.clone()).update_load_time(); + + self.cache.policy_docs.store(Arc::new(cache)); + + let ret = m + .into_iter() + .filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name)) + .map(|(k, v)| (k, v.policy)) + .collect(); + + Ok(ret) + } + + pub async fn merge_policies(&self, name: &str) -> (String, Policy) { + let mut policies = Vec::new(); + let mut to_merge = Vec::new(); + let mut miss_policies = Vec::new(); + + for policy in MappedPolicy::new(name).to_slice() { + if policy.is_empty() { + continue; + } + + if let Some(v) = self.cache.policy_docs.load().get(&policy).cloned() { + policies.push(policy); + to_merge.push(v.policy); + } else { + miss_policies.push(policy); + } + } + + if !miss_policies.is_empty() { + let mut m = HashMap::new(); + for policy in miss_policies { + let _ = self.api.load_policy_doc(&policy, &mut m).await; + } + + for (k, v) in m.iter() { + Cache::add_or_update(&self.cache.policy_docs, k, v, OffsetDateTime::now_utc()); + + policies.push(k.clone()); + to_merge.push(v.policy.clone()); + } + } + + (policies.join(","), Policy::merge_policies(to_merge)) + } + + pub async fn list_policy_docs(&self, bucket_name: &str) -> Result> { + let mut m = HashMap::new(); + + self.api.load_policy_docs(&mut m).await?; + set_default_canned_policies(&mut m); + + let cache = CacheEntity::new(m.clone()).update_load_time(); + + self.cache.policy_docs.store(Arc::new(cache)); + + let ret = m + .into_iter() + .filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name)) + .collect(); + + Ok(ret) + } + + pub async fn list_policy_docs_internal(&self, bucket_name: &str) -> Result> { + let ret = self + .cache + .policy_docs + .load() + .iter() + .filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + Ok(ret) + } + + pub async fn list_temp_accounts(&self, access_key: &str) -> Result> { + let users = self.cache.users.load(); + let mut user_exists = false; + let mut ret = Vec::new(); + + for (_, v) in users.iter() { + let is_derived = v.credentials.is_service_account() || v.credentials.is_temp(); + + if !is_derived && v.credentials.access_key.as_str() == access_key { + user_exists = true; + } else if is_derived && v.credentials.parent_user == access_key { + user_exists = true; + if v.credentials.is_temp() { + let mut u = v.clone(); + u.credentials.secret_key = String::new(); + u.credentials.session_token = String::new(); + + ret.push(u); + } + } + } + + if !user_exists { + return Err(Error::new(IamError::NoSuchUser(access_key.to_string()))); + } + + Ok(ret) + } + + pub async fn list_sts_accounts(&self, access_key: &str) -> Result> { + let users = self.cache.users.load(); + Ok(users + .values() + .filter_map(|x| { + if !access_key.is_empty() && x.credentials.parent_user.as_str() == access_key && x.credentials.is_temp() { + let mut c = x.credentials.clone(); + c.secret_key = String::new(); + c.session_token = String::new(); + return Some(c); + } + + None + }) + .collect()) + } + + pub async fn list_service_accounts(&self, access_key: &str) -> Result> { let users = self.cache.users.load(); Ok(users .values() @@ -146,133 +448,397 @@ where } /// create a service account and update cache - pub async fn add_service_account(&self, cred: Credentials) -> crate::Result { - if cred.parent_user.is_empty() { - return Err(Error::InvalidArgument); - } - - if (cred.access_key.is_empty() && !cred.secret_key.is_empty()) - || (!cred.access_key.is_empty() && cred.secret_key.is_empty()) - { - return Err(Error::StringError("Either ak or sk is empty".into())); + pub async fn add_service_account(&self, cred: Credentials) -> Result { + if cred.access_key.is_empty() || cred.parent_user.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); } let users = self.cache.users.load(); if let Some(x) = users.get(&cred.access_key) { - if x.credentials.parent_user.as_str() != cred.parent_user.as_str() { - return Err(crate::Error::StringError("access key is taken by another user".into())); - } - return Err(crate::Error::StringError("access key already taken".into())); - } - - if let Some(x) = users.get(&cred.parent_user) { if x.credentials.is_service_account() { - return Err(crate::Error::StringError( - "unable to create a service account for another service account".into(), - )); + return Err(Error::new(IamError::IAMActionNotAllowed)); } } - let user_entiry = UserIdentity::from(cred); - let path = format!( - "config/iam/{}{}/identity.json", - UserType::Svc.prefix(), - user_entiry.credentials.access_key - ); - debug!("save object: {path:?}"); - self.api.save_iam_config(&user_entiry, path).await?; + let u = UserIdentity::new(cred); - Cache::add_or_update( - &self.cache.users, - &user_entiry.credentials.access_key, - &user_entiry, - OffsetDateTime::now_utc(), - ); + self.api + .save_user_identity(&u.credentials.access_key, UserType::Svc, u.clone(), None) + .await?; - Ok(user_entiry.update_at.unwrap_or(OffsetDateTime::now_utc())) + self.update_user_with_claims(&u.credentials.access_key, u.clone())?; + + Ok(OffsetDateTime::now_utc()) } - pub async fn is_allowed<'a>(&self, args: Args<'a>) -> bool { - let handler = Handler::new((&self.cache).into(), &self.api, &self.roles); - handler.is_allowed(args).await - } - - pub async fn get_service_account(&self, ak: &str) -> crate::Result<(UserIdentity, Option)> { - let user = self.cache.users.load(); - let Some(u) = user.get(ak) else { - return Err(Error::StringError("no service account".into())); + pub async fn update_service_account(&self, name: &str, opts: UpdateServiceAccountOpts) -> Result { + let Some(ui) = self.cache.users.load().get(name).cloned() else { + return Err(IamError::NoSuchServiceAccount(name.to_string()).into()); }; - // if !u.credentials.is_service_account() { - // return Err(Error::StringError("it is not service account".into())); - // } + if !ui.credentials.is_service_account() { + return Err(IamError::NoSuchServiceAccount(name.to_string()).into()); + } - Ok((u.clone(), None)) + let mut cr = ui.credentials.clone(); + let current_secret_key = cr.secret_key.clone(); + + if !opts.secret_key.is_empty() { + if !is_secret_key_valid(&opts.secret_key) { + return Err(IamError::InvalidSecretKeyLength.into()); + } + + cr.secret_key = opts.secret_key; + } + + if opts.name.is_some() { + cr.name = opts.name; + } + + if opts.description.is_some() { + cr.description = opts.description; + } + + if opts.expiration.is_some() { + // TODO: check expiration + cr.expiration = opts.expiration; + } + + if let Some(status) = opts.status { + match status.as_str() { + val if val == AccountStatus::Enabled.as_ref() => cr.status = auth::ACCOUNT_ON.to_owned(), + val if val == AccountStatus::Disabled.as_ref() => cr.status = auth::ACCOUNT_OFF.to_owned(), + auth::ACCOUNT_ON => cr.status = auth::ACCOUNT_ON.to_owned(), + auth::ACCOUNT_OFF => cr.status = auth::ACCOUNT_OFF.to_owned(), + _ => cr.status = auth::ACCOUNT_OFF.to_owned(), + } + } + + let mut m: HashMap = get_claims_from_token_with_secret(&cr.session_token, ¤t_secret_key)?; + m.remove(SESSION_POLICY_NAME_EXTRACTED); + + let nosp = if let Some(policy) = &opts.session_policy { + policy.version.is_empty() && policy.statements.is_empty() + } else { + false + }; + + if m.contains_key(SESSION_POLICY_NAME) && nosp { + m.remove(SESSION_POLICY_NAME); + m.insert(iam_policy_claim_name_sa(), serde_json::Value::String(INHERITED_POLICY_TYPE.to_owned())); + } + + if let Some(session_policy) = &opts.session_policy { + session_policy.validate()?; + if !session_policy.version.is_empty() && !session_policy.statements.is_empty() { + let policy_buf = serde_json::to_vec(&session_policy)?; + if policy_buf.len() > MAX_SVCSESSION_POLICY_SIZE { + return Err(IamError::PolicyTooLarge.into()); + } + + 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())); + } + } + + m.insert("accessKey".to_owned(), serde_json::Value::String(name.to_owned())); + + cr.session_token = jwt_sign(&m, &cr.secret_key)?; + + let u = UserIdentity::new(cr); + self.api + .save_user_identity(&u.credentials.access_key, UserType::Svc, u.clone(), None) + .await?; + self.update_user_with_claims(&u.credentials.access_key, u.clone())?; + + Ok(OffsetDateTime::now_utc()) } - pub async fn check_key(&self, ak: &str) -> crate::Result<(Option, bool)> { - let user = self - .cache - .users - .load() - .get(ak) - .cloned() - .or_else(|| self.cache.sts_accounts.load().get(ak).cloned()); + pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result> { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } - match user { - Some(u) => { - if u.credentials.is_valid() { - Ok((Some(u), true)) - } else { - Ok((Some(u), false)) + let (mut policies, _) = self.policy_db_get_internal(name, false, false).await?; + let present = !policies.is_empty(); + + for group in groups.iter() { + let (gp, _) = self.policy_db_get_internal(group, true, present).await?; + gp.iter().for_each(|v| { + policies.push(v.clone()); + }); + } + + Ok(policies) + } + + async fn policy_db_get_internal( + &self, + name: &str, + is_group: bool, + policy_present: bool, + ) -> Result<(Vec, OffsetDateTime)> { + if is_group { + let groups = self.cache.groups.load(); + + let g = match groups.get(name) { + Some(p) => p.clone(), + None => { + let mut m = HashMap::new(); + self.api.load_group(name, &mut m).await?; + if let Some(p) = m.get(name) { + Cache::add_or_update(&self.cache.groups, name, p, OffsetDateTime::now_utc()); + } + + m.get(name).cloned().ok_or(IamError::NoSuchGroup(name.to_string()))? + } + }; + + if g.status == STATUS_DISABLED { + return Ok((Vec::new(), OffsetDateTime::now_utc())); + } + + if let Some(policy) = self.cache.group_policies.load().get(name) { + return Ok((policy.to_slice(), policy.update_at)); + } + + if !policy_present { + let mut m = HashMap::new(); + self.api.load_mapped_policy(name, UserType::Reg, true, &mut m).await?; + if let Some(p) = m.get(name) { + Cache::add_or_update(&self.cache.group_policies, name, p, OffsetDateTime::now_utc()); + return Ok((p.to_slice(), p.update_at)); } } - _ => Ok((None, false)), + + return Ok((Vec::new(), OffsetDateTime::now_utc())); } - } - pub async fn policy_db_get(&self, name: &str, _groups: Option>) -> crate::Result> { - // let user = self.cache.users.load(); - // let Some(u) = user.get(name) else { - // return Err(Error::StringError("no service account".into())); - // }; - let policies = self.cache.user_policies.load(); + let users = self.cache.users.load(); + let u = users.get(name).cloned().unwrap_or_default(); + if !u.credentials.is_valid() { + return Ok((Vec::new(), OffsetDateTime::now_utc())); + } - let user_policies = { - if let Some(p) = policies.get(name) { - p.to_slice() - } else { - Vec::new() + let mp = match self.cache.user_policies.load().get(name) { + Some(p) => p.clone(), + None => { + let mut m = HashMap::new(); + self.api.load_mapped_policy(name, UserType::Reg, false, &mut m).await?; + if let Some(p) = m.get(name) { + Cache::add_or_update(&self.cache.user_policies, name, p, OffsetDateTime::now_utc()); + p.clone() + } else { + let mp = match self.cache.sts_policies.load().get(name) { + Some(p) => p.clone(), + None => { + let mut m = HashMap::new(); + self.api.load_mapped_policy(name, UserType::Sts, false, &mut m).await?; + if let Some(p) = m.get(name) { + Cache::add_or_update(&self.cache.sts_policies, name, p, OffsetDateTime::now_utc()); + p.clone() + } else { + MappedPolicy::default() + } + } + }; + mp + } } }; - // TODO: groups + let mut policies: HashSet = mp.to_slice().into_iter().collect(); - Ok(user_policies) + if let Some(groups) = u.credentials.groups.as_ref() { + for group in groups.iter() { + if self + .cache + .groups + .load() + .get(group) + .filter(|v| v.status == STATUS_DISABLED) + .is_some() + { + return Ok((Vec::new(), OffsetDateTime::now_utc())); + } + + let mp = match self.cache.group_policies.load().get(group) { + Some(p) => p.clone(), + None => { + let mut m = HashMap::new(); + self.api.load_mapped_policy(group, UserType::Reg, true, &mut m).await?; + if let Some(p) = m.get(group) { + Cache::add_or_update(&self.cache.group_policies, group, p, OffsetDateTime::now_utc()); + p.clone() + } else { + MappedPolicy::default() + } + } + }; + + mp.to_slice().iter().for_each(|v| { + policies.insert(v.clone()); + }); + } + } + + let update_at = mp.update_at; + + for group in self + .cache + .user_group_memeberships + .load() + .get(name) + .cloned() + .unwrap_or_default() + .iter() + { + if self + .cache + .groups + .load() + .get(group) + .filter(|v| v.status == STATUS_DISABLED) + .is_some() + { + return Ok((Vec::new(), OffsetDateTime::now_utc())); + } + + let mp = match self.cache.group_policies.load().get(group) { + Some(p) => p.clone(), + None => { + let mut m = HashMap::new(); + self.api.load_mapped_policy(group, UserType::Reg, true, &mut m).await?; + if let Some(p) = m.get(group) { + Cache::add_or_update(&self.cache.group_policies, group, p, OffsetDateTime::now_utc()); + p.clone() + } else { + MappedPolicy::default() + } + } + }; + + mp.to_slice().iter().for_each(|v| { + policies.insert(v.clone()); + }); + } + + Ok((policies.into_iter().collect(), update_at)) + } + pub async fn policy_db_set(&self, name: &str, user_type: UserType, is_group: bool, policy: &str) -> Result { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + if policy.is_empty() { + if let Err(err) = self.api.delete_mapped_policy(name, user_type, is_group).await { + if !is_err_no_such_policy(&err) { + return Err(err); + } + } + + if is_group { + Cache::delete(&self.cache.group_policies, name, OffsetDateTime::now_utc()); + } else if user_type == UserType::Sts { + Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc()); + } else { + Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc()); + } + + return Ok(OffsetDateTime::now_utc()); + } + + let mp = MappedPolicy::new(policy); + + let policy_docs_cache = self.cache.policy_docs.load(); + for p in mp.to_slice() { + if !policy_docs_cache.contains_key(&p) { + return Err(Error::new(IamError::NoSuchPolicy)); + } + } + + self.api + .save_mapped_policy(name, user_type, is_group, mp.clone(), None) + .await?; + + if is_group { + Cache::add_or_update(&self.cache.group_policies, name, &mp, OffsetDateTime::now_utc()); + } else if user_type == UserType::Sts { + Cache::add_or_update(&self.cache.sts_policies, name, &mp, OffsetDateTime::now_utc()); + } else { + Cache::add_or_update(&self.cache.user_policies, name, &mp, OffsetDateTime::now_utc()); + } + + Ok(OffsetDateTime::now_utc()) } - pub async fn set_temp_user(&self, _access_key: &str, cred: &Credentials, _policy_name: &str) -> crate::Result<()> { - let user_entiry = UserIdentity::from(cred.clone()); - let path = format!( - "config/iam/{}{}/identity.json", - UserType::Sts.prefix(), - user_entiry.credentials.access_key - ); - debug!("save object: {path:?}"); - self.api.save_iam_config(&user_entiry, path).await?; + 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() { + return Err(Error::new(IamError::InvalidArgument)); + } - Cache::add_or_update( - &self.cache.sts_accounts, - &user_entiry.credentials.access_key, - &user_entiry, - OffsetDateTime::now_utc(), - ); + if let Some(policy) = policy_name { + let mp = MappedPolicy::new(policy); + let (_, combined_policy_stmt) = filter_policies(&self.cache, &mp.policies, "temp"); + if combined_policy_stmt.is_empty() { + return Err(Error::msg(format!("need poliy not found {}", IamError::NoSuchPolicy))); + } - Ok(()) + self.api + .save_mapped_policy(&cred.parent_user, UserType::Sts, false, mp.clone(), None) + .await?; + + Cache::add_or_update(&self.cache.sts_policies, &cred.parent_user, &mp, OffsetDateTime::now_utc()); + } + + let u = UserIdentity::new(cred.clone()); + self.api + .save_user_identity(access_key, UserType::Sts, u.clone(), None) + .await?; + + Cache::add_or_update(&self.cache.sts_accounts, access_key, &u, OffsetDateTime::now_utc()); + + Ok(OffsetDateTime::now_utc()) + } + + pub async fn get_user_info(&self, name: &str) -> Result { + let users = self.cache.users.load(); + let policies = self.cache.user_policies.load(); + let group_members = self.cache.user_group_memeberships.load(); + + let u = match users.get(name) { + Some(u) => u, + None => return Err(Error::new(IamError::NoSuchUser(name.to_string()))), + }; + + if u.credentials.is_temp() || u.credentials.is_service_account() { + return Err(Error::new(IamError::IAMActionNotAllowed)); + } + + let mut uinfo = madmin::UserInfo { + status: if u.credentials.is_valid() { + madmin::AccountStatus::Enabled + } else { + madmin::AccountStatus::Disabled + }, + updated_at: u.update_at, + ..Default::default() + }; + + if let Some(p) = policies.get(name) { + uinfo.policy_name = Some(p.policies.clone()); + uinfo.updated_at = Some(p.update_at); + } + + if let Some(members) = group_members.get(name) { + uinfo.member_of = Some(members.iter().cloned().collect()); + } + + Ok(uinfo) } // returns all users (not STS or service accounts) - pub async fn get_users(&self) -> crate::Result> { + pub async fn get_users(&self) -> Result> { let mut m = HashMap::new(); let users = self.cache.users.load(); @@ -280,8 +846,6 @@ where let group_members = self.cache.user_group_memeberships.load(); for (k, v) in users.iter() { - warn!("k: {}, v: {:?}", k, v.credentials); - if v.credentials.is_temp() || v.credentials.is_service_account() { continue; } @@ -296,8 +860,6 @@ where ..Default::default() }; - warn!("uinfo {:?}", u); - if let Some(p) = policies.get(k) { u.policy_name = Some(p.policies.clone()); u.updated_at = Some(p.update_at); @@ -312,8 +874,80 @@ where Ok(m) } + pub async fn get_bucket_users(&self, bucket_name: &str) -> Result> { + let users = self.cache.users.load(); + let policies_cache = self.cache.user_policies.load(); + let group_members = self.cache.user_group_memeberships.load(); + let group_policy_cache = self.cache.group_policies.load(); + + let mut ret = HashMap::new(); + + for (k, v) in users.iter() { + if v.credentials.is_temp() || v.credentials.is_service_account() { + continue; + } + + let mut policies = Vec::new(); + if let Some(p) = policies_cache.get(k) { + policies.push(p.policies.clone()); + + if let Some(groups) = group_members.get(k) { + for group in groups { + if let Some(p) = group_policy_cache.get(group) { + policies.push(p.policies.clone()); + } + } + } + } + + let matched_policies = filter_policies(&self.cache, &policies.join(","), bucket_name).0; + if matched_policies.is_empty() { + continue; + } + + let mut u = madmin::UserInfo { + policy_name: Some(matched_policies), + status: if v.credentials.is_valid() { + madmin::AccountStatus::Enabled + } else { + madmin::AccountStatus::Disabled + }, + updated_at: v.update_at, + ..Default::default() + }; + + if let Some(members) = group_members.get(k) { + u.member_of = Some(members.iter().cloned().collect()); + } + + ret.insert(k.clone(), u); + } + + Ok(ret) + } + pub async fn get_users_with_mapped_policies(&self) -> HashMap { + let mut m = HashMap::new(); + + self.cache.user_policies.load().iter().for_each(|(k, v)| { + m.insert(k.clone(), v.policies.clone()); + }); + + self.cache.sts_policies.load().iter().for_each(|(k, v)| { + m.insert(k.clone(), v.policies.clone()); + }); + + m + } + + pub async fn add_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result { + let users = self.cache.users.load(); + if let Some(x) = users.get(access_key) { + warn!("user already exists: {:?}", x); + if x.credentials.is_temp() { + return Err(IamError::IAMActionNotAllowed.into()); + } + } - pub async fn add_user(&self, access_key: &str, secret_key: &str, status: &str) -> crate::Result { let status = { match status { val if val == AccountStatus::Enabled.as_ref() => auth::ACCOUNT_ON, @@ -321,121 +955,114 @@ where _ => auth::ACCOUNT_OFF, } }; - - let users = self.cache.users.load(); - if let Some(x) = users.get(access_key) { - warn!("user already exists: {:?}", x); - if x.credentials.is_temp() { - return Err(crate::Error::IAMActionNotAllowed); - } - } - let user_entiry = UserIdentity::from(Credentials { access_key: access_key.to_string(), secret_key: secret_key.to_string(), status: status.to_owned(), ..Default::default() }); - let path = format!( - "config/iam/{}{}/identity.json", - UserType::Reg.prefix(), - user_entiry.credentials.access_key - ); - debug!("save object: {path:?}"); - self.api.save_iam_config(&user_entiry, path).await?; - Cache::add_or_update( - &self.cache.users, - &user_entiry.credentials.access_key, - &user_entiry, - OffsetDateTime::now_utc(), - ); + self.api + .save_user_identity(access_key, UserType::Reg, user_entiry.clone(), None) + .await?; - Ok(user_entiry.update_at.unwrap_or(OffsetDateTime::now_utc())) + self.update_user_with_claims(access_key, user_entiry)?; + + Ok(OffsetDateTime::now_utc()) } - pub async fn delete_user(&self, access_key: &str, _utype: UserType) -> crate::Result<()> { - let users = self.cache.users.load(); - if let Some(x) = users.get(access_key) { - if x.credentials.is_temp() { - return Err(crate::Error::IAMActionNotAllowed); + pub async fn delete_user(&self, access_key: &str, utype: UserType) -> Result<()> { + if access_key.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + if utype == UserType::Reg { + if let Some(member_of) = self.cache.user_group_memeberships.load().get(access_key) { + for member in member_of.iter() { + let _ = self + .remove_members_from_group(member, vec![access_key.to_string()], false) + .await?; + } + } + + let users_cache = self.cache.users.load(); + + for (_, v) in users_cache.iter() { + let u = &v.credentials; + if u.parent_user.as_str() == access_key { + if u.is_service_account() { + let _ = self.api.delete_user_identity(&u.access_key, UserType::Svc).await; + Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc()); + } + + if u.is_temp() { + let _ = self.api.delete_user_identity(&u.access_key, UserType::Sts).await; + Cache::delete(&self.cache.sts_accounts, &u.access_key, OffsetDateTime::now_utc()); + Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc()); + } + } } } - // if utype == UserType::Reg {} - // TODO: Delete user from group memberships + self.api.delete_mapped_policy(access_key, utype, false).await?; - let path = format!("config/iam/{}{}/identity.json", UserType::Reg.prefix(), access_key); - debug!("delete object: {path:?}"); - self.api.delete_iam_config(path).await?; + Cache::delete(&self.cache.user_policies, access_key, OffsetDateTime::now_utc()); + + if let Err(err) = self.api.delete_user_identity(access_key, utype).await { + if !is_err_no_such_user(&err) { + return Err(err); + } + } + + if utype == UserType::Sts { + Cache::delete(&self.cache.sts_accounts, access_key, OffsetDateTime::now_utc()); + } - // delete cache Cache::delete(&self.cache.users, access_key, OffsetDateTime::now_utc()); Ok(()) } - pub async fn get_user(&self, access_key: &str) -> crate::Result> { - let u = self - .cache - .users - .load() - .get(access_key) - .cloned() - .or_else(|| self.cache.sts_accounts.load().get(access_key).cloned()); + pub async fn update_user_secret_key(&self, access_key: &str, secret_key: &str) -> Result<()> { + if access_key.is_empty() || secret_key.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } - Ok(u) - } - - pub async fn get_user_info(&self, access_key: &str) -> crate::Result { let users = self.cache.users.load(); - let policies = self.cache.user_policies.load(); - let group_members = self.cache.user_group_memeberships.load(); - let u = match users.get(access_key) { Some(u) => u, - None => return Err(Error::NoSuchUser(access_key.to_string())), + None => return Err(Error::new(IamError::NoSuchUser(access_key.to_string()))), }; - if u.credentials.is_temp() || u.credentials.is_service_account() { - return Err(Error::IAMActionNotAllowed); - } + let mut cred = u.credentials.clone(); + cred.secret_key = secret_key.to_string(); - let mut uinfo = madmin::UserInfo { - status: if u.credentials.is_valid() { - madmin::AccountStatus::Enabled - } else { - madmin::AccountStatus::Disabled - }, - updated_at: u.update_at, - ..Default::default() - }; + let u = UserIdentity::from(cred); - if let Some(p) = policies.get(access_key) { - uinfo.policy_name = Some(p.policies.clone()); - uinfo.updated_at = Some(p.update_at); - } + self.api + .save_user_identity(access_key, UserType::Reg, u.clone(), None) + .await?; - if let Some(members) = group_members.get(access_key) { - uinfo.member_of = Some(members.iter().cloned().collect()); - } - - Ok(uinfo) + self.update_user_with_claims(access_key, u) } - pub async fn set_user_status(&self, access_key: &str, status: AccountStatus) -> crate::Result { + pub async fn set_user_status(&self, access_key: &str, status: AccountStatus) -> Result { if access_key.is_empty() { - return Err(Error::InvalidArgument); + return Err(Error::new(IamError::InvalidArgument)); + } + + if !access_key.is_empty() && status != AccountStatus::Enabled && status != AccountStatus::Disabled { + return Err(Error::new(IamError::InvalidArgument)); } let users = self.cache.users.load(); let u = match users.get(access_key) { Some(u) => u, - None => return Err(Error::NoSuchUser(access_key.to_string())), + None => return Err(Error::new(IamError::NoSuchUser(access_key.to_string()))), }; if u.credentials.is_temp() || u.credentials.is_service_account() { - return Err(Error::IAMActionNotAllowed); + return Err(Error::new(IamError::IAMActionNotAllowed)); } let status = { @@ -451,29 +1078,36 @@ where status: status.to_owned(), ..Default::default() }); - let path = format!( - "config/iam/{}{}/identity.json", - UserType::Reg.prefix(), - user_entiry.credentials.access_key - ); - debug!("save object: {path:?}"); - self.api.save_iam_config(&user_entiry, path).await?; - Cache::add_or_update( - &self.cache.users, - &user_entiry.credentials.access_key, - &user_entiry, - OffsetDateTime::now_utc(), - ); + self.api + .save_user_identity(access_key, UserType::Reg, user_entiry.clone(), None) + .await?; - Ok(user_entiry.update_at.unwrap_or(OffsetDateTime::now_utc())) + self.update_user_with_claims(access_key, user_entiry)?; + + Ok(OffsetDateTime::now_utc()) } - pub async fn is_temp_user(&self, access_key: &str) -> crate::Result<(bool, String)> { + fn update_user_with_claims(&self, k: &str, u: UserIdentity) -> Result<()> { + let mut u = u; + if u.credentials.session_token.is_empty() { + u.credentials.claims = Some(extract_jwt_claims(&u)?); + } + + if u.credentials.is_temp() && !u.credentials.is_service_account() { + Cache::add_or_update(&self.cache.sts_accounts, k, &u, OffsetDateTime::now_utc()); + } else { + Cache::add_or_update(&self.cache.users, k, &u, OffsetDateTime::now_utc()); + } + + Ok(()) + } + + pub async fn is_temp_user(&self, access_key: &str) -> Result<(bool, String)> { let users = self.cache.users.load(); let u = match users.get(access_key) { Some(u) => u, - None => return Err(Error::NoSuchUser(access_key.to_string())), + None => return Err(Error::new(IamError::NoSuchUser(access_key.to_string()))), }; if u.credentials.is_temp() { @@ -482,48 +1116,474 @@ where Ok((false, String::new())) } } - pub async fn remove_users_from_group(&self, group: &str, members: Vec) -> crate::Result { + + pub async fn add_users_to_group(&self, group: &str, members: Vec) -> Result { if group.is_empty() { - return Err(Error::InvalidArgument); + return Err(Error::new(IamError::InvalidArgument)); } - let users = self.cache.users.load(); - let groups = self.cache.groups.load(); - let group_members_cache = self.cache.user_group_memeberships.load(); + let users_cache = self.cache.users.load(); for member in members.iter() { - let u = users.get(member).ok_or(Error::NoSuchUser(member.to_string()))?; - - if u.credentials.is_temp() || u.credentials.is_service_account() { - return Err(Error::IAMActionNotAllowed); + if let Some(u) = users_cache.get(member) { + if u.credentials.is_temp() || u.credentials.is_service_account() { + return Err(Error::new(IamError::IAMActionNotAllowed)); + } + } else { + return Err(Error::new(IamError::NoSuchUser(member.to_string()))); } } - let group_info = groups.get(group).ok_or(Error::NoSuchGroup(group.to_string()))?; + let gi = match self.cache.groups.load().get(group) { + Some(res) => { + let mut gi = res.clone(); + let mut uniq_set: HashSet = + HashSet::from_iter(gi.members.iter().cloned()); + uniq_set.extend(members.iter().cloned()); - let mut group_members = match group_members_cache.get(group) { - Some(m) => m.clone(), - None => return Err(Error::NoSuchGroup(group.to_string())), + gi.members = uniq_set.into_iter().collect(); + gi + } + None => GroupInfo::new(members.clone()), }; - if members.is_empty() && !group_members.is_empty() { - return Err(Error::GroupNotEmpty); - } + self.api.save_group_info(group, gi.clone()).await?; - if members.is_empty() { - group_members.clear(); - } else { - for member in members.iter() { - group_members.remove(member); + Cache::add_or_update(&self.cache.groups, group, &gi, OffsetDateTime::now_utc()); + + let user_group_memeberships = self.cache.user_group_memeberships.load(); + members.iter().for_each(|member| { + if let Some(m) = user_group_memeberships.get(member) { + let mut m = m.clone(); + m.insert(group.to_string()); + Cache::add_or_update(&self.cache.user_group_memeberships, member, &m, OffsetDateTime::now_utc()); } - } - - let path = format!("config/iam/group/{}.json", group); - debug!("save object: {path:?}"); - self.api.save_iam_config(&members, path).await?; - - Cache::add_or_update(&self.cache.user_group_memeberships, group, &group_members, OffsetDateTime::now_utc()); + }); Ok(OffsetDateTime::now_utc()) } + + pub async fn set_group_status(&self, name: &str, enable: bool) -> Result { + if name.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + let groups = self.cache.groups.load(); + let mut gi = match groups.get(name) { + Some(gi) => gi.clone(), + None => return Err(Error::new(IamError::NoSuchGroup(name.to_string()))), + }; + + if enable { + gi.status = STATUS_ENABLED.to_owned(); + } else { + gi.status = STATUS_DISABLED.to_owned(); + } + + self.api.save_group_info(name, gi.clone()).await?; + + Cache::add_or_update(&self.cache.groups, name, &gi, OffsetDateTime::now_utc()); + + Ok(OffsetDateTime::now_utc()) + } + + pub async fn get_group_description(&self, name: &str) -> Result { + let (ps, updated_at) = self.policy_db_get_internal(name, true, false).await?; + let policy = ps.join(","); + + let gi = self + .cache + .groups + .load() + .get(name) + .cloned() + .ok_or(Error::new(IamError::NoSuchGroup(name.to_string())))?; + + Ok(GroupDesc { + name: name.to_string(), + policy, + members: gi.members, + updated_at: Some(updated_at), + status: gi.status, + }) + } + + pub async fn list_groups(&self) -> Result> { + Ok(self.cache.groups.load().keys().cloned().collect()) + } + + pub async fn remove_members_from_group( + &self, + name: &str, + members: Vec, + update_cache_only: bool, + ) -> Result { + let mut gi = self + .cache + .groups + .load() + .get(name) + .cloned() + .ok_or(Error::new(IamError::NoSuchGroup(name.to_string())))?; + + let s: HashSet<&String> = HashSet::from_iter(gi.members.iter()); + let d: HashSet<&String> = HashSet::from_iter(members.iter()); + gi.members = s.difference(&d).map(|v| v.to_string()).collect::>(); + + if !update_cache_only { + self.api.save_group_info(name, gi.clone()).await?; + } + + Cache::add_or_update(&self.cache.groups, name, &gi, OffsetDateTime::now_utc()); + + let user_group_memeberships = self.cache.user_group_memeberships.load(); + members.iter().for_each(|member| { + if let Some(m) = user_group_memeberships.get(member) { + let mut m = m.clone(); + m.remove(name); + Cache::add_or_update(&self.cache.user_group_memeberships, member, &m, OffsetDateTime::now_utc()); + } + }); + + Ok(OffsetDateTime::now_utc()) + } + + pub async fn remove_users_from_group(&self, group: &str, members: Vec) -> Result { + if group.is_empty() { + return Err(Error::new(IamError::InvalidArgument)); + } + + let users_cache = self.cache.users.load(); + + for member in members.iter() { + if let Some(u) = users_cache.get(member) { + if u.credentials.is_temp() || u.credentials.is_service_account() { + return Err(Error::new(IamError::IAMActionNotAllowed)); + } + } else { + return Err(Error::new(IamError::NoSuchUser(member.to_string()))); + } + } + + let gi = self + .cache + .groups + .load() + .get(group) + .cloned() + .ok_or(Error::new(IamError::NoSuchGroup(group.to_string())))?; + + if members.is_empty() && !gi.members.is_empty() { + return Err(IamError::GroupNotEmpty.into()); + } + + if members.is_empty() { + self.api.delete_mapped_policy(group, UserType::Reg, true).await?; + + self.api.delete_group_info(group).await?; + + Cache::delete(&self.cache.groups, group, OffsetDateTime::now_utc()); + Cache::delete(&self.cache.group_policies, group, OffsetDateTime::now_utc()); + + return Ok(OffsetDateTime::now_utc()); + } + + self.remove_members_from_group(group, members, false).await + } + + fn remove_group_from_memberships_map(&self, group: &str) { + let user_group_memeberships = self.cache.user_group_memeberships.load(); + for (k, v) in user_group_memeberships.iter() { + if v.contains(group) { + let mut m = v.clone(); + m.remove(group); + Cache::add_or_update(&self.cache.user_group_memeberships, k, &m, OffsetDateTime::now_utc()); + } + } + } + + fn update_group_memberships_map(&self, group: &str, gi: &GroupInfo) { + let user_group_memeberships = self.cache.user_group_memeberships.load(); + for member in gi.members.iter() { + if let Some(m) = user_group_memeberships.get(member) { + let mut m = m.clone(); + m.insert(group.to_string()); + Cache::add_or_update(&self.cache.user_group_memeberships, member, &m, OffsetDateTime::now_utc()); + } + } + } + + pub async fn group_notification_handler(&self, group: &str) -> Result<()> { + let mut m = HashMap::new(); + if let Err(err) = self.api.load_group(group, &mut m).await { + if !is_err_no_such_group(&err) { + return Err(err); + } + + self.remove_group_from_memberships_map(group); + Cache::delete(&self.cache.groups, group, OffsetDateTime::now_utc()); + Cache::delete(&self.cache.group_policies, group, OffsetDateTime::now_utc()); + + return Ok(()); + } + + let gi = m[group].clone(); + + Cache::add_or_update(&self.cache.groups, group, &gi, OffsetDateTime::now_utc()); + + self.remove_group_from_memberships_map(group); + self.update_group_memberships_map(group, &gi); + + Ok(()) + } + + pub async fn policy_notification_handler(&self, policy: &str) -> Result<()> { + let mut m = HashMap::new(); + if let Err(err) = self.api.load_policy_doc(policy, &mut m).await { + if !is_err_no_such_policy(&err) { + return Err(err); + } + + Cache::delete(&self.cache.policy_docs, policy, OffsetDateTime::now_utc()); + + let user_policy_cache = self.cache.user_policies.load(); + let users_cache = self.cache.users.load(); + for (k, v) in user_policy_cache.iter() { + let mut set = v.policy_set(); + if set.contains(policy) { + if !users_cache.contains_key(k) { + Cache::delete(&self.cache.user_policies, k, OffsetDateTime::now_utc()); + continue; + } + + set.remove(policy); + + let mp = MappedPolicy::new(&set.iter().cloned().collect::>().join(",")); + + Cache::add_or_update(&self.cache.user_policies, k, &mp, OffsetDateTime::now_utc()); + } + } + + let group_policy_cache = self.cache.group_policies.load(); + for (k, v) in group_policy_cache.iter() { + let mut set = v.policy_set(); + if set.contains(policy) { + set.remove(policy); + + let mp = MappedPolicy::new(&set.iter().cloned().collect::>().join(",")); + Cache::add_or_update(&self.cache.group_policies, k, &mp, OffsetDateTime::now_utc()); + } + } + + return Ok(()); + } + + Cache::add_or_update(&self.cache.policy_docs, policy, &m[policy], OffsetDateTime::now_utc()); + + Ok(()) + } + + pub async fn policy_mapping_notification_handler(&self, name: &str, user_type: UserType, is_group: bool) -> Result<()> { + let mut m = HashMap::new(); + if let Err(err) = self.api.load_mapped_policy(name, user_type, is_group, &mut m).await { + if !is_err_no_such_policy(&err) { + return Err(err); + } + + if is_group { + Cache::delete(&self.cache.group_policies, name, OffsetDateTime::now_utc()); + } else if user_type == UserType::Sts { + Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc()); + } else { + Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc()); + } + + return Ok(()); + } + + let mp = m[name].clone(); + + if is_group { + Cache::add_or_update(&self.cache.group_policies, name, &mp, OffsetDateTime::now_utc()); + } else if user_type == UserType::Sts { + Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc()); + } else { + Cache::add_or_update(&self.cache.user_policies, name, &mp, OffsetDateTime::now_utc()); + } + + Ok(()) + } + pub async fn user_notification_handler(&self, name: &str, user_type: UserType) -> Result<()> { + let mut m = HashMap::new(); + if let Err(err) = self.api.load_user(name, user_type, &mut m).await { + if !is_err_no_such_user(&err) { + return Err(err); + } + + if user_type == UserType::Sts { + Cache::delete(&self.cache.sts_accounts, name, OffsetDateTime::now_utc()); + } else { + Cache::delete(&self.cache.users, name, OffsetDateTime::now_utc()); + } + + let member_of = self.cache.user_group_memeberships.load(); + if let Some(m) = member_of.get(name) { + for group in m.iter() { + if let Err(err) = self.remove_members_from_group(group, vec![name.to_string()], true).await { + if !is_err_no_such_group(&err) { + return Err(err); + } + } + } + } + + if user_type == UserType::Reg { + let users_cache = self.cache.users.load(); + for (_, v) in users_cache.iter() { + let u = &v.credentials; + if u.parent_user.as_str() == name && u.is_service_account() { + let _ = self.api.delete_user_identity(&u.access_key, UserType::Svc).await; + Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc()); + } + } + + let sts_accounts = self.cache.sts_accounts.load(); + if let Some(u) = sts_accounts.get(name) { + let u = &u.credentials; + if u.parent_user.as_str() == name { + let _ = self.api.delete_user_identity(&u.access_key, UserType::Sts).await; + Cache::delete(&self.cache.sts_accounts, &u.access_key, OffsetDateTime::now_utc()); + } + } + } + + Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc()); + + return Ok(()); + } + + let u = m[name].clone(); + + match user_type { + UserType::Sts => { + let name = u.credentials.parent_user; + let mut m = HashMap::new(); + if let Err(err) = self.api.load_mapped_policy(&name, user_type, false, &mut m).await { + if !is_err_no_such_policy(&err) { + return Err(err); + } + + return Ok(()); + } + + Cache::add_or_update(&self.cache.sts_policies, &name, &m[&name], OffsetDateTime::now_utc()); + } + UserType::Reg => { + let mut m = HashMap::new(); + if let Err(err) = self.api.load_mapped_policy(name, user_type, false, &mut m).await { + if !is_err_no_such_policy(&err) { + return Err(err); + } + return Ok(()); + } + + Cache::add_or_update(&self.cache.sts_policies, name, &m[name], OffsetDateTime::now_utc()); + } + + UserType::Svc => { + let users_cache = self.cache.users.load(); + if let Some(u) = users_cache.get(&u.credentials.parent_user) { + let mut m = HashMap::new(); + if let Err(err) = self + .api + .load_mapped_policy(&u.credentials.parent_user, UserType::Reg, false, &mut m) + .await + { + if !is_err_no_such_policy(&err) { + return Err(err); + } + return Ok(()); + } + + Cache::add_or_update( + &self.cache.user_policies, + &u.credentials.parent_user, + &m[&u.credentials.parent_user], + OffsetDateTime::now_utc(), + ); + } else { + let mut m = HashMap::new(); + if let Err(err) = self + .api + .load_mapped_policy(&u.credentials.parent_user, UserType::Sts, false, &mut m) + .await + { + if !is_err_no_such_policy(&err) { + return Err(err); + } + return Ok(()); + } + + Cache::add_or_update( + &self.cache.sts_policies, + &u.credentials.parent_user, + &m[&u.credentials.parent_user], + OffsetDateTime::now_utc(), + ); + } + } + } + + Ok(()) + } +} + +pub fn get_default_policyes() -> HashMap { + let default_policies = &DEFAULT_POLICIES; + default_policies + .iter() + .map(|(n, p)| { + ( + n.to_string(), + PolicyDoc { + version: 1, + policy: p.clone(), + ..Default::default() + }, + ) + }) + .collect() +} + +fn set_default_canned_policies(policies: &mut HashMap) { + let default_policies = &DEFAULT_POLICIES; + for (k, v) in default_policies.iter() { + let name = k.to_string(); + policies.entry(name).or_insert_with(|| PolicyDoc::default_policy(v.clone())); + } +} + +pub fn extract_jwt_claims(u: &UserIdentity) -> Result> { + get_claims_from_token_with_secret(&u.credentials.session_token, &u.credentials.secret_key) +} + +fn filter_policies(cache: &Cache, policy_name: &str, bucket_name: &str) -> (String, Policy) { + let mp = MappedPolicy::new(policy_name).to_slice(); + + let mut policies = Vec::new(); + let mut to_merge = Vec::new(); + for policy in mp { + if policy.is_empty() { + continue; + } + + if let Some(p) = cache.policy_docs.load().get(&policy) { + if bucket_name.is_empty() || p.policy.match_resource(bucket_name) { + policies.push(policy); + to_merge.push(p.policy.clone()); + } + } + } + + (policies.join(","), Policy::merge_policies(to_merge)) } diff --git a/iam/src/policy.rs b/iam/src/policy.rs index cca97be4..e25dca90 100644 --- a/iam/src/policy.rs +++ b/iam/src/policy.rs @@ -3,56 +3,22 @@ mod doc; mod effect; mod function; mod id; +#[allow(clippy::module_inception)] mod policy; pub mod resource; pub mod statement; pub(crate) mod utils; -use action::Action; pub use action::ActionSet; pub use doc::PolicyDoc; + pub use effect::Effect; pub use function::Functions; pub use id::ID; pub use policy::{default::DEFAULT_POLICIES, Policy}; pub use resource::ResourceSet; -use serde::{de, Deserialize, Serialize}; -use serde_json::Value; + pub use statement::Statement; -use std::collections::HashMap; -use time::OffsetDateTime; - -#[derive(Serialize, Deserialize, Clone)] -pub struct MappedPolicy { - pub version: i64, - pub policies: String, - pub update_at: OffsetDateTime, -} - -impl MappedPolicy { - pub fn new(policy: &str) -> Self { - Self { - version: 1, - policies: policy.to_owned(), - update_at: OffsetDateTime::now_utc(), - } - } - - pub fn to_slice(&self) -> Vec { - self.policies - .split(",") - .filter(|v| !v.trim().is_empty()) - .map(|v| v.to_string()) - .collect() - } -} - -pub struct GroupInfo { - version: i64, - status: String, - members: Vec, - update_at: OffsetDateTime, -} #[derive(thiserror::Error, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] @@ -81,49 +47,3 @@ pub enum Error { #[error("invalid resource, type: '{0}', pattern: '{1}'")] InvalidResource(String, String), } - -/// DEFAULT_VERSION is the default version. -/// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html -pub const DEFAULT_VERSION: &str = "2012-10-17"; - -/// check the data is Validator -pub trait Validator { - fn is_valid(&self) -> Result<(), Error> { - Ok(()) - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum UserType { - Svc, - Sts, - Reg, -} - -impl UserType { - pub fn prefix(&self) -> &'static str { - match self { - UserType::Svc => "service-accounts/", - UserType::Sts => "sts/", - UserType::Reg => "users/", - } - } -} - -pub struct Args<'a> { - pub account: &'a str, - pub groups: &'a [String], - pub action: Action, - pub bucket: &'a str, - pub conditions: &'a HashMap>, - pub is_owner: bool, - pub object: &'a str, - pub claims: &'a HashMap, - pub deny_only: bool, -} - -impl<'a> Args<'a> { - pub fn get_role_arn(&self) -> Option<&str> { - self.claims.get("roleArn").and_then(|x| x.as_str()) - } -} diff --git a/iam/src/policy/action.rs b/iam/src/policy/action.rs index eccbc844..a4f35ffd 100644 --- a/iam/src/policy/action.rs +++ b/iam/src/policy/action.rs @@ -1,11 +1,13 @@ -use std::{collections::HashSet, ops::Deref}; - +use ecstore::error::{Error, Result}; use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, ops::Deref}; use strum::{EnumString, IntoStaticStr}; -use super::{utils::wildcard, Error, Validator}; +use crate::sys::Validator; -#[derive(Serialize, Deserialize, Clone, Default)] +use super::{utils::wildcard, Error as IamError}; + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct ActionSet(pub HashSet); impl ActionSet { @@ -35,12 +37,19 @@ impl Deref for ActionSet { } impl Validator for ActionSet { - fn is_valid(&self) -> Result<(), super::Error> { + type Error = Error; + fn is_valid(&self) -> Result<()> { Ok(()) } } -#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone)] +impl PartialEq for ActionSet { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.0.iter().all(|x| other.0.contains(x)) + } +} + +#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, Debug)] #[serde(try_from = "&str", untagged)] pub enum Action { S3Action(S3Action), @@ -77,26 +86,28 @@ impl TryFrom<&str> for Action { type Error = Error; fn try_from(value: &str) -> Result { if value.starts_with(Self::S3_PREFIX) { - Ok(Self::S3Action(S3Action::try_from(value).map_err(|_| Error::InvalidAction(value.into()))?)) + Ok(Self::S3Action( + S3Action::try_from(value).map_err(|_| IamError::InvalidAction(value.into()))?, + )) } else if value.starts_with(Self::ADMIN_PREFIX) { Ok(Self::AdminAction( - AdminAction::try_from(value).map_err(|_| Error::InvalidAction(value.into()))?, + AdminAction::try_from(value).map_err(|_| IamError::InvalidAction(value.into()))?, )) } else if value.starts_with(Self::STS_PREFIX) { Ok(Self::StsAction( - StsAction::try_from(value).map_err(|_| Error::InvalidAction(value.into()))?, + StsAction::try_from(value).map_err(|_| IamError::InvalidAction(value.into()))?, )) } else if value.starts_with(Self::KMS_PREFIX) { Ok(Self::KmsAction( - KmsAction::try_from(value).map_err(|_| Error::InvalidAction(value.into()))?, + KmsAction::try_from(value).map_err(|_| IamError::InvalidAction(value.into()))?, )) } else { - Err(Error::InvalidAction(value.into())) + Err(IamError::InvalidAction(value.into()).into()) } } } -#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr)] +#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr, Debug)] #[cfg_attr(test, derive(Default))] #[serde(try_from = "&str", into = "&str")] pub enum S3Action { @@ -113,7 +124,7 @@ pub enum S3Action { GetObjectVersionAction, } -#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr)] +#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr, Debug)] #[serde(try_from = "&str", into = "&str")] pub enum AdminAction { #[strum(serialize = "admin:*")] @@ -144,11 +155,11 @@ pub enum AdminAction { CreateServiceAccountAdminAction, } -#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr)] +#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr, Debug)] #[serde(try_from = "&str", into = "&str")] pub enum StsAction {} -#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr)] +#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr, Debug)] #[serde(try_from = "&str", into = "&str")] pub enum KmsAction { #[strum(serialize = "kms:*")] diff --git a/iam/src/policy/doc.rs b/iam/src/policy/doc.rs index 9b1b70a9..372424a7 100644 --- a/iam/src/policy/doc.rs +++ b/iam/src/policy/doc.rs @@ -10,3 +10,50 @@ pub struct PolicyDoc { pub create_date: Option, pub update_date: Option, } + +impl PolicyDoc { + pub fn new(policy: Policy) -> Self { + Self { + version: 1, + policy, + create_date: Some(OffsetDateTime::now_utc()), + update_date: Some(OffsetDateTime::now_utc()), + } + } + + pub fn update(&mut self, policy: Policy) { + self.version += 1; + self.policy = policy; + self.update_date = Some(OffsetDateTime::now_utc()); + + if self.create_date.is_none() { + self.create_date = self.update_date; + } + } + + pub fn default_policy(policy: Policy) -> Self { + Self { + version: 1, + policy, + create_date: None, + update_date: None, + } + } +} + +impl TryFrom> for PolicyDoc { + type Error = serde_json::Error; + + fn try_from(value: Vec) -> Result { + match serde_json::from_slice::(&value) { + Ok(res) => Ok(res), + Err(err) => match serde_json::from_slice::(&value) { + Ok(res2) => Ok(Self { + policy: res2, + ..Default::default() + }), + Err(_) => Err(err), + }, + } + } +} diff --git a/iam/src/policy/effect.rs b/iam/src/policy/effect.rs index 48437f64..67cc38c3 100644 --- a/iam/src/policy/effect.rs +++ b/iam/src/policy/effect.rs @@ -1,9 +1,10 @@ +use ecstore::error::{Error, Result}; use serde::{Deserialize, Serialize}; use strum::{EnumString, IntoStaticStr}; -use super::{Error, Validator}; +use crate::sys::Validator; -#[derive(Serialize, Clone, Deserialize, EnumString, IntoStaticStr, Default)] +#[derive(Serialize, Clone, Deserialize, EnumString, IntoStaticStr, Default, Debug, PartialEq)] #[serde(try_from = "&str", into = "&str")] pub enum Effect { #[default] @@ -24,7 +25,8 @@ impl Effect { } impl Validator for Effect { - fn is_valid(&self) -> Result<(), Error> { + type Error = Error; + fn is_valid(&self) -> Result<()> { Ok(()) } } diff --git a/iam/src/policy/function.rs b/iam/src/policy/function.rs index 1141373e..03a020dc 100644 --- a/iam/src/policy/function.rs +++ b/iam/src/policy/function.rs @@ -15,7 +15,7 @@ pub mod key_name; pub mod number; pub mod string; -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct Functions { for_any_value: Vec, for_all_values: Vec, @@ -143,6 +143,21 @@ impl<'de> Deserialize<'de> for Functions { } } +impl PartialEq for Functions { + fn eq(&self, other: &Self) -> bool { + if !(self.for_all_values.len() == other.for_all_values.len() + && self.for_any_value.len() == other.for_any_value.len() + && self.for_normal.len() == other.for_normal.len()) + { + return false; + } + + self.for_any_value.iter().all(|x| other.for_any_value.contains(x)) + && self.for_all_values.iter().all(|x| other.for_all_values.contains(x)) + && self.for_normal.iter().all(|x| other.for_normal.contains(x)) + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct Value; @@ -151,7 +166,6 @@ mod tests { use crate::policy::function::condition::Condition::*; use crate::policy::function::func::FuncKeyValue; use crate::policy::function::key::Key; - use crate::policy::function::key_name::KeyName; use crate::policy::function::string::StringFunc; use crate::policy::function::string::StringFuncValue; use crate::policy::Functions; @@ -369,7 +383,6 @@ mod tests { values: StringFuncValue(vec!["us-east-1"].into_iter().map(ToOwned::to_owned).collect()), }], })], - ..Default::default() }, r#"{"ForAllValues:StringNotLike":{"s3:LocationConstraint":"us-east-1"},"ForAnyValue:StringNotLike":{"s3:LocationConstraint":["us-east-1","us-east-2"]},"StringNotLike":{"s3:LocationConstraint":"us-east-1"}}"#; "3" diff --git a/iam/src/policy/function/addr.rs b/iam/src/policy/function/addr.rs index a141f2a6..b7577f46 100644 --- a/iam/src/policy/function/addr.rs +++ b/iam/src/policy/function/addr.rs @@ -27,9 +27,8 @@ impl AddrFunc { } } -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, PartialEq, Eq, Debug)] #[serde(transparent)] -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] pub struct AddrFuncValue(Vec); impl<'de> Deserialize<'de> for AddrFuncValue { @@ -73,9 +72,9 @@ impl<'de> Deserialize<'de> for AddrFuncValue { cidr_str.to_mut().push_str("/32"); } - Ok(cidr_str + cidr_str .parse::() - .map_err(|_| E::custom(format!("{v} can not be parsed to CIDR")))?) + .map_err(|_| E::custom(format!("{v} can not be parsed to CIDR"))) } } diff --git a/iam/src/policy/function/binary.rs b/iam/src/policy/function/binary.rs index 299dac64..0c6e25f2 100644 --- a/iam/src/policy/function/binary.rs +++ b/iam/src/policy/function/binary.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use super::func::InnerFunc; @@ -5,6 +7,12 @@ use super::func::InnerFunc; pub type BinaryFunc = InnerFunc; // todo implement it -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] #[serde(transparent)] pub struct BinaryFuncValue(String); + +impl BinaryFunc { + pub fn evaluate(&self, _values: &HashMap>) -> bool { + todo!() + } +} diff --git a/iam/src/policy/function/bool_null.rs b/iam/src/policy/function/bool_null.rs index e48e210e..155f9883 100644 --- a/iam/src/policy/function/bool_null.rs +++ b/iam/src/policy/function/bool_null.rs @@ -7,7 +7,7 @@ pub type BoolFunc = InnerFunc; impl BoolFunc { pub fn evaluate_bool(&self, values: &HashMap>) -> bool { for inner in self.0.iter() { - if !match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) { + if !match values.get(inner.key.name().as_str()).and_then(|x| x.first()) { Some(x) => inner.values.0.to_string().as_str() == x, None => false, } { @@ -32,8 +32,7 @@ impl BoolFunc { } } -#[derive(Clone)] -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct BoolFuncValue(bool); impl Serialize for BoolFuncValue { diff --git a/iam/src/policy/function/condition.rs b/iam/src/policy/function/condition.rs index 7ce8e616..3de30660 100644 --- a/iam/src/policy/function/condition.rs +++ b/iam/src/policy/function/condition.rs @@ -1,12 +1,12 @@ use serde::de::{Error, MapAccess}; use serde::ser::SerializeMap; -use serde::{Deserialize, Serialize, Serializer}; +use serde::Deserialize; use std::collections::HashMap; use time::OffsetDateTime; use super::{addr::AddrFunc, binary::BinaryFunc, bool_null::BoolFunc, date::DateFunc, number::NumberFunc, string::StringFunc}; -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Debug)] pub enum Condition { StringEquals(StringFunc), StringNotEquals(StringFunc), @@ -102,7 +102,7 @@ impl Condition { StringNotEqualsIgnoreCase(s) => s.evaluate(for_all, true, false, true, values), StringLike(s) => s.evaluate(for_all, false, true, false, values), StringNotLike(s) => s.evaluate(for_all, false, true, true, values), - BinaryEquals(s) => todo!(), + BinaryEquals(s) => s.evaluate(values), IpAddress(s) => s.evaluate(values), NotIpAddress(s) => s.evaluate(values), Null(s) => s.evaluate_null(values), @@ -164,3 +164,35 @@ impl Condition { } } } + +impl PartialEq for Condition { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::StringEquals(l0), Self::StringEquals(r0)) => l0 == r0, + (Self::StringNotEquals(l0), Self::StringNotEquals(r0)) => l0 == r0, + (Self::StringEqualsIgnoreCase(l0), Self::StringEqualsIgnoreCase(r0)) => l0 == r0, + (Self::StringNotEqualsIgnoreCase(l0), Self::StringNotEqualsIgnoreCase(r0)) => l0 == r0, + (Self::StringLike(l0), Self::StringLike(r0)) => l0 == r0, + (Self::StringNotLike(l0), Self::StringNotLike(r0)) => l0 == r0, + (Self::BinaryEquals(l0), Self::BinaryEquals(r0)) => l0 == r0, + (Self::IpAddress(l0), Self::IpAddress(r0)) => l0 == r0, + (Self::NotIpAddress(l0), Self::NotIpAddress(r0)) => l0 == r0, + (Self::Null(l0), Self::Null(r0)) => l0 == r0, + (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, + (Self::NumericEquals(l0), Self::NumericEquals(r0)) => l0 == r0, + (Self::NumericNotEquals(l0), Self::NumericNotEquals(r0)) => l0 == r0, + (Self::NumericLessThan(l0), Self::NumericLessThan(r0)) => l0 == r0, + (Self::NumericLessThanEquals(l0), Self::NumericLessThanEquals(r0)) => l0 == r0, + (Self::NumericGreaterThan(l0), Self::NumericGreaterThan(r0)) => l0 == r0, + (Self::NumericGreaterThanIfExists(l0), Self::NumericGreaterThanIfExists(r0)) => l0 == r0, + (Self::NumericGreaterThanEquals(l0), Self::NumericGreaterThanEquals(r0)) => l0 == r0, + (Self::DateEquals(l0), Self::DateEquals(r0)) => l0 == r0, + (Self::DateNotEquals(l0), Self::DateNotEquals(r0)) => l0 == r0, + (Self::DateLessThan(l0), Self::DateLessThan(r0)) => l0 == r0, + (Self::DateLessThanEquals(l0), Self::DateLessThanEquals(r0)) => l0 == r0, + (Self::DateGreaterThan(l0), Self::DateGreaterThan(r0)) => l0 == r0, + (Self::DateGreaterThanEquals(l0), Self::DateGreaterThanEquals(r0)) => l0 == r0, + _ => false, + } + } +} diff --git a/iam/src/policy/function/date.rs b/iam/src/policy/function/date.rs index fa44536c..e3abaf88 100644 --- a/iam/src/policy/function/date.rs +++ b/iam/src/policy/function/date.rs @@ -8,7 +8,7 @@ pub type DateFunc = InnerFunc; impl DateFunc { pub fn evaluate(&self, op: impl Fn(&OffsetDateTime, &OffsetDateTime) -> bool, values: &HashMap>) -> bool { for inner in self.0.iter() { - let v = match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) { + let v = match values.get(inner.key.name().as_str()).and_then(|x| x.first()) { Some(x) => x, None => return false, }; @@ -26,8 +26,7 @@ impl DateFunc { } } -#[derive(Clone)] -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct DateFuncValue(OffsetDateTime); impl Serialize for DateFuncValue { @@ -52,7 +51,7 @@ impl<'de> Deserialize<'de> for DateFuncValue { { struct DateVisitor; - impl<'de> de::Visitor<'de> for DateVisitor { + impl de::Visitor<'_> for DateVisitor { type Value = DateFuncValue; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/iam/src/policy/function/func.rs b/iam/src/policy/function/func.rs index 2f75edca..caa647b8 100644 --- a/iam/src/policy/function/func.rs +++ b/iam/src/policy/function/func.rs @@ -7,10 +7,10 @@ use serde::{ use super::key::Key; -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(PartialEq, Eq, Debug)] pub struct InnerFunc(pub(crate) Vec>); -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(PartialEq, Eq, Debug)] pub struct FuncKeyValue { pub key: Key, pub values: T, diff --git a/iam/src/policy/function/key.rs b/iam/src/policy/function/key.rs index f5f224ee..73e0ae18 100644 --- a/iam/src/policy/function/key.rs +++ b/iam/src/policy/function/key.rs @@ -1,9 +1,9 @@ use super::key_name::KeyName; -use crate::policy::{Error, Validator}; +use crate::{policy::Error as PolicyError, sys::Validator}; +use ecstore::error::Error; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(into = "String")] #[serde(try_from = "&str")] pub struct Key { @@ -11,7 +11,9 @@ pub struct Key { pub variable: Option, } -impl Validator for Key {} +impl Validator for Key { + type Error = Error; +} impl Key { pub fn is(&self, other: &KeyName) -> bool { @@ -36,7 +38,7 @@ impl From for String { let mut data = String::from(Into::<&str>::into(&value.name)); if let Some(x) = value.variable.as_ref() { data.push('/'); - data.push_str(&x); + data.push_str(x); } data } @@ -47,7 +49,7 @@ impl TryFrom<&str> for Key { fn try_from(value: &str) -> Result { let mut iter = value.splitn(2, '/'); - let name = iter.next().ok_or_else(|| Error::InvalidKey(value.to_string()))?; + let name = iter.next().ok_or_else(|| PolicyError::InvalidKey(value.to_string()))?; let variable = iter.next().map(Into::into); Ok(Self { diff --git a/iam/src/policy/function/key_name.rs b/iam/src/policy/function/key_name.rs index 0ee33d95..321771ac 100644 --- a/iam/src/policy/function/key_name.rs +++ b/iam/src/policy/function/key_name.rs @@ -54,9 +54,9 @@ impl KeyName { KeyName::Aws(AwsKeyName::AWSUsername), KeyName::Aws(AwsKeyName::AWSGroups), // ldap - KeyName::Ldap(LdapKeyName::LDAPUser), - KeyName::Ldap(LdapKeyName::LDAPUsername), - KeyName::Ldap(LdapKeyName::LDAPGroups), + KeyName::Ldap(LdapKeyName::User), + KeyName::Ldap(LdapKeyName::Username), + KeyName::Ldap(LdapKeyName::Groups), // jwt KeyName::Jwt(JwtKeyName::JWTSub), KeyName::Jwt(JwtKeyName::JWTIss), @@ -252,13 +252,13 @@ pub enum SvcKeyName { #[serde(try_from = "&str", into = "&str")] pub enum LdapKeyName { #[strum(serialize = "ldap:user")] - LDAPUser, + User, #[strum(serialize = "ldap:username")] - LDAPUsername, + Username, #[strum(serialize = "ldap:groups")] - LDAPGroups, + Groups, } #[derive(Clone, EnumString, Debug, IntoStaticStr, Eq, PartialEq, Serialize, Deserialize)] @@ -312,7 +312,7 @@ mod tests { #[test_case("s3:x-amz-copy-source", KeyName::S3(S3KeyName::S3XAmzCopySource))] #[test_case("aws:SecureTransport", KeyName::Aws(AwsKeyName::AWSSecureTransport))] #[test_case("jwt:sub", KeyName::Jwt(JwtKeyName::JWTSub))] - #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::LDAPUser))] + #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::User))] #[test_case("sts:DurationSeconds", KeyName::Sts(StsKeyName::STSDurationSeconds))] #[test_case("svc:DurationSeconds", KeyName::Svc(SvcKeyName::SVCDurationSeconds))] fn key_name_from_str_successful(val: &str, except: KeyName) { @@ -332,7 +332,7 @@ mod tests { #[test_case("s3:x-amz-copy-source", KeyName::S3(S3KeyName::S3XAmzCopySource))] #[test_case("aws:SecureTransport", KeyName::Aws(AwsKeyName::AWSSecureTransport))] #[test_case("jwt:sub", KeyName::Jwt(JwtKeyName::JWTSub))] - #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::LDAPUser))] + #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::User))] #[test_case("sts:DurationSeconds", KeyName::Sts(StsKeyName::STSDurationSeconds))] #[test_case("svc:DurationSeconds", KeyName::Svc(SvcKeyName::SVCDurationSeconds))] fn key_name_deserialize(val: &str, except: KeyName) { @@ -349,7 +349,7 @@ mod tests { #[test_case("s3:x-amz-copy-source", KeyName::S3(S3KeyName::S3XAmzCopySource))] #[test_case("aws:SecureTransport", KeyName::Aws(AwsKeyName::AWSSecureTransport))] #[test_case("jwt:sub", KeyName::Jwt(JwtKeyName::JWTSub))] - #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::LDAPUser))] + #[test_case("ldap:user", KeyName::Ldap(LdapKeyName::User))] #[test_case("sts:DurationSeconds", KeyName::Sts(StsKeyName::STSDurationSeconds))] #[test_case("svc:DurationSeconds", KeyName::Svc(SvcKeyName::SVCDurationSeconds))] fn key_name_serialize(except: &str, value: KeyName) { diff --git a/iam/src/policy/function/number.rs b/iam/src/policy/function/number.rs index 4633c04b..3f94980c 100644 --- a/iam/src/policy/function/number.rs +++ b/iam/src/policy/function/number.rs @@ -8,14 +8,13 @@ use serde::{ pub type NumberFunc = InnerFunc; -#[derive(Clone)] -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct NumberFuncValue(i64); impl NumberFunc { pub fn evaluate(&self, op: impl Fn(&i64, &i64) -> bool, if_exists: bool, values: &HashMap>) -> bool { for inner in self.0.iter() { - let v = match values.get(inner.key.name().as_str()).and_then(|x| x.get(0)) { + let v = match values.get(inner.key.name().as_str()).and_then(|x| x.first()) { Some(x) => x, None => return if_exists, }; @@ -49,7 +48,7 @@ impl<'de> Deserialize<'de> for NumberFuncValue { { struct NumberVisitor; - impl<'de> Visitor<'de> for NumberVisitor { + impl Visitor<'_> for NumberVisitor { type Value = NumberFuncValue; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/iam/src/policy/function/string.rs b/iam/src/policy/function/string.rs index a32274e6..f59bbdbd 100644 --- a/iam/src/policy/function/string.rs +++ b/iam/src/policy/function/string.rs @@ -63,7 +63,7 @@ impl FuncKeyValue { .map(|c| { let mut c = Cow::from(c); for key in KeyName::COMMON_KEYS { - match values.get(key.name()).and_then(|x| x.get(0)) { + match values.get(key.name()).and_then(|x| x.first()) { Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(&key.var_name(), v)), _ => continue, }; @@ -93,7 +93,7 @@ impl FuncKeyValue { .map(|c| { let mut c = Cow::from(c); for key in KeyName::COMMON_KEYS { - match values.get(key.name()).and_then(|x| x.get(0)) { + match values.get(key.name()).and_then(|x| x.first()) { Some(v) if !v.is_empty() => return Cow::Owned(c.to_mut().replace(&key.var_name(), v)), _ => continue, }; @@ -118,8 +118,8 @@ impl FuncKeyValue { } /// 解析values字段 -#[derive(Clone)] -#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +#[derive(Clone, PartialEq, Eq, Debug)] + pub struct StringFuncValue(pub Set); impl Serialize for StringFuncValue { diff --git a/iam/src/policy/id.rs b/iam/src/policy/id.rs index 957b3dd9..d31f0dfd 100644 --- a/iam/src/policy/id.rs +++ b/iam/src/policy/id.rs @@ -1,15 +1,16 @@ +use ecstore::error::{Error, Result}; +use serde::{Deserialize, Serialize}; use std::ops::Deref; -use serde::{Deserialize, Serialize}; +use crate::sys::Validator; -use super::{Error, Validator}; - -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct ID(pub String); impl Validator for ID { + type Error = Error; /// if id is a valid utf string, then it is valid. - fn is_valid(&self) -> Result<(), Error> { + fn is_valid(&self) -> Result<()> { Ok(()) } } diff --git a/iam/src/policy/policy.rs b/iam/src/policy/policy.rs index 806ca387..38ec5dff 100644 --- a/iam/src/policy/policy.rs +++ b/iam/src/policy/policy.rs @@ -1,8 +1,10 @@ +use super::{Effect, Error as IamError, Statement, ID}; +use crate::sys::{Args, Validator, DEFAULT_VERSION}; +use ecstore::error::{Error, Result}; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; -use super::{Args, Effect, Error, Statement, Validator, DEFAULT_VERSION, ID}; - -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct Policy { pub id: ID, pub version: String, @@ -29,12 +31,81 @@ impl Policy { false } + + pub fn match_resource(&self, resource: &str) -> bool { + for statement in self.statements.iter() { + if statement.resources.match_resource(resource) { + return true; + } + } + false + } + + fn drop_duplicate_statements(&mut self) { + let mut dups = HashSet::new(); + for i in 0..self.statements.len() { + if dups.contains(&i) { + // i is already a duplicate of some statement, so we do not need to + // compare with it. + continue; + } + for j in (i + 1)..self.statements.len() { + if !self.statements[i].eq(&self.statements[j]) { + continue; + } + + // save duplicate statement index for removal. + dups.insert(j); + } + } + + // remove duplicate items from the slice. + let mut c = 0; + for i in 0..self.statements.len() { + if dups.contains(&i) { + continue; + } + self.statements[c] = self.statements[i].clone(); + c += 1; + } + self.statements.truncate(c); + } + pub fn merge_policies(inputs: Vec) -> Policy { + let mut merged = Policy::default(); + + for p in inputs { + if merged.version.is_empty() { + merged.version = p.version.clone(); + } + for st in p.statements { + merged.statements.push(st.clone()); + } + } + merged.drop_duplicate_statements(); + merged + } + + pub fn is_empty(&self) -> bool { + self.statements.is_empty() + } + + pub fn validate(&self) -> Result<()> { + self.is_valid() + } + + pub fn parse_config(data: &[u8]) -> Result { + let policy: Policy = serde_json::from_slice(data)?; + policy.validate()?; + Ok(policy) + } } impl Validator for Policy { - fn is_valid(&self) -> Result<(), Error> { + type Error = Error; + + fn is_valid(&self) -> Result<()> { if !self.id.is_empty() && !self.id.eq(DEFAULT_VERSION) { - return Err(Error::InvalidVersion(self.id.0.clone())); + return Err(IamError::InvalidVersion(self.id.0.clone()).into()); } for statement in self.statements.iter() { @@ -48,15 +119,19 @@ impl Validator for Policy { pub mod default { use std::{collections::HashSet, sync::LazyLock}; - use crate::policy::{ - action::{Action, AdminAction, KmsAction, S3Action}, - resource::Resource, - ActionSet, Effect, Functions, ResourceSet, Statement, DEFAULT_VERSION, + use crate::{ + policy::{ + action::{Action, AdminAction, KmsAction, S3Action}, + resource::Resource, + ActionSet, Effect, Functions, ResourceSet, Statement, + }, + sys::DEFAULT_VERSION, }; use super::Policy; - pub const DEFAULT_POLICIES: LazyLock<[(&'static str, Policy); 6]> = LazyLock::new(|| { + #[allow(clippy::incompatible_msrv)] + pub static DEFAULT_POLICIES: LazyLock<[(&'static str, Policy); 6]> = LazyLock::new(|| { [ ( "readwrite", diff --git a/iam/src/policy/resource.rs b/iam/src/policy/resource.rs index 5ae65469..529ec9b8 100644 --- a/iam/src/policy/resource.rs +++ b/iam/src/policy/resource.rs @@ -1,18 +1,20 @@ +use ecstore::error::{Error, Result}; +use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, hash::Hash, ops::Deref, }; -use serde::{Deserialize, Serialize}; +use crate::sys::Validator; use super::{ function::key_name::KeyName, utils::{path, wildcard}, - Error, Validator, + Error as IamError, }; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct ResourceSet(pub HashSet); impl ResourceSet { @@ -25,6 +27,16 @@ impl ResourceSet { false } + + pub fn match_resource(&self, resource: &str) -> bool { + for re in self.0.iter() { + if re.match_resource(resource) { + return true; + } + } + + false + } } impl Deref for ResourceSet { @@ -36,7 +48,8 @@ impl Deref for ResourceSet { } impl Validator for ResourceSet { - fn is_valid(&self) -> Result<(), Error> { + type Error = Error; + fn is_valid(&self) -> Result<()> { for resource in self.0.iter() { resource.is_valid()?; } @@ -45,7 +58,13 @@ impl Validator for ResourceSet { } } -#[derive(Hash, Eq, PartialEq, Serialize, Deserialize, Clone)] +impl PartialEq for ResourceSet { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.0.iter().all(|x| other.0.contains(x)) + } +} + +#[derive(Hash, Eq, PartialEq, Serialize, Deserialize, Clone, Debug)] pub enum Resource { S3(String), Kms(String), @@ -76,15 +95,19 @@ impl Resource { wildcard::is_match(pattern, resource) } + + pub fn match_resource(&self, resource: &str) -> bool { + self.is_match(resource, &HashMap::new()) + } } impl TryFrom<&str> for Resource { type Error = Error; fn try_from(value: &str) -> Result { let resource = if value.starts_with(Self::S3_PREFIX) { - Resource::S3(value[Self::S3_PREFIX.len()..].into()) + Resource::S3(value.strip_prefix(Self::S3_PREFIX).unwrap().into()) } else { - return Err(Error::InvalidResource("unknown".into(), value.into())); + return Err(IamError::InvalidResource("unknown".into(), value.into()).into()); }; resource.is_valid()?; @@ -93,11 +116,12 @@ impl TryFrom<&str> for Resource { } impl Validator for Resource { + type Error = Error; fn is_valid(&self) -> Result<(), Error> { match self { Self::S3(pattern) => { if pattern.is_empty() || pattern.starts_with('/') { - return Err(Error::InvalidResource("s3".into(), pattern.into())); + return Err(IamError::InvalidResource("s3".into(), pattern.into()).into()); } } Self::Kms(pattern) => { @@ -108,7 +132,7 @@ impl Validator for Resource { .map(|(i, _)| i) .is_some() { - return Err(Error::InvalidResource("kms".into(), pattern.into())); + return Err(IamError::InvalidResource("kms".into(), pattern.into()).into()); } } } diff --git a/iam/src/policy/statement.rs b/iam/src/policy/statement.rs index 481a3547..ddbfa2cf 100644 --- a/iam/src/policy/statement.rs +++ b/iam/src/policy/statement.rs @@ -1,8 +1,10 @@ +use crate::sys::{Args, Validator}; + +use super::{action::Action, ActionSet, Effect, Error as IamError, Functions, ResourceSet, ID}; +use ecstore::error::{Error, Result}; use serde::{Deserialize, Serialize}; -use super::{action::Action, ActionSet, Args, Effect, Error, Functions, ResourceSet, Validator, ID}; - -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct Statement { pub sid: ID, pub effect: Effect, @@ -60,17 +62,15 @@ impl Statement { resource.push('/'); } - if self.is_kms() { - if resource == "/" || self.resources.is_empty() { - break 'c self.conditions.evaluate(&args.conditions); - } + if self.is_kms() && (resource == "/" || self.resources.is_empty()) { + break 'c self.conditions.evaluate(args.conditions); } - if !self.resources.is_match(&resource, &args.conditions) && !self.is_admin() && !self.is_sts() { + if !self.resources.is_match(&resource, args.conditions) && !self.is_admin() && !self.is_sts() { break 'c false; } - self.conditions.evaluate(&args.conditions) + self.conditions.evaluate(args.conditions) }; self.effect.is_allowed(check) @@ -78,17 +78,18 @@ impl Statement { } impl Validator for Statement { - fn is_valid(&self) -> Result<(), Error> { + type Error = Error; + fn is_valid(&self) -> Result<()> { self.effect.is_valid()?; // check sid self.sid.is_valid()?; if self.actions.is_empty() || self.not_actions.is_empty() { - return Err(Error::NonAction); + return Err(IamError::NonAction.into()); } if self.resources.is_empty() { - return Err(Error::NonResource); + return Err(IamError::NonResource.into()); } self.actions.is_valid()?; @@ -98,3 +99,13 @@ impl Validator for Statement { Ok(()) } } + +impl PartialEq for Statement { + fn eq(&self, other: &Self) -> bool { + self.effect == other.effect + && self.actions == other.actions + && self.not_actions == other.not_actions + && self.resources == other.resources + && self.conditions == other.conditions + } +} diff --git a/iam/src/policy/utils.rs b/iam/src/policy/utils.rs index 298d2586..a396c154 100644 --- a/iam/src/policy/utils.rs +++ b/iam/src/policy/utils.rs @@ -5,7 +5,7 @@ use serde_json::Value; pub mod path; pub mod wildcard; -pub fn get_values_from_claims(claim: &HashMap, chaim_name: &str) -> (Vec, bool) { +pub fn _get_values_from_claims(claim: &HashMap, chaim_name: &str) -> (Vec, bool) { let mut result = vec![]; let Some(pname) = claim.get(chaim_name) else { return (result, false); @@ -39,7 +39,7 @@ pub fn get_values_from_claims(claim: &HashMap, chaim_name: &str) (result, true) } -pub fn split_path(path: &str, second_index: bool) -> (&str, &str) { +pub fn _split_path(path: &str, second_index: bool) -> (&str, &str) { let index = if second_index { let Some(first) = path.find('/') else { return (path, ""); @@ -63,7 +63,7 @@ pub fn split_path(path: &str, second_index: bool) -> (&str, &str) { #[cfg(test)] mod tests { - use super::split_path; + use super::_split_path; #[test_case::test_case("format.json", false => ("format.json", ""))] #[test_case::test_case("users/tester.json", false => ("users/", "tester.json"))] @@ -82,6 +82,6 @@ mod tests { ("policydb/groups/", "cn=project/d,ou=groups,ou=swengg,dc=min,dc=io.json")) ] fn test_split_path(path: &str, second_index: bool) -> (&str, &str) { - split_path(path, second_index) + _split_path(path, second_index) } } diff --git a/iam/src/service_type.rs b/iam/src/service_type.rs index 459cb08c..fd14ea0c 100644 --- a/iam/src/service_type.rs +++ b/iam/src/service_type.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::error::Error; #[derive(PartialEq, Eq, Debug)] pub enum ServiceType { diff --git a/iam/src/store.rs b/iam/src/store.rs index 96a4b7fc..946a8802 100644 --- a/iam/src/store.rs +++ b/iam/src/store.rs @@ -1,46 +1,133 @@ pub mod object; -use std::collections::HashMap; - -use ecstore::store_api::ObjectInfo; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{ - auth::UserIdentity, - cache::Cache, - policy::{MappedPolicy, PolicyDoc, UserType, DEFAULT_POLICIES}, -}; +use crate::{auth::UserIdentity, cache::Cache, policy::PolicyDoc}; +use ecstore::error::Result; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use time::OffsetDateTime; #[async_trait::async_trait] pub trait Store: Clone + Send + Sync + 'static { - async fn load_iam_config(&self, path: impl AsRef + Send) -> crate::Result<(Item, ObjectInfo)> - where - Item: DeserializeOwned; + async fn save_iam_config(&self, item: Item, path: impl AsRef + Send) -> Result<()>; + async fn load_iam_config(&self, path: impl AsRef + Send) -> Result; + async fn delete_iam_config(&self, path: impl AsRef + Send) -> Result<()>; - async fn save_iam_config(&self, item: Item, path: impl AsRef + Send) -> crate::Result<()>; - async fn delete_iam_config(&self, path: impl AsRef + Send) -> crate::Result<()>; + async fn save_user_identity(&self, name: &str, user_type: UserType, item: UserIdentity, ttl: Option) -> Result<()>; + async fn delete_user_identity(&self, name: &str, user_type: UserType) -> Result<()>; + async fn load_user_identity(&self, name: &str, user_type: UserType) -> Result; - async fn load_all(&self, cache: &Cache) -> crate::Result<()>; + async fn load_user(&self, name: &str, user_type: UserType, m: &mut HashMap) -> Result<()>; + async fn load_users(&self, user_type: UserType, m: &mut HashMap) -> Result<()>; + async fn load_secret_key(&self, name: &str, user_type: UserType) -> Result; - fn get_default_policyes() -> HashMap { - let default_policies = DEFAULT_POLICIES; - default_policies - .iter() - .map(|(n, p)| { - ( - n.to_string(), - PolicyDoc { - version: 1, - policy: p.clone(), - ..Default::default() - }, - ) - }) + async fn save_group_info(&self, name: &str, item: GroupInfo) -> Result<()>; + async fn delete_group_info(&self, name: &str) -> Result<()>; + async fn load_group(&self, name: &str, m: &mut HashMap) -> Result<()>; + async fn load_groups(&self, m: &mut HashMap) -> Result<()>; + + async fn save_policy_doc(&self, name: &str, item: PolicyDoc) -> Result<()>; + async fn delete_policy_doc(&self, name: &str) -> Result<()>; + async fn load_policy(&self, name: &str) -> Result; + async fn load_policy_doc(&self, name: &str, m: &mut HashMap) -> Result<()>; + async fn load_policy_docs(&self, m: &mut HashMap) -> Result<()>; + + async fn save_mapped_policy( + &self, + name: &str, + user_type: UserType, + is_group: bool, + item: MappedPolicy, + ttl: Option, + ) -> Result<()>; + async fn delete_mapped_policy(&self, name: &str, user_type: UserType, is_group: bool) -> Result<()>; + async fn load_mapped_policy( + &self, + name: &str, + user_type: UserType, + is_group: bool, + m: &mut HashMap, + ) -> Result<()>; + async fn load_mapped_policys(&self, user_type: UserType, is_group: bool, m: &mut HashMap) + -> Result<()>; + + async fn load_all(&self, cache: &Cache) -> Result<()>; +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum UserType { + Svc, + Sts, + Reg, +} + +impl UserType { + pub fn prefix(&self) -> &'static str { + match self { + UserType::Svc => "service-accounts/", + UserType::Sts => "sts/", + UserType::Reg => "users/", + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct MappedPolicy { + pub version: i64, + pub policies: String, + pub update_at: OffsetDateTime, +} + +impl Default for MappedPolicy { + fn default() -> Self { + Self { + version: 0, + policies: "".to_owned(), + update_at: OffsetDateTime::now_utc(), + } + } +} + +impl MappedPolicy { + pub fn new(policy: &str) -> Self { + Self { + version: 1, + policies: policy.to_owned(), + update_at: OffsetDateTime::now_utc(), + } + } + + pub fn to_slice(&self) -> Vec { + self.policies + .split(",") + .filter(|v| !v.trim().is_empty()) + .map(|v| v.to_string()) .collect() } - async fn load_users(&self, user_type: UserType) -> crate::Result>; - - async fn load_policy_docs(&self) -> crate::Result>; - async fn load_mapped_policy(&self, user_type: UserType, name: &str, is_group: bool) -> crate::Result; + pub fn policy_set(&self) -> HashSet { + self.policies + .split(",") + .filter(|v| !v.trim().is_empty()) + .map(|v| v.to_string()) + .collect() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct GroupInfo { + pub version: i64, + pub status: String, + pub members: Vec, + pub update_at: Option, +} + +impl GroupInfo { + pub fn new(members: Vec) -> Self { + Self { + version: 1, + status: "enabled".to_owned(), + members, + update_at: Some(OffsetDateTime::now_utc()), + } + } } diff --git a/iam/src/store/object.rs b/iam/src/store/object.rs index 7a9844b2..71e0855e 100644 --- a/iam/src/store/object.rs +++ b/iam/src/store/object.rs @@ -1,26 +1,109 @@ -use std::{collections::HashMap, path::Path, sync::Arc}; - -use ecstore::{ - config::error::is_err_config_not_found, - store::ECStore, - store_api::{ObjectIO, ObjectInfo, ObjectOptions, PutObjReader}, - store_list_objects::{ObjectInfoOrErr, WalkOptions}, - utils::path::{dir, SLASH_SEPARATOR}, - StorageAPI, -}; -use futures::future::try_join_all; -use log::{debug, warn}; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::broadcast; -use tokio::sync::mpsc::{self}; - -use super::Store; +use super::{GroupInfo, MappedPolicy, Store, UserType}; use crate::{ auth::UserIdentity, cache::{Cache, CacheEntity}, - policy::{utils::split_path, MappedPolicy, PolicyDoc, UserType}, - Error, + error::{is_err_no_such_policy, is_err_no_such_user}, + get_global_action_cred, + manager::get_default_policyes, + policy::PolicyDoc, }; +use ecstore::{ + config::{ + common::{delete_config, read_config, read_config_with_metadata, save_config}, + error::is_err_config_not_found, + RUSTFS_CONFIG_PREFIX, + }, + error::{Error, Result}, + store::ECStore, + store_api::{ObjectInfo, ObjectOptions}, + store_list_objects::{ObjectInfoOrErr, WalkOptions}, + utils::path::{path_join_buf, SLASH_SEPARATOR}, +}; +use futures::future::join_all; +use lazy_static::lazy_static; +use serde::{de::DeserializeOwned, Serialize}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::broadcast::{self, Receiver as B_Receiver}; +use tokio::sync::mpsc::{self, Sender}; +use tracing::{debug, info, warn}; + +lazy_static! { + pub static ref IAM_CONFIG_PREFIX: String = format!("{}/iam", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_USERS_PREFIX: String = format!("{}/iam/users/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_SERVICE_ACCOUNTS_PREFIX: String = format!("{}/iam/service-accounts/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_GROUPS_PREFIX: String = format!("{}/iam/groups/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICIES_PREFIX: String = format!("{}/iam/policies/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_STS_PREFIX: String = format!("{}/iam/sts/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICY_DB_PREFIX: String = format!("{}/iam/policydb/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICY_DB_USERS_PREFIX: String = format!("{}/iam/policydb/users/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICY_DB_STS_USERS_PREFIX: String = format!("{}/iam/policydb/sts-users/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICY_DB_SERVICE_ACCOUNTS_PREFIX: String = + format!("{}/iam/policydb/service-accounts/", RUSTFS_CONFIG_PREFIX); + pub static ref IAM_CONFIG_POLICY_DB_GROUPS_PREFIX: String = format!("{}/iam/policydb/groups/", RUSTFS_CONFIG_PREFIX); +} + +const IAM_IDENTITY_FILE: &str = "identity.json"; +const IAM_POLICY_FILE: &str = "policy.json"; +const IAM_GROUP_MEMBERS_FILE: &str = "members.json"; + +fn get_user_identity_path(user: &str, user_type: UserType) -> String { + let base_path: &str = match user_type { + UserType::Svc => &IAM_CONFIG_SERVICE_ACCOUNTS_PREFIX, + UserType::Sts => &IAM_CONFIG_STS_PREFIX, + _ => &IAM_CONFIG_USERS_PREFIX, + }; + + path_join_buf(&[base_path, user, IAM_IDENTITY_FILE]) +} + +fn get_group_info_path(group: &str) -> String { + path_join_buf(&[&IAM_CONFIG_GROUPS_PREFIX, group, IAM_GROUP_MEMBERS_FILE]) +} + +fn get_policy_doc_path(name: &str) -> String { + path_join_buf(&[&IAM_CONFIG_POLICIES_PREFIX, name, IAM_POLICY_FILE]) +} + +fn get_mapped_policy_path(name: &str, user_type: UserType, is_group: bool) -> String { + if is_group { + return path_join_buf(&[&IAM_CONFIG_POLICY_DB_GROUPS_PREFIX, format!("{}.json", name).as_str()]); + } + match user_type { + UserType::Svc => path_join_buf(&[ + &IAM_CONFIG_POLICY_DB_SERVICE_ACCOUNTS_PREFIX, + format!("{}.json", name).as_str(), + ]), + UserType::Sts => path_join_buf(&[&IAM_CONFIG_POLICY_DB_STS_USERS_PREFIX, format!("{}.json", name).as_str()]), + _ => path_join_buf(&[&IAM_CONFIG_POLICY_DB_USERS_PREFIX, format!("{}.json", name).as_str()]), + } +} + +#[derive(Debug)] +pub struct StringOrErr { + pub item: Option, + pub err: Option, +} + +const USERS_LIST_KEY: &str = "users/"; +const SVC_ACC_LIST_KEY: &str = "service-accounts/"; +const GROUPS_LIST_KEY: &str = "groups/"; +const POLICIES_LIST_KEY: &str = "policies/"; +const STS_LIST_KEY: &str = "sts/"; +const POLICY_DB_PREFIX: &str = "policydb/"; +const POLICY_DB_USERS_LIST_KEY: &str = "policydb/users/"; +const POLICY_DB_STS_USERS_LIST_KEY: &str = "policydb/sts-users/"; +const POLICY_DB_GROUPS_LIST_KEY: &str = "policydb/groups/"; + +// split_path splits a path into a top-level directory and a child item. The +// parent directory retains the trailing slash. +fn split_path(s: &str, last_index: bool) -> (&str, &str) { + let i = if last_index { s.rfind('/') } else { s.find('/') }; + + match i { + Some(index) => (&s[..index + 1], &s[index + 1..]), + None => (s, ""), + } +} #[derive(Clone)] pub struct ObjectStore { @@ -34,353 +117,924 @@ impl ObjectStore { Self { object_api } } - async fn list_iam_config_items(&self, prefix: &str) -> crate::Result> { + fn decrypt_data(data: &[u8]) -> Result> { + let de = crypto::decrypt_data(get_global_action_cred().unwrap_or_default().secret_key.as_bytes(), data)?; + Ok(de) + } + + fn encrypt_data(data: &[u8]) -> Result> { + let en = crypto::encrypt_data(get_global_action_cred().unwrap_or_default().secret_key.as_bytes(), data)?; + Ok(en) + } + + async fn load_iamconfig_bytes_with_metadata(&self, path: impl AsRef + Send) -> Result<(Vec, ObjectInfo)> { + let (data, obj) = read_config_with_metadata(self.object_api.clone(), path.as_ref(), &ObjectOptions::default()).await?; + + Ok((Self::decrypt_data(&data)?, obj)) + } + + async fn list_iam_config_items(&self, prefix: &str, ctx_rx: B_Receiver, sender: Sender) { debug!("list iam config items, prefix: {}", &prefix); // todo, 实现walk,使用walk - let (ctx_tx, ctx_rx) = broadcast::channel(1); - // let prefix = format!("{}{}", prefix, item); - let ctx_rx = ctx_rx.resubscribe(); + let store = self.object_api.clone(); let (tx, mut rx) = mpsc::channel::(100); - let store = self.object_api.clone(); let path = prefix.to_owned(); tokio::spawn(async move { store.walk(ctx_rx, Self::BUCKET_NAME, &path, tx, WalkOptions::default()).await }); - let mut ret = Vec::new(); + let prefix = prefix.to_owned(); + tokio::spawn(async move { + while let Some(v) = rx.recv().await { + if let Some(err) = v.err { + let _ = sender + .send(StringOrErr { + item: None, + err: Some(err), + }) + .await; + return; + } + + if let Some(info) = v.item { + let name = info.name.trim_start_matches(&prefix).trim_end_matches(SLASH_SEPARATOR); + let _ = sender + .send(StringOrErr { + item: Some(name.to_owned()), + err: None, + }) + .await; + } + } + }); + } + + async fn list_all_iamconfig_items(&self) -> Result>> { + let (ctx_tx, ctx_rx) = broadcast::channel(1); + let (tx, mut rx) = mpsc::channel::(100); + + self.list_iam_config_items(format!("{}/", *IAM_CONFIG_PREFIX).as_str(), ctx_rx, tx) + .await; + + let mut res = HashMap::new(); while let Some(v) = rx.recv().await { if let Some(err) = v.err { warn!("list_iam_config_items {:?}", err); let _ = ctx_tx.send(true); - return Err(Error::EcstoreError(err)); + return Err(err); } if let Some(item) = v.item { - let name = item.name.trim_start_matches(prefix).trim_end_matches(SLASH_SEPARATOR); - ret.push(name.to_owned()); + let last_index = item.starts_with(POLICY_DB_PREFIX); + let (list_key, trimmed_item) = split_path(&item, last_index); + res.entry(list_key.to_owned()) + .or_insert_with(Vec::new) + .push(trimmed_item.to_owned()); } } - Ok(ret) + let _ = ctx_tx.send(true); - // match items { - // Ok(items) => Result::<_, crate::Error>::Ok(items.prefixes), - // Err(e) => { - // if is_err_config_not_found(&e) { - // Result::<_, crate::Error>::Ok(vec![]) - // } else { - // Err(Error::StringError(format!("list {prefix} failed, err: {e:?}"))) - // } - // } - // } - - // TODO: FIXME: - // Ok(try_join_all(futures).await?.into_iter().flat_map(|x| x.into_iter()).collect()) + Ok(res) } - async fn load_policy(&self, name: &str) -> crate::Result { - let (mut policy, object) = self - .load_iam_config::(&format!("config/iam/policies/{name}/policy.json")) - .await?; + async fn load_policy_doc_concurrent(&self, names: &[String]) -> Result> { + let mut futures = Vec::with_capacity(names.len()); - if policy.version == 0 { - policy.create_date = object.mod_time; - policy.update_date = object.mod_time; + for name in names { + let policy_name = ecstore::utils::path::dir(name); + futures.push(async move { + match self.load_policy(&policy_name).await { + Ok(p) => Ok(p), + Err(err) => { + if !is_err_no_such_policy(&err) { + Err(Error::msg(std::format!("load policy doc failed: {}", err))) + } else { + Ok(PolicyDoc::default()) + } + } + } + }); } - Ok(policy) + let results = join_all(futures).await; + + let mut policies = Vec::with_capacity(results.len()); + for r in results { + match r { + Ok(p) => policies.push(p), + Err(e) => return Err(e), + } + } + + Ok(policies) } - async fn load_user_identity(&self, user_type: UserType, name: &str) -> crate::Result> { - let (mut user, _) = self - .load_iam_config::(&format!( - "config/iam/{base}{name}/identity.json", - base = user_type.prefix(), - name = name - )) - .await?; + async fn load_user_concurrent(&self, names: &[String], user_type: UserType) -> Result> { + let mut futures = Vec::with_capacity(names.len()); - if user.credentials.is_expired() { - return Ok(None); + for name in names { + let user_name = ecstore::utils::path::dir(name); + futures.push(async move { + match self.load_user_identity(&user_name, user_type).await { + Ok(res) => Ok(res), + Err(err) => { + if !is_err_no_such_user(&err) { + Err(Error::msg(std::format!("load user failed: {}", err))) + } else { + Ok(UserIdentity::default()) + } + } + } + }); } - if user.credentials.access_key.is_empty() { - user.credentials.access_key = name.to_owned(); + let results = join_all(futures).await; + + let mut users = Vec::with_capacity(results.len()); + for r in results { + match r { + Ok(u) => users.push(u), + Err(e) => return Err(e), + } } - - // todo, 校验session token - - Ok(Some(user)) + Ok(users) } + + async fn load_mapped_policy_internal(&self, name: &str, user_type: UserType, is_group: bool) -> Result { + let info: MappedPolicy = self + .load_iam_config(get_mapped_policy_path(name, user_type, is_group)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; + + Ok(info) + } + + async fn load_mapped_policy_concurrent( + &self, + names: &[String], + user_type: UserType, + is_group: bool, + ) -> Result> { + let mut futures = Vec::with_capacity(names.len()); + + for name in names { + let policy_name = name.trim_end_matches(".json"); + futures.push(async move { + match self.load_mapped_policy_internal(policy_name, user_type, is_group).await { + Ok(p) => Ok(p), + Err(err) => { + if !is_err_no_such_policy(&err) { + Err(Error::msg(std::format!("load mapped policy failed: {}", err))) + } else { + Ok(MappedPolicy::default()) + } + } + } + }); + } + + let results = join_all(futures).await; + + let mut policies = Vec::with_capacity(results.len()); + for r in results { + match r { + Ok(p) => policies.push(p), + Err(e) => return Err(e), + } + } + + Ok(policies) + } + + // async fn load_policy(&self, name: &str) -> Result { + // let mut policy = self + // .load_iam_config::(&format!("config/iam/policies/{name}/policy.json")) + // .await?; + + // // FIXME: + // // if policy.version == 0 { + // // policy.create_date = object.mod_time; + // // policy.update_date = object.mod_time; + // // } + + // Ok(policy) + // } + + // async fn load_user_identity(&self, user_type: UserType, name: &str) -> Result> { + // let mut user = self + // .load_iam_config::(&format!( + // "config/iam/{base}{name}/identity.json", + // base = user_type.prefix(), + // name = name + // )) + // .await?; + + // if user.credentials.is_expired() { + // return Ok(None); + // } + + // if user.credentials.access_key.is_empty() { + // user.credentials.access_key = name.to_owned(); + // } + + // // todo, 校验session token + + // Ok(Some(user)) + // } } #[async_trait::async_trait] impl Store for ObjectStore { - async fn delete_iam_config(&self, path: impl AsRef + Send) -> crate::Result<()> { - self.object_api - .delete_object( - Self::BUCKET_NAME, - path.as_ref(), - ObjectOptions { - delete_prefix: true, - delete_prefix_object: true, - ..Default::default() - }, - ) - .await - .map_err(crate::Error::EcstoreError)?; + async fn load_iam_config(&self, path: impl AsRef + Send) -> Result { + let mut data = read_config(self.object_api.clone(), path.as_ref()).await?; - Ok(()) + data = Self::decrypt_data(&data)?; + + Ok(serde_json::from_slice(&data)?) } - async fn load_iam_config(&self, path: impl AsRef + Send) -> crate::Result<(Item, ObjectInfo)> - where - Item: DeserializeOwned, - { - debug!("load iam config, path: {}", path.as_ref()); - let mut reader = self - .object_api - .get_object_reader(Self::BUCKET_NAME, path.as_ref(), None, Default::default(), &Default::default()) - .await - .map_err(crate::Error::EcstoreError)?; - - let data = reader.read_all().await.map_err(crate::Error::EcstoreError)?; - // let data = crypto::decrypt_data(&[], &data)?; - - Ok(( - serde_json::from_slice(&data).map_err(|e| crate::Error::StringError(e.to_string()))?, - reader.object_info, - )) - } - #[tracing::instrument(level = "debug", skip(self, item, path))] - async fn save_iam_config(&self, item: Item, path: impl AsRef + Send) -> crate::Result<()> { - let data = serde_json::to_vec(&item).map_err(|e| crate::Error::StringError(e.to_string()))?; - // let data = crypto::encrypt_data(&[], &data)?; + async fn save_iam_config(&self, item: Item, path: impl AsRef + Send) -> Result<()> { + let mut data = serde_json::to_vec(&item)?; + data = Self::encrypt_data(&data)?; - self.object_api - .put_object( - Self::BUCKET_NAME, - path.as_ref(), - &mut PutObjReader::from_vec(data), - &ObjectOptions { - max_parity: true, - ..Default::default() - }, - ) + save_config(self.object_api.clone(), path.as_ref(), &data).await + } + async fn delete_iam_config(&self, path: impl AsRef + Send) -> Result<()> { + delete_config(self.object_api.clone(), path.as_ref()).await + } + + async fn save_user_identity( + &self, + name: &str, + user_type: UserType, + user_identity: UserIdentity, + _ttl: Option, + ) -> Result<()> { + self.save_iam_config(user_identity, get_user_identity_path(name, user_type)) .await - .map_err(crate::Error::EcstoreError)?; + } + async fn delete_user_identity(&self, name: &str, user_type: UserType) -> Result<()> { + self.delete_iam_config(get_user_identity_path(name, user_type)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; + Ok(()) + } + async fn load_user_identity(&self, name: &str, user_type: UserType) -> Result { + let mut u: UserIdentity = self + .load_iam_config(get_user_identity_path(name, user_type)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchUser(name.to_owned())) + } else { + err + } + })?; + if u.credentials.is_expired() { + let _ = self.delete_iam_config(get_user_identity_path(name, user_type)).await; + let _ = self.delete_iam_config(get_mapped_policy_path(name, user_type, false)).await; + return Err(Error::new(crate::error::Error::NoSuchUser(name.to_owned()))); + } + + if u.credentials.access_key.is_empty() { + u.credentials.access_key = name.to_owned(); + } + + if u.credentials.session_token.is_empty() { + // TODO: 解析sts + } + + Ok(u) + } + async fn load_user(&self, name: &str, user_type: UserType, m: &mut HashMap) -> Result<()> { + self.load_user_identity(name, user_type).await.map(|u| { + m.insert(name.to_owned(), u); + }) + } + async fn load_users(&self, user_type: UserType, m: &mut HashMap) -> Result<()> { + let base_prefix = match user_type { + UserType::Reg => IAM_CONFIG_USERS_PREFIX.as_str(), + UserType::Svc => IAM_CONFIG_SERVICE_ACCOUNTS_PREFIX.as_str(), + UserType::Sts => IAM_CONFIG_STS_PREFIX.as_str(), + }; + + let (ctx_tx, ctx_rx) = broadcast::channel(1); + let (tx, mut rx) = mpsc::channel::(100); + + self.list_iam_config_items(base_prefix, ctx_rx, tx).await; + + while let Some(v) = rx.recv().await { + if let Some(err) = v.err { + warn!("list_iam_config_items {:?}", err); + let _ = ctx_tx.send(true); + + return Err(err); + } + + if let Some(item) = v.item { + let name = ecstore::utils::path::dir(&item); + self.load_user(&name, user_type, m).await?; + } + } + let _ = ctx_tx.send(true); + Ok(()) + } + async fn load_secret_key(&self, name: &str, user_type: UserType) -> Result { + let u: UserIdentity = self + .load_iam_config(get_user_identity_path(name, user_type)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchUser(name.to_owned())) + } else { + err + } + })?; + + Ok(u.credentials.secret_key) + } + + async fn save_group_info(&self, name: &str, item: GroupInfo) -> Result<()> { + self.save_iam_config(item, get_group_info_path(name)).await + } + async fn delete_group_info(&self, name: &str) -> Result<()> { + self.delete_iam_config(get_group_info_path(name)).await.map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; + Ok(()) + } + async fn load_group(&self, name: &str, m: &mut HashMap) -> Result<()> { + let u: GroupInfo = self.load_iam_config(get_group_info_path(name)).await.map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; + + m.insert(name.to_owned(), u); + Ok(()) + } + async fn load_groups(&self, m: &mut HashMap) -> Result<()> { + let (ctx_tx, ctx_rx) = broadcast::channel(1); + let (tx, mut rx) = mpsc::channel::(100); + + self.list_iam_config_items(&IAM_CONFIG_GROUPS_PREFIX, ctx_rx, tx).await; + + while let Some(v) = rx.recv().await { + if let Some(err) = v.err { + warn!("list_iam_config_items {:?}", err); + let _ = ctx_tx.send(true); + + return Err(err); + } + + if let Some(item) = v.item { + let name = ecstore::utils::path::dir(&item); + self.load_group(&name, m).await?; + } + } + let _ = ctx_tx.send(true); Ok(()) } - async fn load_policy_docs(&self) -> crate::Result> { - let paths = self.list_iam_config_items("config/iam/policies/").await?; - - let mut result = Self::get_default_policyes(); - for path in paths { - let name = Path::new(&path).iter().rev().nth(0).unwrap(); - - let (mut policy_doc, object_info) = self - .load_iam_config::(format!("config/iam/policies/{}/policy.json", name.to_str().unwrap())) - .await?; - - if policy_doc.version == 0 { - policy_doc.create_date = object_info.mod_time; - policy_doc.update_date = object_info.mod_time; + async fn save_policy_doc(&self, name: &str, policy_doc: PolicyDoc) -> Result<()> { + self.save_iam_config(policy_doc, get_policy_doc_path(name)).await + } + async fn delete_policy_doc(&self, name: &str) -> Result<()> { + self.delete_iam_config(get_policy_doc_path(name)).await.map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err } + })?; + Ok(()) + } + async fn load_policy(&self, name: &str) -> Result { + let (data, obj) = self + .load_iamconfig_bytes_with_metadata(get_policy_doc_path(name)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; - result.insert(name.to_str().unwrap().to_owned(), policy_doc); + let mut info = PolicyDoc::try_from(data)?; + + if info.version == 0 { + info.create_date = obj.mod_time; + info.update_date = obj.mod_time; } - Ok(result) + Ok(info) } - async fn load_users(&self, user_type: UserType) -> crate::Result> { - let paths = self - .list_iam_config_items(format!("{}{}", "config/iam/", user_type.prefix()).as_str()) - .await?; + async fn load_policy_doc(&self, name: &str, m: &mut HashMap) -> Result<()> { + let info = self.load_policy(name).await?; + m.insert(name.to_owned(), info); - let mut result = HashMap::new(); - for path in paths { - let name = Path::new(&path).iter().rev().nth(0).unwrap(); + Ok(()) + } + async fn load_policy_docs(&self, m: &mut HashMap) -> Result<()> { + let (ctx_tx, ctx_rx) = broadcast::channel(1); + let (tx, mut rx) = mpsc::channel::(100); - let (mut user_identity, _) = self - .load_iam_config::(format!("config/iam/users/{}/identity.json", name.to_str().unwrap())) - .await?; + self.list_iam_config_items(&IAM_CONFIG_POLICIES_PREFIX, ctx_rx, tx).await; - if user_identity.credentials.is_expired() { - return Err(Error::NoSuchUser(name.to_str().unwrap().to_owned())); + while let Some(v) = rx.recv().await { + if let Some(err) = v.err { + warn!("list_iam_config_items {:?}", err); + let _ = ctx_tx.send(true); + + return Err(err); } - if user_identity.credentials.access_key.is_empty() { - user_identity.credentials.access_key = name.to_str().unwrap().to_owned(); + if let Some(item) = v.item { + let name = ecstore::utils::path::dir(&item); + self.load_policy_doc(&name, m).await?; } - - // todo 解析 sts - - result.insert(name.to_str().unwrap().to_owned(), user_identity); } - - Ok(result) + let _ = ctx_tx.send(true); + Ok(()) } - /// load all and make a new cache. - async fn load_all(&self, cache: &Cache) -> crate::Result<()> { - let _items = &[ - "policydb/", - "policies/", - "groups/", - "policydb/users/", - "policydb/groups/", - "service-accounts/", - "policydb/sts-users/", - "sts/", - ]; + async fn save_mapped_policy( + &self, + name: &str, + user_type: UserType, + is_group: bool, + mapped_policy: MappedPolicy, + _ttl: Option, + ) -> Result<()> { + self.save_iam_config(mapped_policy, get_mapped_policy_path(name, user_type, is_group)) + .await + } + async fn delete_mapped_policy(&self, name: &str, user_type: UserType, is_group: bool) -> Result<()> { + self.delete_iam_config(get_mapped_policy_path(name, user_type, is_group)) + .await + .map_err(|err| { + if is_err_config_not_found(&err) { + Error::new(crate::error::Error::NoSuchPolicy) + } else { + err + } + })?; + Ok(()) + } + async fn load_mapped_policy( + &self, + name: &str, + user_type: UserType, + is_group: bool, + m: &mut HashMap, + ) -> Result<()> { + let info = self.load_mapped_policy_internal(name, user_type, is_group).await?; - let items = self.list_iam_config_items("config/iam/").await?; - debug!("all iam items: {items:?}"); + m.insert(name.to_owned(), info); - let (policy_docs, users, user_policies, sts_policies, sts_accounts) = ( - Arc::new(tokio::sync::Mutex::new(CacheEntity::new(Self::get_default_policyes()))), - Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), - Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), - Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), - Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), - ); + Ok(()) + } + async fn load_mapped_policys( + &self, + user_type: UserType, + is_group: bool, + m: &mut HashMap, + ) -> Result<()> { + let base_path = { + if is_group { + IAM_CONFIG_POLICY_DB_GROUPS_PREFIX.as_str() + } else { + match user_type { + UserType::Svc => IAM_CONFIG_POLICY_DB_SERVICE_ACCOUNTS_PREFIX.as_str(), + UserType::Sts => IAM_CONFIG_POLICY_DB_STS_USERS_PREFIX.as_str(), + _ => IAM_CONFIG_POLICY_DB_USERS_PREFIX.as_str(), + } + } + }; + let (ctx_tx, ctx_rx) = broadcast::channel(1); + let (tx, mut rx) = mpsc::channel::(100); - // 一次读取32个元素 - let iter = items - .iter() - .map(|item| item.trim_start_matches("config/iam/")) - .map(|item| split_path(item, item.starts_with("policydb/"))) - .filter_map(|(list_key, trimmed_item)| { - debug!("list_key: {list_key}, trimmed_item: {trimmed_item}"); + self.list_iam_config_items(base_path, ctx_rx, tx).await; - if list_key == "format.json" { - return None; + while let Some(v) = rx.recv().await { + if let Some(err) = v.err { + warn!("list_iam_config_items {:?}", err); + let _ = ctx_tx.send(true); + + return Err(err); + } + + if let Some(item) = v.item { + let name = item.trim_end_matches(".json"); + self.load_mapped_policy(name, user_type, is_group, m).await?; + } + } + let _ = ctx_tx.send(true); + Ok(()) + } + + async fn load_all(&self, cache: &Cache) -> Result<()> { + let listed_config_items = self.list_all_iamconfig_items().await?; + + if let Some(policies_list) = listed_config_items.get(POLICIES_LIST_KEY) { + let mut policies_list = policies_list.clone(); + + let mut policy_docs_cache = CacheEntity::new(get_default_policyes()); + + loop { + if policies_list.len() < 32 { + let policy_docs = self.load_policy_doc_concurrent(&policies_list).await?; + + for (idx, p) in policy_docs.into_iter().enumerate() { + if p.policy.version.is_empty() { + continue; + } + + let policy_name = ecstore::utils::path::dir(&policies_list[idx]); + + info!("load policy: {}", policy_name); + + policy_docs_cache.insert(policy_name, p); + } + break; } - let (policy_docs, users, user_policies, sts_policies, sts_accounts) = ( - policy_docs.clone(), - users.clone(), - user_policies.clone(), - sts_policies.clone(), - sts_accounts.clone(), - ); + let policy_docs = self.load_policy_doc_concurrent(&policies_list).await?; - Some(async move { - match list_key { - "policies/" => { - let trimmed_item = dir(trimmed_item); - let name = trimmed_item.trim_end_matches('/'); - let policy_doc = self.load_policy(name).await?; - policy_docs.lock().await.insert(name.to_owned(), policy_doc); - } - "users/" => { - let name = dir(trimmed_item); - if let Some(user) = self.load_user_identity(UserType::Reg, &name).await? { - users.lock().await.insert(name.to_owned(), user); - }; - } - "groups/" => {} - "policydb/users/" | "policydb/groups/" => { - let name = trimmed_item.strip_suffix(".json").unwrap_or(trimmed_item); - let mapped_policy = self - .load_mapped_policy(UserType::Reg, name, list_key == "policydb/groups/") - .await?; - if !mapped_policy.policies.is_empty() { - user_policies.lock().await.insert(name.to_owned(), mapped_policy); - } - } - "service-accounts/" => { - let trimmed_item = dir(trimmed_item); - let name = trimmed_item.trim_end_matches('/'); - let Some(user) = self.load_user_identity(UserType::Svc, name).await? else { - return Ok(()); - }; - - let parent = user.credentials.parent_user.clone(); - - { - users.lock().await.insert(name.to_owned(), user); - } - - if users.lock().await.get(&parent).is_some() { - return Ok(()); - } - - match self.load_mapped_policy(UserType::Sts, parent.as_str(), false).await { - Ok(m) => sts_policies.lock().await.insert(name.to_owned(), m), - Err(Error::EcstoreError(e)) if is_err_config_not_found(&e) => return Ok(()), - Err(e) => return Err(e), - }; - } - "sts/" => { - let name = dir(trimmed_item); - if let Some(user) = self.load_user_identity(UserType::Sts, &name).await? { - warn!("sts_accounts insert {}, user {:?}", name, &user.credentials.access_key); - sts_accounts.lock().await.insert(name.to_owned(), user); - }; - } - "policydb/sts-users/" => { - let name = trimmed_item.strip_suffix(".json").unwrap_or(trimmed_item); - let mapped_policy = self.load_mapped_policy(UserType::Sts, name, false).await?; - if !mapped_policy.policies.is_empty() { - sts_policies.lock().await.insert(name.to_owned(), mapped_policy); - } - } - _ => {} + for (idx, p) in policy_docs.into_iter().enumerate() { + if p.policy.version.is_empty() { + continue; } - crate::Result::Ok(()) - }) - }); + let policy_name = ecstore::utils::path::dir(&policies_list[idx]); + info!("load policy: {}", policy_name); + policy_docs_cache.insert(policy_name, p); + } - let mut all_futures = Vec::with_capacity(32); + policies_list = policies_list.split_off(32); + } - for f in iter { - all_futures.push(f); + cache.policy_docs.store(Arc::new(policy_docs_cache.update_load_time())); + } - if all_futures.len() == 32 { - try_join_all(all_futures).await?; - all_futures = Vec::with_capacity(32); + let mut user_items_cache = CacheEntity::default(); + + // users + if let Some(item_name_list) = listed_config_items.get(USERS_LIST_KEY) { + let mut item_name_list = item_name_list.clone(); + + // let mut items_cache = CacheEntity::default(); + + loop { + if item_name_list.len() < 32 { + let items = self.load_user_concurrent(&item_name_list, UserType::Reg).await?; + + for (idx, p) in items.into_iter().enumerate() { + if p.credentials.access_key.is_empty() { + continue; + } + + let name = ecstore::utils::path::dir(&item_name_list[idx]); + info!("load reg user: {}", name); + user_items_cache.insert(name, p); + } + break; + } + + let items = self.load_user_concurrent(&item_name_list, UserType::Reg).await?; + + for (idx, p) in items.into_iter().enumerate() { + if p.credentials.access_key.is_empty() { + continue; + } + + let name = ecstore::utils::path::dir(&item_name_list[idx]); + info!("load reg user: {}", name); + user_items_cache.insert(name, p); + } + + item_name_list = item_name_list.split_off(32); + } + + // cache.users.store(Arc::new(items_cache.update_load_time())); + } + + // groups + if let Some(item_name_list) = listed_config_items.get(GROUPS_LIST_KEY) { + let mut items_cache = CacheEntity::default(); + + for item in item_name_list.iter() { + let name = ecstore::utils::path::dir(item); + info!("load group: {}", name); + if let Err(err) = self.load_group(&name, &mut items_cache).await { + return Err(Error::msg(std::format!("load group failed: {}", err))); + }; + } + + cache.groups.store(Arc::new(items_cache.update_load_time())); + } + + // user policies + if let Some(item_name_list) = listed_config_items.get(POLICY_DB_USERS_LIST_KEY) { + let mut item_name_list = item_name_list.clone(); + + let mut items_cache = CacheEntity::default(); + + loop { + if item_name_list.len() < 32 { + let items = self + .load_mapped_policy_concurrent(&item_name_list, UserType::Reg, false) + .await?; + + for (idx, p) in items.into_iter().enumerate() { + if p.policies.is_empty() { + continue; + } + + let name = item_name_list[idx].trim_end_matches(".json").to_owned(); + info!("load user policy: {}", name); + items_cache.insert(name, p); + } + break; + } + + let items = self + .load_mapped_policy_concurrent(&item_name_list, UserType::Reg, false) + .await?; + + for (idx, p) in items.into_iter().enumerate() { + if p.policies.is_empty() { + continue; + } + + let name = item_name_list[idx].trim_end_matches(".json").to_owned(); + info!("load user policy: {}", name); + items_cache.insert(name, p); + } + + item_name_list = item_name_list.split_off(32); + } + + cache.user_policies.store(Arc::new(items_cache.update_load_time())); + } + + // group policy + if let Some(item_name_list) = listed_config_items.get(SVC_ACC_LIST_KEY) { + let mut items_cache = CacheEntity::default(); + + for item in item_name_list.iter() { + let name = item.trim_end_matches(".json"); + + info!("load group policy: {}", name); + if let Err(err) = self.load_mapped_policy(name, UserType::Reg, true, &mut items_cache).await { + return Err(Error::msg(std::format!("load group failed: {}", err))); + }; + } + + cache.group_policies.store(Arc::new(items_cache.update_load_time())); + } + + let mut sts_policies_cache = CacheEntity::default(); + + // svc users + if let Some(item_name_list) = listed_config_items.get(POLICY_DB_GROUPS_LIST_KEY) { + let mut items_cache = HashMap::default(); + + for item in item_name_list.iter() { + let name = ecstore::utils::path::dir(item); + info!("load svc user: {}", name); + if let Err(err) = self.load_user(&name, UserType::Svc, &mut items_cache).await { + return Err(Error::msg(std::format!("load_user failed: {}", err))); + }; + } + + for (_, v) in items_cache.iter() { + let parent = v.credentials.parent_user.clone(); + if !user_items_cache.contains_key(&parent) { + info!("load sts user policy: {}", parent); + if let Err(err) = self + .load_mapped_policy(&parent, UserType::Sts, false, &mut sts_policies_cache) + .await + { + if !is_err_no_such_policy(&err) { + return Err(Error::msg(std::format!("load_mapped_policy failed: {}", err))); + } + } + } + } + + // 合并 items_cache 到 user_items_cache + user_items_cache.extend(items_cache); + + // cache.users.store(Arc::new(items_cache.update_load_time())); + } + + cache.build_user_group_memberships(); + let mut sts_items_cache = CacheEntity::default(); + // sts users + if let Some(item_name_list) = listed_config_items.get(STS_LIST_KEY) { + for item in item_name_list.iter() { + let name = ecstore::utils::path::dir(item); + info!("load sts user: {}", name); + if let Err(err) = self.load_user(&name, UserType::Sts, &mut sts_items_cache).await { + return Err(Error::msg(std::format!("load user failed: {}", err))); + }; } } - if !all_futures.is_empty() { - try_join_all(all_futures).await?; + // sts user policy + if let Some(item_name_list) = listed_config_items.get(POLICY_DB_STS_USERS_LIST_KEY) { + for item in item_name_list.iter() { + let name = item.trim_end_matches(".json"); + info!("load sts user policy: {}", name); + if let Err(err) = self + .load_mapped_policy(name, UserType::Sts, false, &mut sts_policies_cache) + .await + { + return Err(Error::msg(std::format!("load user failed: {}", err))); + }; + } } - if let Some(x) = Arc::into_inner(users) { - cache.users.store(Arc::new(x.into_inner().update_load_time())) - } - - if let Some(x) = Arc::into_inner(policy_docs) { - cache.policy_docs.store(Arc::new(x.into_inner().update_load_time())) - } - if let Some(x) = Arc::into_inner(user_policies) { - cache.user_policies.store(Arc::new(x.into_inner().update_load_time())) - } - if let Some(x) = Arc::into_inner(sts_policies) { - cache.sts_policies.store(Arc::new(x.into_inner().update_load_time())) - } - if let Some(x) = Arc::into_inner(sts_accounts) { - cache.sts_accounts.store(Arc::new(x.into_inner().update_load_time())) - } + cache.users.store(Arc::new(user_items_cache.update_load_time())); + cache.sts_accounts.store(Arc::new(sts_items_cache.update_load_time())); + cache.sts_policies.store(Arc::new(sts_policies_cache.update_load_time())); Ok(()) } - async fn load_mapped_policy(&self, user_type: UserType, name: &str, _is_group: bool) -> crate::Result { - let (p, _) = self - .load_iam_config::(&format!("{base}{name}.json", base = user_type.prefix(), name = name)) - .await?; - Ok(p) - } + // /// load all and make a new cache. + // async fn load_all(&self, cache: &Cache) -> Result<()> { + // let _items = &[ + // "policydb/", + // "policies/", + // "groups/", + // "policydb/users/", + // "policydb/groups/", + // "service-accounts/", + // "policydb/sts-users/", + // "sts/", + // ]; + + // let items = self.list_iam_config_items("config/iam/").await?; + // debug!("all iam items: {items:?}"); + + // let (policy_docs, users, user_policies, sts_policies, sts_accounts) = ( + // Arc::new(tokio::sync::Mutex::new(CacheEntity::new(Self::get_default_policyes()))), + // Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), + // Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), + // Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), + // Arc::new(tokio::sync::Mutex::new(CacheEntity::default())), + // ); + + // // 一次读取32个元素 + // let iter = items + // .iter() + // .map(|item| item.trim_start_matches("config/iam/")) + // .map(|item| split_path(item, item.starts_with("policydb/"))) + // .filter_map(|(list_key, trimmed_item)| { + // debug!("list_key: {list_key}, trimmed_item: {trimmed_item}"); + + // if list_key == "format.json" { + // return None; + // } + + // let (policy_docs, users, user_policies, sts_policies, sts_accounts) = ( + // policy_docs.clone(), + // users.clone(), + // user_policies.clone(), + // sts_policies.clone(), + // sts_accounts.clone(), + // ); + + // Some(async move { + // match list_key { + // "policies/" => { + // let trimmed_item = dir(trimmed_item); + // let name = trimmed_item.trim_end_matches('/'); + // let policy_doc = self.load_policy(name).await?; + // policy_docs.lock().await.insert(name.to_owned(), policy_doc); + // } + // "users/" => { + // let name = dir(trimmed_item); + // if let Some(user) = self.load_user_identity(UserType::Reg, &name).await? { + // users.lock().await.insert(name.to_owned(), user); + // }; + // } + // "groups/" => {} + // "policydb/users/" | "policydb/groups/" => { + // let name = trimmed_item.strip_suffix(".json").unwrap_or(trimmed_item); + // let mapped_policy = self + // .load_mapped_policy(UserType::Reg, name, list_key == "policydb/groups/") + // .await?; + // if !mapped_policy.policies.is_empty() { + // user_policies.lock().await.insert(name.to_owned(), mapped_policy); + // } + // } + // "service-accounts/" => { + // let trimmed_item = dir(trimmed_item); + // let name = trimmed_item.trim_end_matches('/'); + // let Some(user) = self.load_user_identity(UserType::Svc, name).await? else { + // return Ok(()); + // }; + + // let parent = user.credentials.parent_user.clone(); + + // { + // users.lock().await.insert(name.to_owned(), user); + // } + + // if users.lock().await.get(&parent).is_some() { + // return Ok(()); + // } + + // match self.load_mapped_policy(UserType::Sts, parent.as_str(), false).await { + // Ok(m) => sts_policies.lock().await.insert(name.to_owned(), m), + // Err(Error::EcstoreError(e)) if is_err_config_not_found(&e) => return Ok(()), + // Err(e) => return Err(e), + // }; + // } + // "sts/" => { + // let name = dir(trimmed_item); + // if let Some(user) = self.load_user_identity(UserType::Sts, &name).await? { + // warn!("sts_accounts insert {}, user {:?}", name, &user.credentials.access_key); + // sts_accounts.lock().await.insert(name.to_owned(), user); + // }; + // } + // "policydb/sts-users/" => { + // let name = trimmed_item.strip_suffix(".json").unwrap_or(trimmed_item); + // let mapped_policy = self.load_mapped_policy(UserType::Sts, name, false).await?; + // if !mapped_policy.policies.is_empty() { + // sts_policies.lock().await.insert(name.to_owned(), mapped_policy); + // } + // } + // _ => {} + // } + + // Result::Ok(()) + // }) + // }); + + // let mut all_futures = Vec::with_capacity(32); + + // for f in iter { + // all_futures.push(f); + + // if all_futures.len() == 32 { + // try_join_all(all_futures).await?; + // all_futures = Vec::with_capacity(32); + // } + // } + + // if !all_futures.is_empty() { + // try_join_all(all_futures).await?; + // } + + // if let Some(x) = Arc::into_inner(users) { + // cache.users.store(Arc::new(x.into_inner().update_load_time())) + // } + + // if let Some(x) = Arc::into_inner(policy_docs) { + // cache.policy_docs.store(Arc::new(x.into_inner().update_load_time())) + // } + // if let Some(x) = Arc::into_inner(user_policies) { + // cache.user_policies.store(Arc::new(x.into_inner().update_load_time())) + // } + // if let Some(x) = Arc::into_inner(sts_policies) { + // cache.sts_policies.store(Arc::new(x.into_inner().update_load_time())) + // } + // if let Some(x) = Arc::into_inner(sts_accounts) { + // cache.sts_accounts.store(Arc::new(x.into_inner().update_load_time())) + // } + + // Ok(()) + // } } diff --git a/iam/src/sys.rs b/iam/src/sys.rs new file mode 100644 index 00000000..0babd8f4 --- /dev/null +++ b/iam/src/sys.rs @@ -0,0 +1,754 @@ +use std::collections::HashMap; +use std::collections::HashSet; +use std::sync::Arc; + +use crate::arn::ARN; +use crate::auth::contains_reserved_chars; +use crate::auth::create_new_credentials_with_metadata; +use crate::auth::generate_credentials; +use crate::auth::is_access_key_valid; +use crate::auth::is_secret_key_valid; +use crate::auth::Credentials; +use crate::auth::UserIdentity; +use crate::auth::ACCOUNT_ON; +use crate::error::is_err_no_such_account; +use crate::error::is_err_no_such_temp_account; +use crate::error::Error as IamError; +use crate::get_global_action_cred; +use crate::manager::extract_jwt_claims; +use crate::manager::get_default_policyes; +use crate::manager::IamCache; +use crate::policy::action::Action; +use crate::policy::Policy; +use crate::policy::PolicyDoc; +use crate::store::MappedPolicy; +use crate::store::Store; +use crate::store::UserType; +use ecstore::error::{Error, Result}; +use ecstore::utils::crypto::base64_decode; +use ecstore::utils::crypto::base64_encode; +use madmin::GroupDesc; +use serde_json::json; +use serde_json::Value; +use time::OffsetDateTime; + +pub const MAX_SVCSESSION_POLICY_SIZE: usize = 4096; + +pub const STATUS_ENABLED: &str = "enabled"; +pub const STATUS_DISABLED: &str = "disabled"; + +pub const POLICYNAME: &str = "policy"; +pub const SESSION_POLICY_NAME: &str = "sessionPolicy"; +pub const SESSION_POLICY_NAME_EXTRACTED: &str = "sessionPolicy-extracted"; + +pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy"; +pub const INHERITED_POLICY_TYPE: &str = "inherited-policy"; + +pub struct IamSys { + store: Arc>, + roles_map: HashMap, +} + +impl IamSys { + pub fn new(store: Arc>) -> Self { + Self { + store, + roles_map: HashMap::new(), + } + } + + pub async fn load_group(&self, name: &str) -> Result<()> { + self.store.group_notification_handler(name).await + } + + pub async fn load_policy(&self, name: &str) -> Result<()> { + self.store.policy_notification_handler(name).await + } + + pub async fn load_policy_mapping(&self, name: &str, user_type: UserType, is_group: bool) -> Result<()> { + self.store + .policy_mapping_notification_handler(name, user_type, is_group) + .await + } + + pub async fn load_user(&self, name: &str, user_type: UserType) -> Result<()> { + self.store.user_notification_handler(name, user_type).await + } + + pub async fn load_service_account(&self, name: &str) -> Result<()> { + self.store.user_notification_handler(name, UserType::Svc).await + } + + pub async fn delete_policy(&self, name: &str, notify: bool) -> Result<()> { + for k in get_default_policyes().keys() { + if k == name { + return Err(Error::msg("system policy can not be deleted")); + } + } + + self.store.delete_policy(name, notify).await?; + + if notify { + // TODO: implement notification + } + + Ok(()) + } + + pub async fn info_policy(&self, name: &str) -> Result { + let d = self.store.get_policy_doc(name).await?; + + let pdata = serde_json::to_string(&d.policy)?; + + Ok(madmin::PolicyInfo { + policy_name: name.to_string(), + policy: json!(pdata), + create_date: d.create_date, + update_date: d.update_date, + }) + } + + pub async fn list_polices(&self, bucket_name: &str) -> Result> { + self.store.list_polices(bucket_name).await + } + + pub async fn list_policy_docs(&self, bucket_name: &str) -> Result> { + self.store.list_policy_docs(bucket_name).await + } + + pub async fn set_policy(&self, name: &str, policy: Policy) -> Result { + self.store.set_policy(name, policy).await + + // TODO: notification + } + + pub async fn delete_user(&self, name: &str, _notify: bool) -> Result<()> { + self.store.delete_user(name, UserType::Reg).await + // TODO: notification + } + + pub async fn current_policies(&self, name: &str) -> String { + self.store.merge_policies(name).await.0 + } + + pub async fn list_bucket_users(&self, bucket_name: &str) -> Result> { + self.store.get_bucket_users(bucket_name).await + } + + pub async fn list_users(&self) -> Result> { + self.store.get_users().await + } + + pub async fn set_temp_user(&self, name: &str, cred: &Credentials, policy_name: Option<&str>) -> Result { + self.store.set_temp_user(name, cred, policy_name).await + // TODO: notification + } + + pub async fn is_temp_user(&self, name: &str) -> Result<(bool, String)> { + let Some(u) = self.store.get_user(name).await else { + return Err(IamError::NoSuchUser(name.to_string()).into()); + }; + if u.credentials.is_temp() { + Ok((true, u.credentials.parent_user)) + } else { + Ok((false, "".to_string())) + } + } + pub async fn is_service_account(&self, name: &str) -> Result<(bool, String)> { + let Some(u) = self.store.get_user(name).await else { + return Err(IamError::NoSuchUser(name.to_string()).into()); + }; + + if u.credentials.is_service_account() { + Ok((true, u.credentials.parent_user)) + } else { + Ok((false, "".to_string())) + } + } + + pub async fn get_user_info(&self, name: &str) -> Result { + self.store.get_user_info(name).await + } + + pub async fn set_user_status(&self, name: &str, status: madmin::AccountStatus) -> Result { + self.store.set_user_status(name, status).await + // TODO: notification + } + + pub async fn new_service_account( + &self, + parent_user: &str, + groups: Vec, + opts: NewServiceAccountOpts, + ) -> Result { + if parent_user.is_empty() { + return Err(IamError::InvalidArgument.into()); + } + if !opts.access_key.is_empty() && opts.secret_key.is_empty() { + return Err(IamError::NoSecretKeyWithAccessKey.into()); + } + + if !opts.secret_key.is_empty() && opts.access_key.is_empty() { + return Err(IamError::NoAccessKeyWithSecretKey.into()); + } + + if parent_user == opts.access_key { + return Err(IamError::IAMActionNotAllowed.into()); + } + + // TODO: check allow_site_replicator_account + + let policy_buf = if let Some(policy) = opts.session_policy { + policy.validate()?; + let buf = serde_json::to_vec(&policy)?; + if buf.len() > MAX_SVCSESSION_POLICY_SIZE { + return Err(IamError::PolicyTooLarge.into()); + } + + buf + } else { + Vec::new() + }; + + let mut m = HashMap::new(); + m.insert("parent".to_owned(), 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()); + } else { + m.insert(iam_policy_claim_name_sa(), INHERITED_POLICY_TYPE.to_owned()); + } + + if let Some(claims) = opts.claims { + for (k, v) in claims.iter() { + if !m.contains_key(k) { + m.insert(k.to_owned(), v.to_owned()); + } + } + } + + let (access_key, secret_key) = if !opts.access_key.is_empty() || !opts.secret_key.is_empty() { + (opts.access_key, opts.secret_key) + } else { + generate_credentials()? + }; + + let mut cred = create_new_credentials_with_metadata(&access_key, &secret_key, &m, &secret_key, None)?; + cred.parent_user = parent_user.to_owned(); + cred.groups = Some(groups); + cred.status = ACCOUNT_ON.to_owned(); + cred.name = opts.name; + cred.description = opts.description; + cred.expiration = opts.expiration; + + self.store.add_service_account(cred).await + + // TODO: notification + } + + pub async fn update_service_account(&self, name: &str, opts: UpdateServiceAccountOpts) -> Result { + self.store.update_service_account(name, opts).await + + // TODO: notification + } + + pub async fn list_service_accounts(&self, access_key: &str) -> Result> { + self.store.list_service_accounts(access_key).await + } + + pub async fn list_tmep_accounts(&self, access_key: &str) -> Result> { + self.store.list_temp_accounts(access_key).await + } + + pub async fn list_sts_accounts(&self, access_key: &str) -> Result> { + self.store.list_sts_accounts(access_key).await + } + + pub async fn get_service_account(&self, access_key: &str) -> Result<(Credentials, Option)> { + let (mut da, policy) = self.get_service_account_internal(access_key).await?; + + da.credentials.secret_key.clear(); + da.credentials.session_token.clear(); + + Ok((da.credentials, policy)) + } + + async fn get_service_account_internal(&self, access_key: &str) -> Result<(UserIdentity, Option)> { + let (sa, claims) = match self.get_account_with_claims(access_key).await { + Ok(res) => res, + Err(err) => { + if is_err_no_such_account(&err) { + return Err(IamError::NoSuchServiceAccount(access_key.to_string()).into()); + } + + return Err(err); + } + }; + + if !sa.credentials.is_service_account() { + return Err(IamError::NoSuchServiceAccount(access_key.to_string()).into()); + } + + let op_pt = claims.get(&iam_policy_claim_name_sa()); + let op_sp = claims.get(SESSION_POLICY_NAME); + if let (Some(pt), Some(sp)) = (op_pt, op_sp) { + if pt == EMBEDDED_POLICY_TYPE { + let policy = serde_json::from_slice(&base64_decode(sp.as_str().unwrap_or_default().as_bytes())?)?; + return Ok((sa, Some(policy))); + } + } + + Ok((sa, None)) + } + + async fn get_account_with_claims(&self, access_key: &str) -> Result<(UserIdentity, HashMap)> { + let Some(acc) = self.store.get_user(access_key).await else { + return Err(IamError::NoSuchAccount(access_key.to_string()).into()); + }; + + let m = extract_jwt_claims(&acc)?; + + Ok((acc, m)) + } + + pub async fn get_temporary_account(&self, access_key: &str) -> Result<(Credentials, Option)> { + let (mut sa, policy) = match self.get_temp_account(access_key).await { + Ok(res) => res, + Err(err) => { + if is_err_no_such_temp_account(&err) { + // TODO: load_user + match self.get_temp_account(access_key).await { + Ok(res) => res, + Err(err) => return Err(err), + }; + } + + return Err(err); + } + }; + sa.credentials.secret_key.clear(); + sa.credentials.session_token.clear(); + + Ok((sa.credentials, policy)) + } + + async fn get_temp_account(&self, access_key: &str) -> Result<(UserIdentity, Option)> { + let (sa, claims) = match self.get_account_with_claims(access_key).await { + Ok(res) => res, + Err(err) => { + if is_err_no_such_account(&err) { + return Err(IamError::NoSuchTempAccount(access_key.to_string()).into()); + } + + return Err(err); + } + }; + + if !sa.credentials.is_temp() { + return Err(IamError::NoSuchTempAccount(access_key.to_string()).into()); + } + + let op_pt = claims.get(&iam_policy_claim_name_sa()); + let op_sp = claims.get(SESSION_POLICY_NAME); + if let (Some(pt), Some(sp)) = (op_pt, op_sp) { + if pt == EMBEDDED_POLICY_TYPE { + let policy = serde_json::from_slice(&base64_decode(sp.as_str().unwrap_or_default().as_bytes())?)?; + return Ok((sa, Some(policy))); + } + } + + Ok((sa, None)) + } + + pub async fn get_claims_for_svc_acc(&self, access_key: &str) -> Result> { + let Some(u) = self.store.get_user(access_key).await else { + return Err(IamError::NoSuchServiceAccount(access_key.to_string()).into()); + }; + + if u.credentials.is_service_account() { + return Err(IamError::NoSuchServiceAccount(access_key.to_string()).into()); + } + + extract_jwt_claims(&u) + } + + pub async fn delete_service_account(&self, access_key: &str) -> Result<()> { + let Some(u) = self.store.get_user(access_key).await else { + return Ok(()); + }; + + if u.credentials.is_service_account() { + return Ok(()); + } + + self.store.delete_user(access_key, UserType::Svc).await + + // TODO: notification + } + + pub async fn create_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result { + if !is_access_key_valid(access_key) { + return Err(IamError::InvalidAccessKeyLength.into()); + } + + if contains_reserved_chars(access_key) { + return Err(IamError::ContainsReservedChars.into()); + } + + if !is_secret_key_valid(secret_key) { + return Err(IamError::InvalidSecretKeyLength.into()); + } + + self.store.add_user(access_key, secret_key, status).await + // TODO: notification + } + + pub async fn set_user_secret_key(&self, access_key: &str, secret_key: &str) -> Result<()> { + if !is_access_key_valid(access_key) { + return Err(IamError::InvalidAccessKeyLength.into()); + } + + if !is_secret_key_valid(secret_key) { + return Err(IamError::InvalidSecretKeyLength.into()); + } + + self.store.update_user_secret_key(access_key, secret_key).await + } + + pub async fn check_key(&self, access_key: &str) -> Result<(Option, bool)> { + if let Some(sys_cred) = get_global_action_cred() { + if sys_cred.access_key == access_key { + return Ok((Some(UserIdentity::new(sys_cred)), true)); + } + } + + match self.store.get_user(access_key).await { + Some(res) => { + let ok = res.credentials.is_valid(); + + Ok((Some(res), ok)) + } + None => Ok((None, false)), + } + } + + pub async fn get_user(&self, access_key: &str) -> Option { + match self.check_key(access_key).await { + Ok((u, _)) => u, + _ => None, + } + } + + pub async fn add_users_to_group(&self, group: &str, users: Vec) -> Result { + if contains_reserved_chars(group) { + return Err(IamError::GroupNameContainsReservedChars.into()); + } + self.store.add_users_to_group(group, users).await + // TODO: notification + } + + pub async fn remove_users_from_group(&self, group: &str, users: Vec) -> Result { + self.store.remove_users_from_group(group, users).await + // TODO: notification + } + + pub async fn set_group_status(&self, group: &str, enable: bool) -> Result { + self.store.set_group_status(group, enable).await + // TODO: notification + } + pub async fn get_group_description(&self, group: &str) -> Result { + self.store.get_group_description(group).await + } + + pub async fn list_groups(&self) -> Result> { + self.store.list_groups().await + } + + pub async fn policy_db_set(&self, name: &str, user_type: UserType, is_group: bool, policy: &str) -> Result { + self.store.policy_db_set(name, user_type, is_group, policy).await + // TODO: notification + } + + pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result> { + self.store.policy_db_get(name, groups).await + } + + pub async fn is_allowed_sts(&self, args: &Args<'_>, parent_user: &str) -> bool { + let is_owner = parent_user == get_global_action_cred().unwrap().access_key; + let role_arn = args.get_role_arn(); + let policies = { + if is_owner { + Vec::new() + } else if role_arn.is_some() { + let Ok(arn) = ARN::parse(role_arn.unwrap_or_default()) else { return false }; + + MappedPolicy::new(self.roles_map.get(&arn).map_or_else(String::default, |v| v.clone()).as_str()).to_slice() + } else { + let Ok(p) = self.policy_db_get(parent_user, args.groups).await else { return false }; + + p + //TODO: FROM JWT + } + }; + + if policies.is_empty() { + return false; + } + + let combined_policy = { + if is_owner { + Policy::default() + } else { + let (a, c) = self.store.merge_policies(&policies.join(",")).await; + if a.is_empty() { + return false; + } + c + } + }; + + let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy(args); + if has_session_policy { + return is_allowed_sp && (is_owner || combined_policy.is_allowed(args)); + } + + is_owner || combined_policy.is_allowed(args) + } + + pub async fn is_allowed_service_account(&self, args: &Args<'_>, parent_user: &str) -> bool { + let Some(p) = args.claims.get("parent") else { + return false; + }; + + if p.as_str() != Some(parent_user) { + return false; + } + + let is_owner = parent_user == get_global_action_cred().unwrap().access_key; + + let role_arn = args.get_role_arn(); + + let svc_policies = { + if is_owner { + Vec::new() + } else if role_arn.is_some() { + let Ok(arn) = ARN::parse(role_arn.unwrap_or_default()) else { return false }; + MappedPolicy::new(self.roles_map.get(&arn).map_or_else(String::default, |v| v.clone()).as_str()).to_slice() + } else { + let Ok(p) = self.policy_db_get(parent_user, args.groups).await else { return false }; + p + } + }; + + if !is_owner && svc_policies.is_empty() { + return false; + } + + let combined_policy = { + if is_owner { + Policy::default() + } else { + let (a, c) = self.store.merge_policies(&svc_policies.join(",")).await; + if a.is_empty() { + return false; + } + c + } + }; + + let mut parent_args = args.clone(); + parent_args.account = parent_user; + + let Some(sa) = args.claims.get(&iam_policy_claim_name_sa()) else { + return false; + }; + + let Some(sa_str) = sa.as_str() else { + return false; + }; + + if sa_str == INHERITED_POLICY_TYPE { + return is_owner || combined_policy.is_allowed(&parent_args); + } + + let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy_for_service_account(args); + if has_session_policy { + return is_allowed_sp && (is_owner || combined_policy.is_allowed(&parent_args)); + } + + is_owner || combined_policy.is_allowed(&parent_args) + } + + async fn get_combined_policy(&self, policies: &[String]) -> Policy { + self.store.merge_policies(&policies.join(",")).await.1 + } + + pub async fn is_allowed(&self, args: &Args<'_>) -> bool { + if args.is_owner { + return true; + } + + let Ok((is_temp, parent_user)) = self.is_temp_user(args.account).await else { return false }; + + if is_temp { + return self.is_allowed_sts(args, &parent_user).await; + } + + let Ok((is_svc, parent_user)) = self.is_service_account(args.account).await else { return false }; + + if is_svc { + return self.is_allowed_service_account(args, &parent_user).await; + } + + let Ok(policies) = self.policy_db_get(args.account, args.groups).await else { return false }; + + if policies.is_empty() { + return false; + } + + self.get_combined_policy(&policies).await.is_allowed(args) + } +} + +fn is_allowed_by_session_policy(args: &Args<'_>) -> (bool, bool) { + let Some(spolicy) = args.claims.get(SESSION_POLICY_NAME_EXTRACTED) else { + return (false, false); + }; + + let has_session_policy = true; + + let Some(spolicy_str) = spolicy.as_str() else { + return (has_session_policy, false); + }; + + let Ok(sub_policy) = Policy::parse_config(spolicy_str.as_bytes()) else { + return (has_session_policy, false); + }; + + if sub_policy.version.is_empty() { + return (has_session_policy, false); + } + + let mut session_policy_args = args.clone(); + session_policy_args.is_owner = false; + + (has_session_policy, sub_policy.is_allowed(&session_policy_args)) +} + +fn is_allowed_by_session_policy_for_service_account(args: &Args<'_>) -> (bool, bool) { + let Some(spolicy) = args.claims.get(SESSION_POLICY_NAME_EXTRACTED) else { + return (false, false); + }; + + let mut has_session_policy = true; + + let Some(spolicy_str) = spolicy.as_str() else { + return (has_session_policy, false); + }; + + let Ok(sub_policy) = Policy::parse_config(spolicy_str.as_bytes()) else { + return (has_session_policy, false); + }; + + if sub_policy.version.is_empty() && sub_policy.statements.is_empty() && sub_policy.id.is_empty() { + has_session_policy = false; + return (has_session_policy, false); + } + + let mut session_policy_args = args.clone(); + session_policy_args.is_owner = false; + + (has_session_policy, sub_policy.is_allowed(&session_policy_args)) +} + +#[derive(Debug, Clone, Default)] +pub struct NewServiceAccountOpts { + pub session_policy: Option, + pub access_key: String, + pub secret_key: String, + pub name: Option, + pub description: Option, + pub expiration: Option, + pub allow_site_replicator_account: bool, + pub claims: Option>, +} + +pub struct UpdateServiceAccountOpts { + pub session_policy: Option, + pub secret_key: String, + pub name: Option, + pub description: Option, + pub expiration: Option, + pub status: Option, +} + +pub fn iam_policy_claim_name_sa() -> String { + "sa-policy".to_string() +} + +/// DEFAULT_VERSION is the default version. +/// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html +pub const DEFAULT_VERSION: &str = "2012-10-17"; + +/// check the data is Validator +pub trait Validator { + type Error; + fn is_valid(&self) -> Result<()> { + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Args<'a> { + pub account: &'a str, + pub groups: &'a [String], + pub action: Action, + pub bucket: &'a str, + pub conditions: &'a HashMap>, + pub is_owner: bool, + pub object: &'a str, + pub claims: &'a HashMap, + pub deny_only: bool, +} + +impl Args<'_> { + pub fn get_role_arn(&self) -> Option<&str> { + self.claims.get("roleArn").and_then(|x| x.as_str()) + } + pub fn get_policies(&self, policy_claim_name: &str) -> (HashSet, bool) { + get_policies_from_claims(self.claims, policy_claim_name) + } +} + +fn get_values_from_claims(claims: &HashMap, claim_name: &str) -> (HashSet, bool) { + let mut s = HashSet::new(); + if let Some(pname) = claims.get(claim_name) { + if let Some(pnames) = pname.as_array() { + for pname in pnames { + if let Some(pname_str) = pname.as_str() { + for pname in pname_str.split(',') { + let pname = pname.trim(); + if !pname.is_empty() { + s.insert(pname.to_string()); + } + } + } + } + return (s, true); + } else if let Some(pname_str) = pname.as_str() { + for pname in pname_str.split(',') { + let pname = pname.trim(); + if !pname.is_empty() { + s.insert(pname.to_string()); + } + } + return (s, true); + } + } + (s, false) +} + +fn get_policies_from_claims(claims: &HashMap, policy_claim_name: &str) -> (HashSet, bool) { + get_values_from_claims(claims, policy_claim_name) +} diff --git a/iam/src/utils.rs b/iam/src/utils.rs index ab8f4538..00448fdd 100644 --- a/iam/src/utils.rs +++ b/iam/src/utils.rs @@ -1,16 +1,16 @@ -use crate::Error; +use ecstore::error::{Error, Result}; use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header}; use rand::{Rng, RngCore}; use serde::{de::DeserializeOwned, Serialize}; -pub fn gen_access_key(length: usize) -> crate::Result { +pub fn gen_access_key(length: usize) -> Result { const ALPHA_NUMERIC_TABLE: [char; 36] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ]; if length < 3 { - return Err(Error::StringError("access key length is too short".into())); + return Err(Error::msg("access key length is too short")); } let mut result = String::with_capacity(length); @@ -27,7 +27,7 @@ pub fn gen_secret_key(length: usize) -> crate::Result { use base64_simd::URL_SAFE_NO_PAD; if length < 8 { - return Err(Error::StringError("secret key length is too short".into())); + return Err(Error::msg("secret key length is too short")); } let mut rng = rand::thread_rng(); diff --git a/iam/tests/policy_is_allowed.rs b/iam/tests/policy_is_allowed.rs index 84144caf..8d8c600d 100644 --- a/iam/tests/policy_is_allowed.rs +++ b/iam/tests/policy_is_allowed.rs @@ -3,7 +3,9 @@ use iam::policy::action::ActionSet; use iam::policy::action::S3Action::*; use iam::policy::resource::ResourceSet; use iam::policy::Effect::*; -use iam::policy::{Args, Policy, Statement, DEFAULT_VERSION}; +use iam::policy::{Policy, Statement}; +use iam::sys::Args; +use iam::sys::DEFAULT_VERSION; use serde_json::Value; use std::collections::HashMap; use test_case::test_case; @@ -44,7 +46,7 @@ struct ArgsBuilder { )] #[test_case( Policy{ - version: DEFAULT_VERSION.into(), + version: iam::sys::DEFAULT_VERSION.into(), statements: vec![ Statement{ effect: Allow, @@ -579,7 +581,7 @@ struct ArgsBuilder { )] #[test_case( Policy{ - version: DEFAULT_VERSION.into(), + version: iam::sys::DEFAULT_VERSION.into(), statements: vec![ Statement{ effect: Deny, diff --git a/madmin/Cargo.toml b/madmin/Cargo.toml index 122ac738..8977a66a 100644 --- a/madmin/Cargo.toml +++ b/madmin/Cargo.toml @@ -15,5 +15,6 @@ common.workspace = true humantime.workspace = true hyper.workspace = true serde.workspace = true +serde_json.workspace = true time.workspace = true tracing.workspace = true diff --git a/madmin/src/group.rs b/madmin/src/group.rs index 93f66002..bca11571 100644 --- a/madmin/src/group.rs +++ b/madmin/src/group.rs @@ -1,5 +1,6 @@ use serde::Deserialize; use serde::Serialize; +use time::OffsetDateTime; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -17,3 +18,13 @@ pub struct GroupAddRemove { #[serde(rename = "isRemove")] pub is_remove: bool, } + +#[derive(Debug, Serialize, Deserialize)] +pub struct GroupDesc { + pub name: String, + pub status: String, + pub members: Vec, + pub policy: String, + #[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, +} diff --git a/madmin/src/lib.rs b/madmin/src/lib.rs index c2184003..f21f3c76 100644 --- a/madmin/src/lib.rs +++ b/madmin/src/lib.rs @@ -4,6 +4,7 @@ pub mod health; pub mod info_commands; pub mod metrics; pub mod net; +pub mod policy; pub mod service_commands; pub mod trace; pub mod user; @@ -11,4 +12,5 @@ pub mod utils; pub use group::*; pub use info_commands::*; +pub use policy::*; pub use user::*; diff --git a/madmin/src/policy.rs b/madmin/src/policy.rs new file mode 100644 index 00000000..512a6ecb --- /dev/null +++ b/madmin/src/policy.rs @@ -0,0 +1,14 @@ +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; +use time::OffsetDateTime; + +#[derive(Debug, Serialize, Deserialize)] +pub struct PolicyInfo { + pub policy_name: String, + pub policy: Value, + #[serde(skip_serializing_if = "Option::is_none")] + pub create_date: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub update_date: Option, +} diff --git a/madmin/src/service_commands.rs b/madmin/src/service_commands.rs index 9db8b57d..1b65a5a7 100644 --- a/madmin/src/service_commands.rs +++ b/madmin/src/service_commands.rs @@ -5,6 +5,7 @@ use hyper::Uri; use crate::{trace::TraceType, utils::parse_duration}; #[derive(Debug, Default)] +#[allow(dead_code)] pub struct ServiceTraceOpts { s3: bool, internal: bool, @@ -26,6 +27,7 @@ pub struct ServiceTraceOpts { threshold: Duration, } +#[allow(dead_code)] impl ServiceTraceOpts { fn trace_types(&self) -> TraceType { let mut tt = TraceType::default(); diff --git a/madmin/src/user.rs b/madmin/src/user.rs index 4bc26dd0..9f47227a 100644 --- a/madmin/src/user.rs +++ b/madmin/src/user.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum AccountStatus { #[serde(rename = "enabled")] Enabled, diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index bcba70e8..762736f2 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -23,6 +23,7 @@ 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::error::Error as IamError; use iam::{auth, get_global_action_cred}; use madmin::metrics::RealtimeMetrics; use madmin::utils::parse_duration; @@ -120,91 +121,116 @@ 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)> { let Some(mut cred) = get_global_action_cred() else { - return Err(s3_error!(InternalError, "action cred not init")); + return Err(S3Error::with_message( + S3ErrorCode::InternalError, + format!("get_global_action_cred {:?}", IamError::IamSysNotInitialized), + )); }; let sys_cred = cred.clone(); if cred.access_key != ak { - let Ok(iam_store) = iam::get() else { return Err(s3_error!(InternalError, "iam not init")) }; + let Ok(iam_store) = iam::get() else { + return Err(S3Error::with_message( + S3ErrorCode::InternalError, + format!("check_key_valid {:?}", IamError::IamSysNotInitialized), + )); + }; - match iam_store + let (u, ok) = iam_store .check_key(ak) .await - .map_err(|_e| s3_error!(InternalError, "check key failed"))? - { - (Some(u), true) => { - cred = u.credentials; - } - (Some(u), false) => { - 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")); - } - } - } - - 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()); + + // if !ok { + // if u.credentials.status == "off" { + // return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); + // } + + // 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")); + // } + + // return Err(s3_error!(InvalidRequest, "check key failed")); + // } + // _ => { + // return Err(s3_error!(InvalidRequest, "check key failed")); + // } + // } } - let owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; + unimplemented!() - // permitRootAccess - // SessionPolicyName - Ok((cred, owner)) + // 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()); + // } + + // let owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key; + + // // permitRootAccess + // // SessionPolicyName + // 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")); - } + unimplemented!() + // 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; + // let mut token = token; - if cred.is_service_account() { - token = cred.session_token.as_str(); - secret = cred.secret_key.clone(); - } + // if cred.is_service_account() { + // token = cred.session_token.as_str(); + // secret = cred.secret_key.clone(); + // } - if !token.is_empty() { - let claims: STSClaims = - get_claims_from_token_with_secret(token, &secret).map_err(|_e| s3_error!(InvalidRequest, "invalid token"))?; - return Ok(claims); - } + // 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(STSClaims::default()) + // Ok(STSClaims::default()) } pub fn get_session_token(hds: &HeaderMap) -> Option { @@ -216,88 +242,89 @@ pub struct AssumeRoleHandle {} #[async_trait::async_trait] impl Operation for AssumeRoleHandle { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { - warn!("handle AssumeRoleHandle"); + unimplemented!() + // 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")) }; - // TODO: 判断权限, 不允许sts访问 + // // TODO: 判断权限, 不允许sts访问 - 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")); + // } - warn!("AssumeRole get cred {:?}", &user); - warn!("AssumeRole get body {:?}", &body); + // 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(&user.access_key, None).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, "sk not init")); + // }; - let exp = { - if body.duration_seconds > 0 { - body.duration_seconds - } else { - 3600 - } - }; + // let exp = { + // if body.duration_seconds > 0 { + // body.duration_seconds + // } else { + // 3600 + // } + // }; - let mut claims = STSClaims { - parent: user.access_key.clone(), - exp, - ..Default::default() - }; + // let mut claims = STSClaims { + // parent: user.access_key.clone(), + // exp, + // ..Default::default() + // }; - let ak = iam::utils::gen_access_key(20).unwrap_or_default(); - let sk = iam::utils::gen_secret_key(32).unwrap_or_default(); + // let ak = iam::utils::gen_access_key(20).unwrap_or_default(); + // let sk = iam::utils::gen_secret_key(32).unwrap_or_default(); - claims.access_key = ak.clone(); + // claims.access_key = ak.clone(); - 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, "")), - }; + // 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, "")), + // }; - cred.parent_user = user.access_key.clone(); + // cred.parent_user = user.access_key.clone(); - if let Err(err) = iam_store.set_temp_user(&cred.access_key, &cred, "").await { - error!("set_temp_user err {:?}", err); - return Err(s3_error!(InternalError, "set_temp_user failed")); - } + // 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: 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() - }; + // 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/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index 0d2a3c1d..9b850fb9 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -4,10 +4,7 @@ use http::HeaderMap; use hyper::StatusCode; use iam::{ auth::CredentialsBuilder, - policy::{ - action::{Action, AdminAction::ListServiceAccountsAdminAction}, - Args, - }, + policy::action::{Action, AdminAction::ListServiceAccountsAdminAction}, }; use madmin::{AddServiceAccountReq, ListServiceAccountsResp, ServiceAccountInfo}; use matchit::Params; @@ -27,99 +24,101 @@ impl Operation for AddServiceAccount { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle AddServiceAccount "); - let Some(input_cred) = req.credentials else { - return Err(s3_error!(InvalidRequest, "get cred failed")); - }; - let _is_owner = true; // 先按true处理,后期根据请求决定。 + unimplemented!() - let mut input = req.input; - let body = match input.store_all_unlimited().await { - Ok(b) => b, - Err(e) => { - warn!("get body failed, e: {:?}", e); - return Err(s3_error!(InvalidRequest, "get body failed")); - } - }; + // let Some(input_cred) = req.credentials else { + // return Err(s3_error!(InvalidRequest, "get cred failed")); + // }; + // let _is_owner = true; // 先按true处理,后期根据请求决定。 - let mut create_req: AddServiceAccountReq = - serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?; + // let mut input = req.input; + // let body = match input.store_all_unlimited().await { + // Ok(b) => b, + // Err(e) => { + // warn!("get body failed, e: {:?}", e); + // return Err(s3_error!(InvalidRequest, "get body failed")); + // } + // }; - create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok()); + // let mut create_req: AddServiceAccountReq = + // serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?; - if create_req.access_key.trim().len() != create_req.access_key.len() { - return Err(s3_error!(InvalidRequest, "access key has spaces")); - } + // create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok()); - let (cred, _) = check_key_valid(None, &input_cred.access_key).await.map_err(|e| { - debug!("check key failed: {e:?}"); - s3_error!(InternalError, "check key failed") - })?; - - // TODO check create_req validity - - // 校验合法性, Name, Expiration, Description - let target_user = if let Some(u) = create_req.target_user { - u - } else { - cred.access_key - }; - let _deny_only = true; - - // todo 校验权限 - - // if !iam::is_allowed(Args { - // account: &cred.access_key, - // groups: &[], - // action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction), - // bucket: "", - // conditions: &HashMap::new(), - // is_owner, - // object: "", - // claims: &HashMap::new(), - // deny_only, - // }) - // .await - // .unwrap_or(false) - // { - // return Err(s3_error!(AccessDenied)); + // if create_req.access_key.trim().len() != create_req.access_key.len() { + // return Err(s3_error!(InvalidRequest, "access key has spaces")); // } - // - let cred = CredentialsBuilder::new() - .parent_user(target_user) - .access_key(create_req.access_key) - .secret_key(create_req.secret_key) - .description(create_req.description.unwrap_or_default()) - .expiration(create_req.expiration) - .session_policy({ - match create_req.policy { - Some(p) if !p.is_empty() => { - Some(serde_json::from_slice(p.as_bytes()).map_err(|_| s3_error!(InvalidRequest, "invalid policy"))?) - } - _ => None, - } - }) - .name(create_req.name) - .try_build() - .map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?; + // let (cred, _) = check_key_valid(None, &input_cred.access_key).await.map_err(|e| { + // debug!("check key failed: {e:?}"); + // s3_error!(InternalError, "check key failed") + // })?; - let resp = serde_json::to_vec(&AddServiceAccountResp { - credentials: Credentials { - access_key: &cred.access_key, - secret_key: &cred.secret_key, - session_token: None, - expiration: cred.expiration, - }, - }) - .unwrap() - .into(); + // // TODO check create_req validity - iam::add_service_account(cred).await.map_err(|e| { - debug!("add cred failed: {e:?}"); - s3_error!(InternalError, "add cred failed") - })?; + // // 校验合法性, Name, Expiration, Description + // let target_user = if let Some(u) = create_req.target_user { + // u + // } else { + // cred.access_key + // }; + // let _deny_only = true; - Ok(S3Response::new((StatusCode::OK, resp))) + // // todo 校验权限 + + // // if !iam::is_allowed(Args { + // // account: &cred.access_key, + // // groups: &[], + // // action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction), + // // bucket: "", + // // conditions: &HashMap::new(), + // // is_owner, + // // object: "", + // // claims: &HashMap::new(), + // // deny_only, + // // }) + // // .await + // // .unwrap_or(false) + // // { + // // return Err(s3_error!(AccessDenied)); + // // } + // // + + // let cred = CredentialsBuilder::new() + // .parent_user(target_user) + // .access_key(create_req.access_key) + // .secret_key(create_req.secret_key) + // .description(create_req.description.unwrap_or_default()) + // .expiration(create_req.expiration) + // .session_policy({ + // match create_req.policy { + // Some(p) if !p.is_empty() => { + // Some(serde_json::from_slice(p.as_bytes()).map_err(|_| s3_error!(InvalidRequest, "invalid policy"))?) + // } + // _ => None, + // } + // }) + // .name(create_req.name) + // .try_build() + // .map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?; + + // let resp = serde_json::to_vec(&AddServiceAccountResp { + // credentials: Credentials { + // access_key: &cred.access_key, + // secret_key: &cred.secret_key, + // session_token: None, + // expiration: cred.expiration, + // }, + // }) + // .unwrap() + // .into(); + + // iam::add_service_account(cred).await.map_err(|e| { + // debug!("add cred failed: {e:?}"); + // s3_error!(InternalError, "add cred failed") + // })?; + + // Ok(S3Response::new((StatusCode::OK, resp))) } } @@ -144,82 +143,76 @@ impl Operation for InfoServiceAccount { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle InfoServiceAccount"); - let Some(cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) }; + // let Some(cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) }; - //accessKey - let Some(ak) = req.uri.query().and_then(|x| { - for mut x in x.split('&').map(|x| x.split('=')) { - let Some(key) = x.next() else { - continue; - }; + // //accessKey + // let Some(ak) = req.uri.query().and_then(|x| { + // for mut x in x.split('&').map(|x| x.split('=')) { + // let Some(key) = x.next() else { + // continue; + // }; - if key != "accessKey" { - continue; - } + // if key != "accessKey" { + // continue; + // } - let Some(value) = x.next() else { - continue; - }; + // let Some(value) = x.next() else { + // continue; + // }; - return Some(value); - } + // return Some(value); + // } - None - }) else { - return Err(s3_error!(InvalidRequest, "access key is not exist")); - }; - - let (sa, _sp) = iam::get_service_account(ak).await.map_err(|e| { - debug!("get service account failed, err: {e:?}"); - s3_error!(InternalError) - })?; - - if !iam::is_allowed(Args { - account: &sa.access_key, - groups: &sa.groups.unwrap_or_default()[..], - action: Action::AdminAction(ListServiceAccountsAdminAction), - bucket: "", - conditions: &HashMap::new(), - is_owner: true, - object: "", - claims: &HashMap::new(), - deny_only: false, - }) - .await - .map_err(|_| s3_error!(InternalError))? - { - let req_user = &cred.access_key; - if req_user != &sa.parent_user { - return Err(s3_error!(AccessDenied)); - } - } - - // let implied_policy = sp.version.is_empty() && sp.statements.is_empty(); - // let sva = if implied_policy { - // sp - // } else { - // // 这里使用 - // todo!(); + // None + // }) else { + // return Err(s3_error!(InvalidRequest, "access key is not exist")); // }; - let body = serde_json::to_vec(&InfoServiceAccountResp { - parent_user: sa.parent_user, - account_status: sa.status, - implied_policy: true, - // policy: serde_json::to_string_pretty(&sva).map_err(|_| s3_error!(InternalError, "json marshal failed"))?, - policy: "".into(), - name: sa.name.unwrap_or_default(), - description: sa.description.unwrap_or_default(), - expiration: sa.expiration, - }) - .map_err(|_| s3_error!(InternalError, "json marshal failed"))?; + unimplemented!() - Ok(S3Response::new(( - StatusCode::OK, - crypto::encrypt_data(cred.access_key.as_bytes(), &body[..]) - .map_err(|_| s3_error!(InternalError, "encrypt data failed"))? - .into(), - ))) + // let (sa, _sp) = iam::get_service_account(ak).await.map_err(|e| { + // debug!("get service account failed, err: {e:?}"); + // s3_error!(InternalError) + // })?; + + // if !iam::is_allowed(Args { + // account: &sa.access_key, + // groups: &sa.groups.unwrap_or_default()[..], + // action: Action::AdminAction(ListServiceAccountsAdminAction), + // bucket: "", + // conditions: &HashMap::new(), + // is_owner: true, + // object: "", + // claims: &HashMap::new(), + // deny_only: false, + // }) + // .await + // .map_err(|_| s3_error!(InternalError))? + // { + // let req_user = &cred.access_key; + // if req_user != &sa.parent_user { + // return Err(s3_error!(AccessDenied)); + // } + // } + + // let body = serde_json::to_vec(&InfoServiceAccountResp { + // parent_user: sa.parent_user, + // account_status: sa.status, + // implied_policy: true, + // // policy: serde_json::to_string_pretty(&sva).map_err(|_| s3_error!(InternalError, "json marshal failed"))?, + // policy: "".into(), + // name: sa.name.unwrap_or_default(), + // description: sa.description.unwrap_or_default(), + // expiration: sa.expiration, + // }) + // .map_err(|_| s3_error!(InternalError, "json marshal failed"))?; + + // Ok(S3Response::new(( + // StatusCode::OK, + // crypto::encrypt_data(cred.access_key.as_bytes(), &body[..]) + // .map_err(|_| s3_error!(InternalError, "encrypt data failed"))? + // .into(), + // ))) } } @@ -233,60 +226,61 @@ pub struct ListServiceAccount {} impl Operation for ListServiceAccount { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle ListServiceAccount"); - let query = { - if let Some(query) = req.uri.query() { - let input: ListServiceAccountQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; - input - } else { - ListServiceAccountQuery::default() - } - }; + unimplemented!() + // let query = { + // if let Some(query) = req.uri.query() { + // let input: ListServiceAccountQuery = + // from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; + // input + // } else { + // ListServiceAccountQuery::default() + // } + // }; - let target_account = if let Some(user) = query.user { - user - } else { - let Some(input_cred) = req.credentials else { - return Err(s3_error!(InvalidRequest, "get cred failed")); - }; + // let target_account = if let Some(user) = query.user { + // user + // } else { + // let Some(input_cred) = req.credentials else { + // return Err(s3_error!(InvalidRequest, "get cred failed")); + // }; - let (cred, _owner) = check_key_valid(None, &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(None, &input_cred.access_key).await.map_err(|e| { + // debug!("check key failed: {e:?}"); + // s3_error!(InternalError, "check key failed") + // })?; - if cred.parent_user.is_empty() { - input_cred.access_key - } else { - cred.parent_user - } - }; + // if cred.parent_user.is_empty() { + // input_cred.access_key + // } else { + // cred.parent_user + // } + // }; - let service_accounts = iam::list_service_accounts(&target_account).await.map_err(|e| { - debug!("list service account failed: {e:?}"); - s3_error!(InternalError, "list service account failed") - })?; + // let service_accounts = iam::list_service_accounts(&target_account).await.map_err(|e| { + // debug!("list service account failed: {e:?}"); + // s3_error!(InternalError, "list service account failed") + // })?; - let accounts: Vec = service_accounts - .into_iter() - .map(|sa| ServiceAccountInfo { - parent_user: sa.parent_user, - account_status: sa.status, - implied_policy: true, // or set according to your logic - access_key: sa.access_key, - name: sa.name, - description: sa.description, - expiration: sa.expiration, - }) - .collect(); + // let accounts: Vec = service_accounts + // .into_iter() + // .map(|sa| ServiceAccountInfo { + // parent_user: sa.parent_user, + // account_status: sa.status, + // implied_policy: true, // or set according to your logic + // access_key: sa.access_key, + // name: sa.name, + // description: sa.description, + // expiration: sa.expiration, + // }) + // .collect(); - let data = serde_json::to_vec(&ListServiceAccountsResp { accounts }) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?; + // let data = serde_json::to_vec(&ListServiceAccountsResp { accounts }) + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?; - let mut header = HeaderMap::new(); - header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); - Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) + // Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) } } diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 829f2221..73e7b30c 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -24,78 +24,79 @@ pub struct AddUser {} impl Operation for AddUser { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle AddUser"); - let query = { - if let Some(query) = req.uri.query() { - let input: AddUserQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?; - input - } else { - AddUserQuery::default() - } - }; + unimplemented!() + // let query = { + // if let Some(query) = req.uri.query() { + // let input: AddUserQuery = + // from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?; + // input + // } else { + // AddUserQuery::default() + // } + // }; - 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 ak = query.access_key.as_deref().unwrap_or_default(); + // let ak = query.access_key.as_deref().unwrap_or_default(); - if ak.is_empty() { - return Err(s3_error!(InvalidArgument, "access key is empty")); - } + // if ak.is_empty() { + // return Err(s3_error!(InvalidArgument, "access key is empty")); + // } - let mut input = req.input; - let body = match input.store_all_unlimited().await { - Ok(b) => b, - Err(e) => { - warn!("get body failed, e: {:?}", e); - return Err(s3_error!(InvalidRequest, "get body failed")); - } - }; + // let mut input = req.input; + // let body = match input.store_all_unlimited().await { + // Ok(b) => b, + // Err(e) => { + // warn!("get body failed, e: {:?}", e); + // return Err(s3_error!(InvalidRequest, "get body failed")); + // } + // }; - // let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body) - // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?; + // // let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body) + // // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?; - let args: AddOrUpdateUserReq = serde_json::from_slice(&body) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?; + // let args: AddOrUpdateUserReq = serde_json::from_slice(&body) + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?; - warn!("add user args {:?}", args); + // warn!("add user args {:?}", args); - if args.secret_key.is_empty() { - return Err(s3_error!(InvalidArgument, "access key is empty")); - } + // if args.secret_key.is_empty() { + // return Err(s3_error!(InvalidArgument, "access key is empty")); + // } - if let Some(sys_cred) = get_global_action_cred() { - if sys_cred.access_key == ak { - return Err(s3_error!(InvalidArgument, "can't create user with system access key")); - } - } + // if let Some(sys_cred) = get_global_action_cred() { + // if sys_cred.access_key == ak { + // return Err(s3_error!(InvalidArgument, "can't create user with system access key")); + // } + // } - if let (Some(user), true) = iam::get_user(ak) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))? - { - if user.credentials.is_temp() || user.credentials.is_service_account() { - return Err(s3_error!(InvalidArgument, "can't create user with service account access key")); - } - } + // if let (Some(user), true) = iam::get_user(ak) + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))? + // { + // if user.credentials.is_temp() || user.credentials.is_service_account() { + // return Err(s3_error!(InvalidArgument, "can't create user with service account access key")); + // } + // } - let token = get_session_token(&req.headers); + // let token = get_session_token(&req.headers); - let (cred, _) = check_key_valid(token, &input_cred.access_key).await?; + // let (cred, _) = check_key_valid(token, &input_cred.access_key).await?; - if (cred.is_temp() || cred.is_service_account()) && cred.parent_user == input_cred.access_key { - return Err(s3_error!(InvalidArgument, "can't create user with service account access key")); - } + // if (cred.is_temp() || cred.is_service_account()) && cred.parent_user == input_cred.access_key { + // return Err(s3_error!(InvalidArgument, "can't create user with service account access key")); + // } - iam::create_user(ak, &args.secret_key, "enabled") - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?; + // iam::create_user(ak, &args.secret_key, "enabled") + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?; - let mut header = HeaderMap::new(); - header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); - Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) + // Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) } } @@ -105,41 +106,43 @@ impl Operation for SetUserStatus { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle SetUserStatus"); - let query = { - if let Some(query) = req.uri.query() { - let input: AddUserQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; - input - } else { - AddUserQuery::default() - } - }; + unimplemented!() - let ak = query.access_key.as_deref().unwrap_or_default(); + // let query = { + // if let Some(query) = req.uri.query() { + // let input: AddUserQuery = + // from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; + // input + // } else { + // AddUserQuery::default() + // } + // }; - if ak.is_empty() { - return Err(s3_error!(InvalidArgument, "access key is empty")); - } + // let ak = query.access_key.as_deref().unwrap_or_default(); - let Some(input_cred) = req.credentials else { - return Err(s3_error!(InvalidRequest, "get cred failed")); - }; + // if ak.is_empty() { + // return Err(s3_error!(InvalidArgument, "access key is empty")); + // } - if input_cred.access_key == ak { - return Err(s3_error!(InvalidArgument, "can't change status of self")); - } + // let Some(input_cred) = req.credentials else { + // return Err(s3_error!(InvalidRequest, "get cred failed")); + // }; - let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default()) - .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?; + // if input_cred.access_key == ak { + // return Err(s3_error!(InvalidArgument, "can't change status of self")); + // } - iam::set_user_status(ak, status) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?; + // let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default()) + // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?; - let mut header = HeaderMap::new(); - header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + // iam::set_user_status(ak, status) + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?; - Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + + // Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) } } @@ -148,24 +151,25 @@ pub struct ListUsers {} impl Operation for ListUsers { async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle ListUsers"); - let users = iam::list_users() - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?; + unimplemented!() + // let users = iam::list_users() + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?; - let data = serde_json::to_vec(&users) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?; + // 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 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 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()); + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); - Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) + // Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) } } @@ -174,38 +178,39 @@ pub struct RemoveUser {} impl Operation for RemoveUser { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle RemoveUser"); - let query = { - if let Some(query) = req.uri.query() { - let input: AddUserQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; - input - } else { - AddUserQuery::default() - } - }; + unimplemented!() + // let query = { + // if let Some(query) = req.uri.query() { + // let input: AddUserQuery = + // from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; + // input + // } else { + // AddUserQuery::default() + // } + // }; - let ak = query.access_key.as_deref().unwrap_or_default(); + // let ak = query.access_key.as_deref().unwrap_or_default(); - if ak.is_empty() { - return Err(s3_error!(InvalidArgument, "access key is empty")); - } + // if ak.is_empty() { + // return Err(s3_error!(InvalidArgument, "access key is empty")); + // } - let (is_temp, _) = iam::is_temp_user(ak) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?; + // let (is_temp, _) = iam::is_temp_user(ak) + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?; - if is_temp { - return Err(s3_error!(InvalidArgument, "can't remove temp user")); - } + // if is_temp { + // return Err(s3_error!(InvalidArgument, "can't remove temp user")); + // } - iam::delete_user(ak, true) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?; + // iam::delete_user(ak, true) + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?; - let mut header = HeaderMap::new(); - header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); - Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) + // Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header)) } } @@ -214,32 +219,33 @@ pub struct GetUserInfo {} impl Operation for GetUserInfo { async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { warn!("handle GetUserInfo"); - let query = { - if let Some(query) = req.uri.query() { - let input: AddUserQuery = - from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; - input - } else { - AddUserQuery::default() - } - }; + unimplemented!() + // let query = { + // if let Some(query) = req.uri.query() { + // let input: AddUserQuery = + // from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?; + // input + // } else { + // AddUserQuery::default() + // } + // }; - let ak = query.access_key.as_deref().unwrap_or_default(); + // let ak = query.access_key.as_deref().unwrap_or_default(); - if ak.is_empty() { - return Err(s3_error!(InvalidArgument, "access key is empty")); - } + // if ak.is_empty() { + // return Err(s3_error!(InvalidArgument, "access key is empty")); + // } - let info = iam::get_user_info(ak) - .await - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?; + // let info = iam::get_user_info(ak) + // .await + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?; - let data = serde_json::to_vec(&info) - .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?; + // let data = serde_json::to_vec(&info) + // .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?; - let mut header = HeaderMap::new(); - header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); + // let mut header = HeaderMap::new(); + // header.insert(CONTENT_TYPE, "application/json".parse().unwrap()); - Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) + // Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header)) } } diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index ee6678c7..5e48b839 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -31,11 +31,7 @@ impl S3Auth for IAMAuth { warn!("Failed to get secret key from simple auth"); if let Ok(iam_store) = iam::get() { - let c = CacheInner::from(&iam_store.cache); - warn!("Failed to get secret key from simple auth, try cache {}", access_key); - warn!("users {:?}", c.users.values()); - warn!("sts_accounts {:?}", c.sts_accounts.values()); - if let Some(id) = c.get_user(access_key) { + if let Some(id) = iam_store.get_user(access_key).await { warn!("get cred {:?}", id.credentials); return Ok(SecretKey::from(id.credentials.secret_key.clone())); } diff --git a/scripts/run.sh b/scripts/run.sh index 55b3f7ff..27ca7caa 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -19,8 +19,8 @@ fi export RUSTFS_STORAGE_CLASS_INLINE_BLOCK="512 KB" -DATA_DIR_ARG="./target/volume/test{0...4}" -# DATA_DIR_ARG="./target/volume/test" +# DATA_DIR_ARG="./target/volume/test{0...4}" +DATA_DIR_ARG="./target/volume/test" if [ -n "$1" ]; then DATA_DIR_ARG="$1"