move policy out of iam

This commit is contained in:
weisd
2025-03-26 10:18:03 +08:00
parent 545ae79e44
commit c8e13b8ab5
56 changed files with 539 additions and 378 deletions
Generated
+30
View File
@@ -2944,6 +2944,7 @@ dependencies = [
"lazy_static",
"log",
"madmin",
"policy",
"rand 0.8.5",
"regex",
"serde",
@@ -4541,6 +4542,34 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "policy"
version = "0.0.1"
dependencies = [
"arc-swap",
"async-trait",
"base64-simd",
"common",
"crypto",
"futures",
"ipnetwork",
"itertools",
"jsonwebtoken",
"lazy_static",
"log",
"madmin",
"rand 0.8.5",
"regex",
"serde",
"serde_json",
"strum",
"test-case",
"thiserror 2.0.11",
"time",
"tokio",
"tracing",
]
[[package]]
name = "polling"
version = "3.7.4"
@@ -5310,6 +5339,7 @@ dependencies = [
"mime_guess",
"netif",
"pin-project-lite",
"policy",
"prost",
"prost-build",
"prost-types",
+1
View File
@@ -42,6 +42,7 @@ flatbuffers = "24.12.23"
futures = "0.3.31"
futures-util = "0.3.31"
common = { path = "./common/common" }
policy = {path = "./policy"}
hex = "0.4.3"
hyper = "1.6.0"
hyper-util = { version = "0.1.10", features = [
@@ -14,10 +14,7 @@ use protos::{
use rmp_serde::{Deserializer, Serializer};
use serde::{Deserialize, Serialize};
use std::{error::Error, io::Cursor};
use tokio::io::AsyncWrite;
use tokio::spawn;
use tokio::sync::mpsc;
use tonic::codegen::tokio_stream::wrappers::ReceiverStream;
use tonic::codegen::tokio_stream::StreamExt;
use tonic::Request;
+4 -4
View File
@@ -95,7 +95,7 @@ impl S3PeerSys {
for (i, client) in self.clients.iter().enumerate() {
if let Some(v) = client.get_pools() {
if v.contains(&pool_idx) {
per_pool_errs.push(errs[i].as_ref().map(|e| clone_err(e)));
per_pool_errs.push(errs[i].as_ref().map(clone_err));
}
}
}
@@ -130,7 +130,7 @@ impl S3PeerSys {
for (i, client) in self.clients.iter().enumerate() {
if let Some(v) = client.get_pools() {
if v.contains(&pool_idx) {
per_pool_errs.push(errs[i].as_ref().map(|e| clone_err(e)));
per_pool_errs.push(errs[i].as_ref().map(clone_err));
}
}
}
@@ -781,7 +781,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
let bucket = bucket.to_string();
let bs_clone = before_state.clone();
let as_clone = after_state.clone();
let errs_clone = errs.iter().map(|e| e.as_ref().map(|e| clone_err(e))).collect::<Vec<_>>();
let errs_clone = errs.iter().map(|e| e.as_ref().map(clone_err)).collect::<Vec<_>>();
futures.push(async move {
if bs_clone.read().await[idx] == DRIVE_STATE_MISSING {
info!("bucket not find, will recreate");
@@ -795,7 +795,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
}
}
}
errs_clone[idx].as_ref().map(|e| clone_err(e))
errs_clone[idx].as_ref().map(clone_err)
});
}
+1 -1
View File
@@ -107,7 +107,7 @@ fn reduce_errs(errs: &[Option<Error>], ignored_errs: &[Box<dyn CheckErrorFn>]) -
if let Some(&c) = error_counts.get(&max_err) {
if let Some(&err_idx) = error_map.get(&max_err) {
let err = errs[err_idx].as_ref().map(|e| clone_err(e));
let err = errs[err_idx].as_ref().map(clone_err);
return (c, err);
}
+1 -1
View File
@@ -5429,7 +5429,7 @@ pub fn should_heal_object_on_disk(
}
}
}
(false, err.as_ref().map(|e| clone_err(e)))
(false, err.as_ref().map(clone_err))
}
async fn get_disks_info(disks: &[Option<DiskStore>], eps: &[Endpoint]) -> Vec<madmin::Disk> {
+3 -3
View File
@@ -665,7 +665,7 @@ impl ECStore {
has_def_pool = true;
if !is_err_object_not_found(err) && !is_err_version_not_found(err) {
return Err(clone_err(&err));
return Err(clone_err(err));
}
if pinfo.object_info.delete_marker && !pinfo.object_info.name.is_empty() {
@@ -803,7 +803,7 @@ impl ECStore {
}
let _ = task.await;
if let Some(err) = first_err.read().await.as_ref() {
return Err(clone_err(&err));
return Err(clone_err(err));
}
Ok(())
}
@@ -1069,7 +1069,7 @@ impl Clone for PoolObjInfo {
Self {
index: self.index,
object_info: self.object_info.clone(),
err: self.err.as_ref().map(|e| clone_err(e)),
err: self.err.as_ref().map(clone_err),
}
}
}
+1
View File
@@ -15,6 +15,7 @@ log.workspace = true
time = { workspace = true, features = ["serde-human-readable"] }
serde = { workspace = true, features = ["derive", "rc"] }
ecstore = { path = "../ecstore" }
policy.workspace = true
serde_json.workspace = true
async-trait.workspace = true
thiserror.workspace = true
+5 -6
View File
@@ -7,14 +7,13 @@ use std::{
use arc_swap::{ArcSwap, AsRaw, Guard};
use log::warn;
use policy::{
auth::UserIdentity,
policy::{Args, PolicyDoc},
};
use time::OffsetDateTime;
use crate::{
auth::UserIdentity,
policy::PolicyDoc,
store::{GroupInfo, MappedPolicy},
sys::Args,
};
use crate::store::{GroupInfo, MappedPolicy};
pub struct Cache {
pub policy_docs: ArcSwap<CacheEntity<PolicyDoc>>,
+2 -3
View File
@@ -1,12 +1,11 @@
use ecstore::disk::error::clone_disk_err;
use ecstore::disk::error::DiskError;
use crate::policy;
use policy::policy::Error as PolicyError;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
PolicyError(#[from] policy::Error),
PolicyError(#[from] PolicyError),
#[error("ecsotre error: {0}")]
EcstoreError(common::error::Error),
-154
View File
@@ -1,154 +0,0 @@
// use std::{borrow::Cow, collections::HashMap};
// use log::{info, warn};
// 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<ARN, Vec<String>>,
// }
// impl<'m, T> Handler<'m, T> {
// pub fn new(cache: CacheInner, api: &'m T, roles: &'m HashMap<ARN, Vec<String>>) -> 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))
// }
// async fn get_policy(&self, name: &str, _groups: &[String]) -> crate::Result<Vec<String>> {
// if name.is_empty() {
// return Err(Error::InvalidArgument);
// }
// 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<Option<&str>> {
// 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)
// }
// }
// /// 如果是临时用户,返回Ok(Some(partent_name)))
// /// 如果不是临时用户,返回Ok(None)
// fn is_service_account<'a>(&self, user_name: &'a str) -> crate::Result<Option<&str>> {
// 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)
// }
// }
// // 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;
// };
// 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;
// if is_owner_derived {
// } else if let Some(x) = role_arn {
// let Ok(arn) = x.parse::<ARN>() 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));
// }
// if is_owner_derived && svc_policies.as_ref().map(|x| x.as_ref().len()).unwrap_or_default() == 0 {
// return false;
// }
// false
// }
// 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;
// }
// 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,
// _ => {}
// }
// let Ok(policies) = self.get_policy(&args.account, &args.groups).await else { return false };
// if policies.is_empty() {
// return false;
// }
// let policy = self.get_combined_policy(&policies[..]).await;
// policy.is_allowed(&args)
// }
// }
+1 -8
View File
@@ -1,23 +1,16 @@
use auth::Credentials;
use common::error::{Error, Result};
use ecstore::store::ECStore;
use error::Error as IamError;
use log::debug;
use manager::IamCache;
use policy::auth::Credentials;
use std::sync::{Arc, OnceLock};
use store::object::ObjectStore;
use sys::IamSys;
pub mod cache;
mod format;
mod handler;
pub mod arn;
pub mod auth;
pub mod error;
pub mod manager;
pub mod policy;
pub mod service_type;
pub mod store;
pub mod utils;
+8 -7
View File
@@ -1,15 +1,11 @@
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,
get_global_action_cred,
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,
UpdateServiceAccountOpts, MAX_SVCSESSION_POLICY_SIZE, SESSION_POLICY_NAME, SESSION_POLICY_NAME_EXTRACTED,
STATUS_DISABLED, STATUS_ENABLED,
},
};
use common::error::{Error, Result};
@@ -17,6 +13,12 @@ use ecstore::config::error::is_err_config_not_found;
use ecstore::utils::{crypto::base64_encode, path::path_join_buf};
use log::{debug, warn};
use madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
use policy::{
arn::ARN,
auth::{self, get_claims_from_token_with_secret, is_secret_key_valid, jwt_sign, Credentials, UserIdentity},
format::Format,
policy::{iam_policy_claim_name_sa, Policy, PolicyDoc, DEFAULT_POLICIES, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
@@ -486,7 +488,6 @@ where
if !is_secret_key_valid(&secret) {
return Err(IamError::InvalidSecretKeyLength.into());
}
cr.secret_key = secret;
}
-49
View File
@@ -1,49 +0,0 @@
pub mod action;
mod doc;
mod effect;
mod function;
mod id;
#[allow(clippy::module_inception)]
mod policy;
pub mod resource;
pub mod statement;
pub(crate) mod utils;
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;
pub use statement::Statement;
#[derive(thiserror::Error, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum Error {
#[error("invalid Version '{0}'")]
InvalidVersion(String),
#[error("invalid Effect '{0}'")]
InvalidEffect(String),
#[error("both 'Action' and 'NotAction' are empty")]
NonAction,
#[error("'Resource' is empty")]
NonResource,
#[error("invalid key name: '{0}'")]
InvalidKeyName(String),
#[error("invalid key: '{0}'")]
InvalidKey(String),
#[error("invalid action: '{0}'")]
InvalidAction(String),
#[error("invalid resource, type: '{0}', pattern: '{1}'")]
InvalidResource(String, String),
}
+2 -1
View File
@@ -1,7 +1,8 @@
pub mod object;
use crate::{auth::UserIdentity, cache::Cache, policy::PolicyDoc};
use crate::cache::Cache;
use common::error::Result;
use policy::{auth::UserIdentity, policy::PolicyDoc};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use time::OffsetDateTime;
+1 -2
View File
@@ -1,11 +1,9 @@
use super::{GroupInfo, MappedPolicy, Store, UserType};
use crate::{
auth::UserIdentity,
cache::{Cache, CacheEntity},
error::{is_err_no_such_policy, is_err_no_such_user},
get_global_action_cred,
manager::{extract_jwt_claims, get_default_policyes},
policy::PolicyDoc,
};
use common::error::{Error, Result};
use ecstore::{
@@ -21,6 +19,7 @@ use ecstore::{
};
use futures::future::join_all;
use lazy_static::lazy_static;
use policy::{auth::UserIdentity, policy::PolicyDoc};
use serde::{de::DeserializeOwned, Serialize};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
+17 -89
View File
@@ -1,16 +1,3 @@
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;
@@ -18,9 +5,6 @@ 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;
@@ -29,8 +13,25 @@ use ecstore::utils::crypto::base64_decode;
use ecstore::utils::crypto::base64_encode;
use madmin::AddOrUpdateUserReq;
use madmin::GroupDesc;
use policy::arn::ARN;
use policy::auth::contains_reserved_chars;
use policy::auth::create_new_credentials_with_metadata;
use policy::auth::generate_credentials;
use policy::auth::is_access_key_valid;
use policy::auth::is_secret_key_valid;
use policy::auth::Credentials;
use policy::auth::UserIdentity;
use policy::auth::ACCOUNT_ON;
use policy::policy::iam_policy_claim_name_sa;
use policy::policy::Args;
use policy::policy::Policy;
use policy::policy::PolicyDoc;
use policy::policy::EMBEDDED_POLICY_TYPE;
use policy::policy::INHERITED_POLICY_TYPE;
use serde_json::json;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use time::OffsetDateTime;
pub const MAX_SVCSESSION_POLICY_SIZE: usize = 4096;
@@ -42,9 +43,6 @@ 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<T> {
store: Arc<IamCache<T>>,
roles_map: HashMap<ARN, String>,
@@ -697,73 +695,3 @@ pub struct UpdateServiceAccountOpts {
pub expiration: Option<OffsetDateTime>,
pub status: Option<String>,
}
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 Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object: &'a str,
pub claims: &'a HashMap<String, Value>,
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<String>, bool) {
get_policies_from_claims(self.claims, policy_claim_name)
}
}
fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, 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<String, Value>, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_values_from_claims(claims, policy_claim_name)
}
+36
View File
@@ -0,0 +1,36 @@
[package]
name = "policy"
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
tokio.workspace = true
log.workspace = true
time = { workspace = true, features = ["serde-human-readable"] }
serde = { workspace = true, features = ["derive", "rc"] }
serde_json.workspace = true
async-trait.workspace = true
thiserror.workspace = true
strum = { version = "0.27.1", features = ["derive"] }
arc-swap = "1.7.1"
crypto = { path = "../crypto" }
ipnetwork = { version = "0.21.1", features = ["serde"] }
itertools = "0.14.0"
futures.workspace = true
rand.workspace = true
base64-simd = "0.8.0"
jsonwebtoken = "9.3.0"
tracing.workspace = true
madmin.workspace = true
lazy_static.workspace = true
regex = "1.11.1"
common.workspace = true
[dev-dependencies]
test-case.workspace = true
@@ -1,6 +1,5 @@
use crate::error::Error as IamError;
use crate::policy::Policy;
use crate::sys::{iam_policy_claim_name_sa, Validator, INHERITED_POLICY_TYPE};
use crate::policy::{iam_policy_claim_name_sa, Policy, Validator, INHERITED_POLICY_TYPE};
use crate::utils;
use crate::utils::extract_claims;
use common::error::{Error, Result};
+145
View File
@@ -0,0 +1,145 @@
use crate::policy;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
PolicyError(#[from] policy::Error),
#[error("ecsotre error: {0}")]
EcstoreError(common::error::Error),
#[error("{0}")]
StringError(String),
#[error("crypto: {0}")]
CryptoError(#[from] crypto::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,
#[error("invalid arguments specified")]
InvalidArgument,
#[error("not initialized")]
IamSysNotInitialized,
#[error("invalid service type: {0}")]
InvalidServiceType(String),
#[error("malformed credential")]
ErrCredMalformed,
#[error("CredNotInitialized")]
CredNotInitialized,
#[error("invalid access key length")]
InvalidAccessKeyLength,
#[error("invalid secret key length")]
InvalidSecretKeyLength,
#[error("access key contains reserved characters =,")]
ContainsReservedChars,
#[error("group name contains reserved characters =,")]
GroupNameContainsReservedChars,
#[error("jwt err {0}")]
JWTError(jsonwebtoken::errors::Error),
#[error("no access key")]
NoAccessKey,
#[error("invalid token")]
InvalidToken,
#[error("invalid access_key")]
InvalidAccessKey,
#[error("action not allowed")]
IAMActionNotAllowed,
#[error("invalid expiration")]
InvalidExpiration,
#[error("no secret key with access key")]
NoSecretKeyWithAccessKey,
#[error("no access key with secret key")]
NoAccessKeyWithSecretKey,
#[error("policy too large")]
PolicyTooLarge,
}
// pub fn is_err_no_such_user(e: &Error) -> bool {
// matches!(e, Error::NoSuchUser(_))
// }
pub fn is_err_no_such_policy(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchPolicy)
} else {
false
}
}
pub fn is_err_no_such_user(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchUser(_))
} else {
false
}
}
pub fn is_err_no_such_account(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchAccount(_))
} else {
false
}
}
pub fn is_err_no_such_temp_account(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchTempAccount(_))
} else {
false
}
}
pub fn is_err_no_such_group(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchGroup(_))
} else {
false
}
}
pub fn is_err_no_such_service_account(err: &common::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchServiceAccount(_))
} else {
false
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod arn;
pub mod auth;
pub mod error;
pub mod format;
pub mod policy;
pub mod service_type;
pub mod utils;
+128
View File
@@ -0,0 +1,128 @@
pub mod action;
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 std::collections::{HashMap, HashSet};
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_json::Value;
pub use statement::Statement;
use common::error::Result;
pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy";
pub const INHERITED_POLICY_TYPE: &str = "inherited-policy";
#[derive(thiserror::Error, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum Error {
#[error("invalid Version '{0}'")]
InvalidVersion(String),
#[error("invalid Effect '{0}'")]
InvalidEffect(String),
#[error("both 'Action' and 'NotAction' are empty")]
NonAction,
#[error("'Resource' is empty")]
NonResource,
#[error("invalid key name: '{0}'")]
InvalidKeyName(String),
#[error("invalid key: '{0}'")]
InvalidKey(String),
#[error("invalid action: '{0}'")]
InvalidAction(String),
#[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 {
type Error;
fn is_valid(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Args<'a> {
pub account: &'a str,
pub groups: &'a Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object: &'a str,
pub claims: &'a HashMap<String, Value>,
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<String>, bool) {
get_policies_from_claims(self.claims, policy_claim_name)
}
}
fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, 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<String, Value>, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_values_from_claims(claims, policy_claim_name)
}
pub fn iam_policy_claim_name_sa() -> String {
"sa-policy".to_string()
}
@@ -3,9 +3,7 @@ use serde::{Deserialize, Serialize};
use std::{collections::HashSet, ops::Deref};
use strum::{EnumString, IntoStaticStr};
use crate::sys::Validator;
use super::{utils::wildcard, Error as IamError};
use super::{utils::wildcard, Error as IamError, Validator};
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct ActionSet(pub HashSet<Action>);
@@ -2,7 +2,7 @@ use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
use strum::{EnumString, IntoStaticStr};
use crate::sys::Validator;
use super::Validator;
#[derive(Serialize, Clone, Deserialize, EnumString, IntoStaticStr, Default, Debug, PartialEq)]
#[serde(try_from = "&str", into = "&str")]
@@ -1,5 +1,5 @@
use super::key_name::KeyName;
use crate::{policy::Error as PolicyError, sys::Validator};
use crate::policy::{Error as PolicyError, Validator};
use common::error::Error;
use serde::{Deserialize, Serialize};
@@ -2,7 +2,7 @@ use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use crate::sys::Validator;
use super::Validator;
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct ID(pub String);
@@ -1,5 +1,4 @@
use super::{Effect, Error as IamError, Statement, ID};
use crate::sys::{Args, Validator, DEFAULT_VERSION};
use super::{Args, Effect, Error as IamError, Statement, Validator, DEFAULT_VERSION, ID};
use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
@@ -122,13 +121,10 @@ 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,
},
sys::DEFAULT_VERSION,
use crate::policy::{
action::{Action, AdminAction, KmsAction, S3Action},
resource::Resource,
ActionSet, Effect, Functions, ResourceSet, Statement, DEFAULT_VERSION,
};
use super::Policy;
@@ -323,7 +319,7 @@ pub mod default {
#[cfg(test)]
mod test {
use super::*;
use common::error::{Error, Result};
use common::error::Result;
#[tokio::test]
async fn test_parse_policy() -> Result<()> {
@@ -6,12 +6,10 @@ use std::{
ops::Deref,
};
use crate::sys::Validator;
use super::{
function::key_name::KeyName,
utils::{path, wildcard},
Error as IamError,
Error as IamError, Validator,
};
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
@@ -1,6 +1,4 @@
use crate::sys::{Args, Validator};
use super::{action::Action, ActionSet, Effect, Error as IamError, Functions, ResourceSet, ID};
use super::{action::Action, ActionSet, Args, Effect, Error as IamError, Functions, ResourceSet, Validator, ID};
use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
+110
View File
@@ -0,0 +1,110 @@
use common::error::{Error, Result};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header};
use rand::{Rng, RngCore};
use serde::{de::DeserializeOwned, Serialize};
pub fn gen_access_key(length: usize) -> Result<String> {
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::msg("access key length is too short"));
}
let mut result = String::with_capacity(length);
let mut rng = rand::thread_rng();
for _ in 0..length {
result.push(ALPHA_NUMERIC_TABLE[rng.gen_range(0..ALPHA_NUMERIC_TABLE.len())]);
}
Ok(result)
}
pub fn gen_secret_key(length: usize) -> Result<String> {
use base64_simd::URL_SAFE_NO_PAD;
if length < 8 {
return Err(Error::msg("secret key length is too short"));
}
let mut rng = rand::thread_rng();
let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)];
rng.fill_bytes(&mut key);
let encoded = URL_SAFE_NO_PAD.encode_to_string(&key);
let key_str = encoded.replace("/", "+");
Ok(key_str)
}
pub fn generate_jwt<T: Serialize>(claims: &T, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
let header = Header::new(Algorithm::HS512);
jsonwebtoken::encode(&header, &claims, &EncodingKey::from_secret(secret.as_bytes()))
}
pub fn extract_claims<T: DeserializeOwned>(
token: &str,
secret: &str,
) -> Result<jsonwebtoken::TokenData<T>, jsonwebtoken::errors::Error> {
jsonwebtoken::decode::<T>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&jsonwebtoken::Validation::new(Algorithm::HS512),
)
}
#[cfg(test)]
mod tests {
use super::{gen_access_key, gen_secret_key, generate_jwt};
use serde::{Deserialize, Serialize};
#[test]
fn test_gen_access_key() {
let a = gen_access_key(10).unwrap();
let b = gen_access_key(10).unwrap();
assert_eq!(a.len(), 10);
assert_eq!(b.len(), 10);
assert_ne!(a, b);
}
#[test]
fn test_gen_secret_key() {
let a = gen_secret_key(10).unwrap();
let b = gen_secret_key(10).unwrap();
assert_ne!(a, b);
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
company: String,
}
#[test]
fn test_generate_jwt() {
let claims = Claims {
sub: "user1".to_string(),
company: "example".to_string(),
};
let secret = "my_secret";
let token = generate_jwt(&claims, secret).unwrap();
assert!(!token.is_empty());
}
// #[test]
// fn test_extract_claims() {
// let claims = Claims {
// sub: "user1".to_string(),
// company: "example".to_string(),
// };
// let secret = "my_secret";
// let token = generate_jwt(&claims, secret).unwrap();
// let decoded_claims = extract_claims::<Claims>(&token, secret).unwrap();
// assert_eq!(decoded_claims.claims, claims);
// }
}
@@ -1,11 +1,8 @@
use iam::policy::action::Action;
use iam::policy::action::ActionSet;
use iam::policy::action::S3Action::*;
use iam::policy::resource::ResourceSet;
use iam::policy::Effect::*;
use iam::policy::{Policy, Statement};
use iam::sys::Args;
use iam::sys::DEFAULT_VERSION;
use policy::policy::action::Action;
use policy::policy::action::S3Action::*;
use policy::policy::ActionSet;
use policy::policy::Effect::*;
use policy::policy::*;
use serde_json::Value;
use std::collections::HashMap;
use test_case::test_case;
@@ -24,10 +21,10 @@ struct ArgsBuilder {
}
#[test_case(
Policy{
version: DEFAULT_VERSION.into(),
policy::policy::Policy{
version: policy::policy::DEFAULT_VERSION.into(),
statements: vec![
Statement{
policy::policy::Statement{
effect: Allow,
actions: ActionSet(vec![Action::S3Action(PutObjectAction), Action::S3Action(GetBucketLocationAction)].into_iter().collect()),
resources: ResourceSet(vec!["arn:aws:s3:::*".try_into().unwrap()].into_iter().collect()),
@@ -46,7 +43,7 @@ struct ArgsBuilder {
)]
#[test_case(
Policy{
version: iam::sys::DEFAULT_VERSION.into(),
version: DEFAULT_VERSION.into(),
statements: vec![
Statement{
effect: Allow,
@@ -581,7 +578,7 @@ struct ArgsBuilder {
)]
#[test_case(
Policy{
version: iam::sys::DEFAULT_VERSION.into(),
version: DEFAULT_VERSION.into(),
statements: vec![
Statement{
effect: Deny,
+1
View File
@@ -22,6 +22,7 @@ bytes.workspace = true
clap.workspace = true
common.workspace = true
ecstore.workspace = true
policy.workspace =true
flatbuffers.workspace = true
futures.workspace = true
futures-util.workspace = true
+2 -1
View File
@@ -2,8 +2,9 @@ use std::collections::HashMap;
use crate::admin::{router::Operation, utils::has_space_be};
use http::{HeaderMap, StatusCode};
use iam::{error::is_err_no_such_user, get_global_action_cred, policy::Policy, store::MappedPolicy};
use iam::{error::is_err_no_such_user, get_global_action_cred, store::MappedPolicy};
use matchit::Params;
use policy::policy::Policy;
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
use serde::Deserialize;
use serde_urlencoded::from_bytes;
+1 -1
View File
@@ -5,7 +5,6 @@ use hyper::StatusCode;
use iam::{
error::is_err_no_such_service_account,
get_global_action_cred,
policy::Policy,
sys::{NewServiceAccountOpts, UpdateServiceAccountOpts},
};
use madmin::{
@@ -13,6 +12,7 @@ use madmin::{
ServiceAccountInfo, UpdateServiceAccountReq,
};
use matchit::Params;
use policy::policy::Policy;
use s3s::S3ErrorCode::InvalidRequest;
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
use serde::Deserialize;
+2 -1
View File
@@ -6,8 +6,9 @@ use crate::{
};
use ecstore::utils::{crypto::base64_encode, xml};
use http::StatusCode;
use iam::{auth::get_new_credentials_with_metadata, manager::get_token_signing_key, policy::Policy, sys::SESSION_POLICY_NAME};
use iam::{manager::get_token_signing_key, sys::SESSION_POLICY_NAME};
use matchit::Params;
use policy::{auth::get_new_credentials_with_metadata, policy::Policy};
use s3s::{
dto::{AssumeRoleOutput, Credentials, Timestamp},
s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result,
+2 -2
View File
@@ -1,11 +1,11 @@
use std::collections::HashMap;
use http::HeaderMap;
use iam::auth;
use iam::auth::get_claims_from_token_with_secret;
use iam::error::Error as IamError;
use iam::get_global_action_cred;
use iam::sys::SESSION_POLICY_NAME;
use policy::auth;
use policy::auth::get_claims_from_token_with_secret;
use s3s::auth::S3Auth;
use s3s::auth::SecretKey;
use s3s::auth::SimpleAuth;
+3 -3
View File
@@ -1,9 +1,9 @@
use super::ecfs::FS;
use crate::auth::{check_key_valid, get_condition_values};
use iam::auth;
use iam::error::Error as IamError;
use iam::policy::action::{Action, S3Action};
use iam::sys::Args;
use policy::auth;
use policy::policy::action::{Action, S3Action};
use policy::policy::Args;
use s3s::access::{S3Access, S3AccessContext};
use s3s::{dto::*, s3_error, S3Error, S3ErrorCode, S3Request, S3Result};
use std::collections::HashMap;
+2 -2
View File
@@ -39,10 +39,10 @@ use ecstore::xhttp;
use futures::pin_mut;
use futures::{Stream, StreamExt};
use http::HeaderMap;
use iam::policy::action::Action;
use iam::policy::action::S3Action;
use lazy_static::lazy_static;
use log::warn;
use policy::policy::action::Action;
use policy::policy::action::S3Action;
use s3s::dto::*;
use s3s::s3_error;
use s3s::S3Error;