mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 16:48:58 +00:00
Generated
+3
@@ -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",
|
||||
]
|
||||
|
||||
@@ -33,7 +33,7 @@ pub async fn read_config<S: StorageAPI>(api: Arc<S>, file: &str) -> Result<Vec<u
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
async fn read_config_with_metadata<S: StorageAPI>(
|
||||
pub async fn read_config_with_metadata<S: StorageAPI>(
|
||||
api: Arc<S>,
|
||||
file: &str,
|
||||
opts: &ObjectOptions,
|
||||
@@ -72,6 +72,30 @@ pub async fn save_config<S: StorageAPI>(api: Arc<S>, file: &str, data: &[u8]) ->
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_config<S: StorageAPI>(api: Arc<S>, 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<S: StorageAPI>(api: Arc<S>, file: &str, data: &[u8], opts: &ObjectOptions) -> Result<()> {
|
||||
let _ = api
|
||||
.put_object(
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::new_object_layer_fn;
|
||||
use crate::set_disk::SetDisks;
|
||||
use crate::store_api::{BucketInfo, HTTPRangeSpec, ObjectIO, ObjectOptions};
|
||||
use crate::store_api::{BucketInfo, ObjectIO, ObjectOptions};
|
||||
use bytesize::ByteSize;
|
||||
use http::HeaderMap;
|
||||
use path_clean::PathClean;
|
||||
|
||||
+28
-24
@@ -2,7 +2,7 @@ use crate::disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::heal::background_heal_ops::{heal_bucket, heal_object};
|
||||
use crate::heal::heal_commands::{HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN};
|
||||
use crate::utils::path::SLASH_SEPARATOR;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use chrono::{DateTime, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::ops::Sub;
|
||||
@@ -15,7 +15,7 @@ use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const MRF_OPS_QUEUE_SIZE: u64 = 100000;
|
||||
pub const HEAL_DIR: &'static str = ".heal";
|
||||
pub const HEAL_DIR: &str = ".heal";
|
||||
pub const HEAL_MRFMETA_FORMAT: u64 = 1;
|
||||
pub const HEAL_MRFMETA_VERSION_V1: u64 = 1;
|
||||
|
||||
@@ -49,6 +49,12 @@ pub struct MRFState {
|
||||
closing: AtomicBool,
|
||||
}
|
||||
|
||||
impl Default for MRFState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MRFState {
|
||||
pub fn new() -> MRFState {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(MRF_OPS_QUEUE_SIZE as usize);
|
||||
@@ -89,29 +95,27 @@ impl MRFState {
|
||||
if let Err(err) = heal_bucket(&op.bucket).await {
|
||||
error!("heal bucket failed, bucket: {}, err: {:?}", op.bucket, err);
|
||||
}
|
||||
} else if op.versions.is_empty() {
|
||||
if let Err(err) =
|
||||
heal_object(&op.bucket, &op.object, &op.version_id.clone().unwrap_or_default(), scan_mode).await
|
||||
{
|
||||
error!("heal object failed, bucket: {}, object: {}, err: {:?}", op.bucket, op.object, err);
|
||||
}
|
||||
} else {
|
||||
if op.versions.is_empty() {
|
||||
if let Err(err) =
|
||||
heal_object(&op.bucket, &op.object, &op.version_id.clone().unwrap_or_default(), scan_mode).await
|
||||
{
|
||||
error!("heal object failed, bucket: {}, object: {}, err: {:?}", op.bucket, op.object, err);
|
||||
}
|
||||
} else {
|
||||
let vers = op.versions.len() / 16;
|
||||
if vers > 0 {
|
||||
for i in 0..vers {
|
||||
let start = i * 16;
|
||||
let end = start + 16;
|
||||
if let Err(err) = heal_object(
|
||||
&op.bucket,
|
||||
&op.object,
|
||||
&Uuid::from_slice(&op.versions[start..end]).expect("").to_string(),
|
||||
scan_mode,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("heal object failed, bucket: {}, object: {}, err: {:?}", op.bucket, op.object, err);
|
||||
}
|
||||
let vers = op.versions.len() / 16;
|
||||
if vers > 0 {
|
||||
for i in 0..vers {
|
||||
let start = i * 16;
|
||||
let end = start + 16;
|
||||
if let Err(err) = heal_object(
|
||||
&op.bucket,
|
||||
&op.object,
|
||||
&Uuid::from_slice(&op.versions[start..end]).expect("").to_string(),
|
||||
scan_mode,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("heal object failed, bucket: {}, object: {}, err: {:?}", op.bucket, op.object, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,19 +97,19 @@ pub async fn collect_local_metrics(types: MetricType, opts: &CollectMetricsOpts)
|
||||
real_time_metrics.aggregated.scanner = Some(metrics);
|
||||
}
|
||||
|
||||
if types.contains(&MetricType::OS) {}
|
||||
// if types.contains(&MetricType::OS) {}
|
||||
|
||||
if types.contains(&MetricType::BATCH_JOBS) {}
|
||||
// if types.contains(&MetricType::BATCH_JOBS) {}
|
||||
|
||||
if types.contains(&MetricType::SITE_RESYNC) {}
|
||||
// if types.contains(&MetricType::SITE_RESYNC) {}
|
||||
|
||||
if types.contains(&MetricType::NET) {}
|
||||
// if types.contains(&MetricType::NET) {}
|
||||
|
||||
if types.contains(&MetricType::MEM) {}
|
||||
// if types.contains(&MetricType::MEM) {}
|
||||
|
||||
if types.contains(&MetricType::CPU) {}
|
||||
// if types.contains(&MetricType::CPU) {}
|
||||
|
||||
if types.contains(&MetricType::RPC) {}
|
||||
// if types.contains(&MetricType::RPC) {}
|
||||
|
||||
real_time_metrics
|
||||
.by_host
|
||||
@@ -128,10 +128,8 @@ async fn collect_local_disks_metrics(disks: &HashSet<String>) -> HashMap<String,
|
||||
let mut metrics = HashMap::new();
|
||||
let storage_info = store.local_storage_info().await;
|
||||
for d in storage_info.disks.iter() {
|
||||
if !disks.is_empty() {
|
||||
if !disks.contains(&d.endpoint) {
|
||||
continue;
|
||||
}
|
||||
if !disks.is_empty() && !disks.contains(&d.endpoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if d.state != *DRIVE_STATE_OK && d.state != *DRIVE_STATE_UNFORMATTED {
|
||||
|
||||
@@ -672,6 +672,7 @@ impl ECStore {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
pub async fn walk(
|
||||
self: Arc<Self>,
|
||||
rx: B_Receiver<bool>,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+83
-11
@@ -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<Self> {
|
||||
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<Self, Self::Err> {
|
||||
todo!()
|
||||
pub fn parse(arn_str: &str) -> Result<Self> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+197
-165
@@ -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::{iam_policy_claim_name_sa, Validator, INHERITED_POLICY_TYPE};
|
||||
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::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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
// 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<Vec<BorrowedFormatItem<'static>>> =
|
||||
LazyCell::new(|| time::format_description::parse("[year][month][day]").unwrap());
|
||||
// Ok(CredentialHeader {
|
||||
// access_key: ak.to_owned(),
|
||||
// scop: CredentialHeaderScope {
|
||||
// date: {
|
||||
// const FORMATTER: LazyCell<Vec<BorrowedFormatItem<'static>>> =
|
||||
// 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<Vec<String>>,
|
||||
pub claims: Option<HashMap<String, String>>,
|
||||
pub claims: Option<HashMap<String, Value>>,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub fn new(elem: &str) -> crate::Result<Self> {
|
||||
let header: CredentialHeader = elem.try_into()?;
|
||||
Self::check_key_value(header)
|
||||
}
|
||||
// pub fn new(elem: &str) -> Result<Self> {
|
||||
// let header: CredentialHeader = elem.try_into()?;
|
||||
// Self::check_key_value(header)
|
||||
// }
|
||||
|
||||
pub fn check_key_value(_header: CredentialHeader) -> crate::Result<Self> {
|
||||
todo!()
|
||||
}
|
||||
// pub fn check_key_value(_header: CredentialHeader) -> Result<Self> {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
if self.expiration.is_none() {
|
||||
@@ -158,6 +158,21 @@ impl Credentials {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_implied_policy(&self) -> bool {
|
||||
if self.is_service_account() {
|
||||
return self
|
||||
.claims
|
||||
.as_ref()
|
||||
.map(|x| {
|
||||
x.get(&iam_policy_claim_name_sa())
|
||||
.map_or(false, |v| v == INHERITED_POLICY_TYPE)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
if self.status == "off" {
|
||||
return false;
|
||||
@@ -171,50 +186,67 @@ impl Credentials {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_new_credentials_with_metadata<T: Serialize>(
|
||||
claims: &T,
|
||||
token_secret: &str,
|
||||
exp: Option<usize>,
|
||||
) -> crate::Result<Credentials> {
|
||||
let ak = utils::gen_access_key(20).unwrap_or_default();
|
||||
let sk = utils::gen_secret_key(32).unwrap_or_default();
|
||||
|
||||
create_new_credentials_with_metadata(&ak, &sk, claims, token_secret, exp)
|
||||
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 create_new_credentials_with_metadata<T: Serialize>(
|
||||
pub fn get_new_credentials_with_metadata(claims: &HashMap<String, Value>, token_secret: &str) -> Result<Credentials> {
|
||||
let (ak, sk) = generate_credentials()?;
|
||||
|
||||
create_new_credentials_with_metadata(&ak, &sk, claims, token_secret)
|
||||
}
|
||||
|
||||
pub fn create_new_credentials_with_metadata(
|
||||
ak: &str,
|
||||
sk: &str,
|
||||
claims: &T,
|
||||
claims: &HashMap<String, Value>,
|
||||
token_secret: &str,
|
||||
exp: Option<usize>,
|
||||
) -> crate::Result<Credentials> {
|
||||
) -> Result<Credentials> {
|
||||
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 expiration = {
|
||||
if let Some(v) = claims.get("exp") {
|
||||
if let Some(expiry) = v.as_i64() {
|
||||
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_utc().offset()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let token = utils::generate_jwt(claims, token_secret)?;
|
||||
|
||||
Ok(Credentials {
|
||||
access_key: ak.to_owned(),
|
||||
secret_key: sk.to_owned(),
|
||||
session_token: token,
|
||||
status: ACCOUNT_ON.to_owned(),
|
||||
expiration: exp.map(|v| OffsetDateTime::now_utc().saturating_add(Duration::seconds(v as i64))),
|
||||
expiration,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_claims_from_token_with_secret<T: DeserializeOwned>(token: &str, secret: &str) -> crate::Result<T> {
|
||||
let ms = extract_claims::<T>(token, secret).map_err(Error::JWTError)?;
|
||||
pub fn get_claims_from_token_with_secret<T: DeserializeOwned>(token: &str, secret: &str) -> Result<T> {
|
||||
let ms = extract_claims::<T>(token, secret)?;
|
||||
// TODO SessionPolicyName
|
||||
Ok(ms.claims)
|
||||
}
|
||||
|
||||
pub fn jwt_sign<T: Serialize>(claims: &T, token_secret: &str) -> Result<String> {
|
||||
let token = utils::generate_jwt(claims, token_secret)?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CredentialsBuilder {
|
||||
session_policy: Option<Policy>,
|
||||
@@ -284,30 +316,30 @@ impl CredentialsBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_build(self) -> crate::Result<Credentials> {
|
||||
pub fn try_build(self) -> Result<Credentials> {
|
||||
self.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CredentialsBuilder> for Credentials {
|
||||
type Error = crate::Error;
|
||||
type Error = Error;
|
||||
fn try_from(mut value: CredentialsBuilder) -> Result<Self, Self::Error> {
|
||||
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 +348,9 @@ impl TryFrom<CredentialsBuilder> 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 +387,21 @@ impl TryFrom<CredentialsBuilder> 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 +412,66 @@ impl TryFrom<CredentialsBuilder> 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!()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
+45
-29
@@ -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<Vec<Policy>> {
|
||||
todo!()
|
||||
}
|
||||
// fn get_policy(&self, _name: &str, _groups: &[String]) -> crate::Result<Vec<Policy>> {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
/// 如果是临时用户,返回Ok(Some(partent_name)))
|
||||
/// 如果不是临时用户,返回Ok(None)
|
||||
fn is_temp_user(&self, user_name: &str) -> crate::Result<Option<&str>> {
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
// 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<Option<&str>> {
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
|
||||
+76
-3
@@ -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,65 @@ 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<T> = std::result::Result<T, Error>;
|
||||
// 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::<Error>() {
|
||||
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::<Error>() {
|
||||
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::<Error>() {
|
||||
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::<Error>() {
|
||||
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::<Error>() {
|
||||
matches!(e, Error::NoSuchGroup(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_no_such_service_account(err: &ecstore::error::Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<Error>() {
|
||||
matches!(e, Error::NoSuchServiceAccount(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -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,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
+128
-128
@@ -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<ARN, Vec<String>>,
|
||||
}
|
||||
// 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> {
|
||||
// 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))
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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()))?;
|
||||
// /// 如果是临时用户,返回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)
|
||||
}
|
||||
}
|
||||
// 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()))?;
|
||||
// /// 如果是临时用户,返回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)
|
||||
}
|
||||
}
|
||||
// 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::<ARN>() 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::<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));
|
||||
}
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
|
||||
+17
-90
@@ -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<Arc<IamCache<ObjectStore>>> = OnceLock::new();
|
||||
static IAM_SYS: OnceLock<Arc<IamSys<ObjectStore>>> = OnceLock::new();
|
||||
|
||||
static GLOBAL_ACTIVE_CRED: OnceLock<Credentials> = OnceLock::new();
|
||||
|
||||
@@ -53,96 +50,26 @@ pub fn init_global_action_cred(ak: Option<String>, sk: Option<String>) -> Result
|
||||
secret_key: sk,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_e| Error::CredNotInitialized)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_global_action_cred() -> Option<Credentials> {
|
||||
GLOBAL_ACTIVE_CRED.get().cloned()
|
||||
}
|
||||
|
||||
pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> crate::Result<()> {
|
||||
pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> 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<Arc<IamCache<ObjectStore>>> {
|
||||
IAM_SYS.get().map(Arc::clone).ok_or(Error::IamSysNotInitialized)
|
||||
}
|
||||
|
||||
pub async fn is_allowed(args: Args<'_>) -> crate::Result<bool> {
|
||||
Ok(get()?.is_allowed(args).await)
|
||||
}
|
||||
|
||||
pub async fn get_service_account(ak: &str) -> crate::Result<(Credentials, Option<Policy>)> {
|
||||
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<OffsetDateTime> {
|
||||
get()?.add_service_account(cred).await
|
||||
}
|
||||
|
||||
pub async fn check_key(ak: &str) -> crate::Result<(Option<UserIdentity>, 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<HashMap<String, madmin::UserInfo>> {
|
||||
get()?.get_users().await
|
||||
}
|
||||
|
||||
pub async fn get_user(ak: &str) -> crate::Result<(Option<UserIdentity>, bool)> {
|
||||
get()?.check_key(ak).await
|
||||
}
|
||||
|
||||
pub async fn create_user(ak: &str, sk: &str, status: &str) -> crate::Result<OffsetDateTime> {
|
||||
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<madmin::UserInfo> {
|
||||
get()?.get_user_info(ak).await
|
||||
}
|
||||
|
||||
pub async fn set_user_status(ak: &str, status: AccountStatus) -> crate::Result<OffsetDateTime> {
|
||||
get()?.set_user_status(ak, status).await
|
||||
}
|
||||
|
||||
pub async fn list_service_accounts(ak: &str) -> crate::Result<Vec<Credentials>> {
|
||||
get()?.list_service_accounts(ak).await
|
||||
}
|
||||
|
||||
pub async fn remove_users_from_group(group: &str, members: Vec<String>) -> crate::Result<OffsetDateTime> {
|
||||
get()?.remove_users_from_group(group, members).await
|
||||
pub fn get() -> Result<Arc<IamSys<ObjectStore>>> {
|
||||
IAM_SYS
|
||||
.get()
|
||||
.map(Arc::clone)
|
||||
.ok_or(Error::new(IamError::IamSysNotInitialized))
|
||||
}
|
||||
|
||||
+1315
-247
File diff suppressed because it is too large
Load Diff
+3
-83
@@ -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<String> {
|
||||
self.policies
|
||||
.split(",")
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.map(|v| v.to_string())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GroupInfo {
|
||||
version: i64,
|
||||
status: String,
|
||||
members: Vec<String>,
|
||||
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<String, Vec<String>>,
|
||||
pub is_owner: bool,
|
||||
pub object: &'a str,
|
||||
pub claims: &'a HashMap<String, Value>,
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
+26
-15
@@ -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<Action>);
|
||||
|
||||
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<Self, Self::Error> {
|
||||
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:*")]
|
||||
|
||||
@@ -10,3 +10,50 @@ pub struct PolicyDoc {
|
||||
pub create_date: Option<OffsetDateTime>,
|
||||
pub update_date: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
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<Vec<u8>> for PolicyDoc {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
match serde_json::from_slice::<PolicyDoc>(&value) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => match serde_json::from_slice::<Policy>(&value) {
|
||||
Ok(res2) => Ok(Self {
|
||||
policy: res2,
|
||||
..Default::default()
|
||||
}),
|
||||
Err(_) => Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Condition>,
|
||||
for_all_values: Vec<Condition>,
|
||||
@@ -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"
|
||||
|
||||
@@ -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<IpNetwork>);
|
||||
|
||||
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::<IpNetwork>()
|
||||
.map_err(|_| E::custom(format!("{v} can not be parsed to CIDR")))?)
|
||||
.map_err(|_| E::custom(format!("{v} can not be parsed to CIDR")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BinaryFuncValue>;
|
||||
|
||||
// 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<String, Vec<String>>) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ pub type BoolFunc = InnerFunc<BoolFuncValue>;
|
||||
impl BoolFunc {
|
||||
pub fn evaluate_bool(&self, values: &HashMap<String, Vec<String>>) -> 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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub type DateFunc = InnerFunc<DateFuncValue>;
|
||||
impl DateFunc {
|
||||
pub fn evaluate(&self, op: impl Fn(&OffsetDateTime, &OffsetDateTime) -> bool, values: &HashMap<String, Vec<String>>) -> 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 {
|
||||
|
||||
@@ -7,10 +7,10 @@ use serde::{
|
||||
|
||||
use super::key::Key;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct InnerFunc<T>(pub(crate) Vec<FuncKeyValue<T>>);
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct FuncKeyValue<T> {
|
||||
pub key: Key,
|
||||
pub values: T,
|
||||
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
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<Key> 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<Self, Self::Error> {
|
||||
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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -8,14 +8,13 @@ use serde::{
|
||||
|
||||
pub type NumberFunc = InnerFunc<NumberFuncValue>;
|
||||
|
||||
#[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<String, Vec<String>>) -> 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 {
|
||||
|
||||
@@ -63,7 +63,7 @@ impl FuncKeyValue<StringFuncValue> {
|
||||
.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<StringFuncValue> {
|
||||
.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<StringFuncValue> {
|
||||
}
|
||||
|
||||
/// 解析values字段
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
||||
pub struct StringFuncValue(pub Set<String>);
|
||||
|
||||
impl Serialize for StringFuncValue {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
+85
-10
@@ -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>) -> 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<Policy> {
|
||||
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",
|
||||
|
||||
@@ -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<Resource>);
|
||||
|
||||
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<Self, Self::Error> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-12
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde_json::Value;
|
||||
pub mod path;
|
||||
pub mod wildcard;
|
||||
|
||||
pub fn get_values_from_claims(claim: &HashMap<String, Value>, chaim_name: &str) -> (Vec<String>, bool) {
|
||||
pub fn _get_values_from_claims(claim: &HashMap<String, Value>, chaim_name: &str) -> (Vec<String>, 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<String, Value>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(dead_code)]
|
||||
pub fn is_simple_match<P, N>(pattern: P, name: N) -> bool
|
||||
where
|
||||
P: AsRef<str>,
|
||||
@@ -14,12 +15,13 @@ where
|
||||
inner_match(pattern, name, false)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_match_as_pattern_prefix<P, N>(pattern: P, text: N) -> bool
|
||||
where
|
||||
P: AsRef<str>,
|
||||
N: AsRef<str>,
|
||||
{
|
||||
let (mut p, mut t) = (pattern.as_ref().as_bytes().into_iter(), text.as_ref().as_bytes().into_iter());
|
||||
let (mut p, mut t) = (pattern.as_ref().as_bytes().iter(), text.as_ref().as_bytes().iter());
|
||||
|
||||
while let (Some(&x), Some(&y)) = (p.next(), t.next()) {
|
||||
if x == b'*' {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::Error;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum ServiceType {
|
||||
|
||||
+121
-34
@@ -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<Item>(&self, path: impl AsRef<str> + Send) -> crate::Result<(Item, ObjectInfo)>
|
||||
where
|
||||
Item: DeserializeOwned;
|
||||
async fn save_iam_config<Item: Serialize + Send>(&self, item: Item, path: impl AsRef<str> + Send) -> Result<()>;
|
||||
async fn load_iam_config<Item: DeserializeOwned>(&self, path: impl AsRef<str> + Send) -> Result<Item>;
|
||||
async fn delete_iam_config(&self, path: impl AsRef<str> + Send) -> Result<()>;
|
||||
|
||||
async fn save_iam_config<Item: Serialize + Send>(&self, item: Item, path: impl AsRef<str> + Send) -> crate::Result<()>;
|
||||
async fn delete_iam_config(&self, path: impl AsRef<str> + Send) -> crate::Result<()>;
|
||||
async fn save_user_identity(&self, name: &str, user_type: UserType, item: UserIdentity, ttl: Option<usize>) -> 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<UserIdentity>;
|
||||
|
||||
async fn load_all(&self, cache: &Cache) -> crate::Result<()>;
|
||||
async fn load_user(&self, name: &str, user_type: UserType, m: &mut HashMap<String, UserIdentity>) -> Result<()>;
|
||||
async fn load_users(&self, user_type: UserType, m: &mut HashMap<String, UserIdentity>) -> Result<()>;
|
||||
async fn load_secret_key(&self, name: &str, user_type: UserType) -> Result<String>;
|
||||
|
||||
fn get_default_policyes() -> HashMap<String, PolicyDoc> {
|
||||
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<String, GroupInfo>) -> Result<()>;
|
||||
async fn load_groups(&self, m: &mut HashMap<String, GroupInfo>) -> 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<PolicyDoc>;
|
||||
async fn load_policy_doc(&self, name: &str, m: &mut HashMap<String, PolicyDoc>) -> Result<()>;
|
||||
async fn load_policy_docs(&self, m: &mut HashMap<String, PolicyDoc>) -> Result<()>;
|
||||
|
||||
async fn save_mapped_policy(
|
||||
&self,
|
||||
name: &str,
|
||||
user_type: UserType,
|
||||
is_group: bool,
|
||||
item: MappedPolicy,
|
||||
ttl: Option<usize>,
|
||||
) -> 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<String, MappedPolicy>,
|
||||
) -> Result<()>;
|
||||
async fn load_mapped_policys(&self, user_type: UserType, is_group: bool, m: &mut HashMap<String, MappedPolicy>)
|
||||
-> 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<String> {
|
||||
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<HashMap<String, UserIdentity>>;
|
||||
|
||||
async fn load_policy_docs(&self) -> crate::Result<HashMap<String, PolicyDoc>>;
|
||||
async fn load_mapped_policy(&self, user_type: UserType, name: &str, is_group: bool) -> crate::Result<MappedPolicy>;
|
||||
pub fn policy_set(&self) -> HashSet<String> {
|
||||
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<String>,
|
||||
pub update_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl GroupInfo {
|
||||
pub fn new(members: Vec<String>) -> Self {
|
||||
Self {
|
||||
version: 1,
|
||||
status: "enabled".to_owned(),
|
||||
members,
|
||||
update_at: Some(OffsetDateTime::now_utc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+938
-282
File diff suppressed because it is too large
Load Diff
+756
@@ -0,0 +1,756 @@
|
||||
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::AddOrUpdateUserReq;
|
||||
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<T> {
|
||||
store: Arc<IamCache<T>>,
|
||||
roles_map: HashMap<ARN, String>,
|
||||
}
|
||||
|
||||
impl<T: Store> IamSys<T> {
|
||||
pub fn new(store: Arc<IamCache<T>>) -> 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<madmin::PolicyInfo> {
|
||||
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<HashMap<String, Policy>> {
|
||||
self.store.list_polices(bucket_name).await
|
||||
}
|
||||
|
||||
pub async fn list_policy_docs(&self, bucket_name: &str) -> Result<HashMap<String, PolicyDoc>> {
|
||||
self.store.list_policy_docs(bucket_name).await
|
||||
}
|
||||
|
||||
pub async fn set_policy(&self, name: &str, policy: Policy) -> Result<OffsetDateTime> {
|
||||
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<HashMap<String, madmin::UserInfo>> {
|
||||
self.store.get_bucket_users(bucket_name).await
|
||||
}
|
||||
|
||||
pub async fn list_users(&self) -> Result<HashMap<String, madmin::UserInfo>> {
|
||||
self.store.get_users().await
|
||||
}
|
||||
|
||||
pub async fn set_temp_user(&self, name: &str, cred: &Credentials, policy_name: Option<&str>) -> Result<OffsetDateTime> {
|
||||
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<madmin::UserInfo> {
|
||||
self.store.get_user_info(name).await
|
||||
}
|
||||
|
||||
pub async fn set_user_status(&self, name: &str, status: madmin::AccountStatus) -> Result<OffsetDateTime> {
|
||||
self.store.set_user_status(name, status).await
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn new_service_account(
|
||||
&self,
|
||||
parent_user: &str,
|
||||
groups: Option<Vec<String>>,
|
||||
opts: NewServiceAccountOpts,
|
||||
) -> Result<(Credentials, OffsetDateTime)> {
|
||||
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<String, Value> = HashMap::new();
|
||||
m.insert("parent".to_owned(), serde_json::Value::String(parent_user.to_owned()));
|
||||
|
||||
if !policy_buf.is_empty() {
|
||||
m.insert(SESSION_POLICY_NAME.to_owned(), serde_json::Value::String(base64_encode(&policy_buf)));
|
||||
m.insert(iam_policy_claim_name_sa(), serde_json::Value::String(EMBEDDED_POLICY_TYPE.to_owned()));
|
||||
} else {
|
||||
m.insert(iam_policy_claim_name_sa(), serde_json::Value::String(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)?;
|
||||
cred.parent_user = parent_user.to_owned();
|
||||
cred.groups = groups;
|
||||
cred.status = ACCOUNT_ON.to_owned();
|
||||
cred.name = opts.name;
|
||||
cred.description = opts.description;
|
||||
cred.expiration = opts.expiration;
|
||||
|
||||
let create_at = self.store.add_service_account(cred.clone()).await?;
|
||||
|
||||
Ok((cred, create_at))
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn update_service_account(&self, name: &str, opts: UpdateServiceAccountOpts) -> Result<OffsetDateTime> {
|
||||
self.store.update_service_account(name, opts).await
|
||||
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn list_service_accounts(&self, access_key: &str) -> Result<Vec<Credentials>> {
|
||||
self.store.list_service_accounts(access_key).await
|
||||
}
|
||||
|
||||
pub async fn list_tmep_accounts(&self, access_key: &str) -> Result<Vec<UserIdentity>> {
|
||||
self.store.list_temp_accounts(access_key).await
|
||||
}
|
||||
|
||||
pub async fn list_sts_accounts(&self, access_key: &str) -> Result<Vec<Credentials>> {
|
||||
self.store.list_sts_accounts(access_key).await
|
||||
}
|
||||
|
||||
pub async fn get_service_account(&self, access_key: &str) -> Result<(Credentials, Option<Policy>)> {
|
||||
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<Policy>)> {
|
||||
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<String, Value>)> {
|
||||
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<Policy>)> {
|
||||
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<Policy>)> {
|
||||
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<HashMap<String, Value>> {
|
||||
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, args: &AddOrUpdateUserReq) -> Result<OffsetDateTime> {
|
||||
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(&args.secret_key) {
|
||||
return Err(IamError::InvalidSecretKeyLength.into());
|
||||
}
|
||||
|
||||
self.store.add_user(access_key, args).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<UserIdentity>, 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<UserIdentity> {
|
||||
match self.check_key(access_key).await {
|
||||
Ok((u, _)) => u,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_users_to_group(&self, group: &str, users: Vec<String>) -> Result<OffsetDateTime> {
|
||||
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<String>) -> Result<OffsetDateTime> {
|
||||
self.store.remove_users_from_group(group, users).await
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn set_group_status(&self, group: &str, enable: bool) -> Result<OffsetDateTime> {
|
||||
self.store.set_group_status(group, enable).await
|
||||
// TODO: notification
|
||||
}
|
||||
pub async fn get_group_description(&self, group: &str) -> Result<GroupDesc> {
|
||||
self.store.get_group_description(group).await
|
||||
}
|
||||
|
||||
pub async fn list_groups(&self) -> Result<Vec<String>> {
|
||||
self.store.list_groups().await
|
||||
}
|
||||
|
||||
pub async fn policy_db_set(&self, name: &str, user_type: UserType, is_group: bool, policy: &str) -> Result<OffsetDateTime> {
|
||||
self.store.policy_db_set(name, user_type, is_group, policy).await
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub 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<Policy>,
|
||||
pub access_key: String,
|
||||
pub secret_key: String,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
pub allow_site_replicator_account: bool,
|
||||
pub claims: Option<HashMap<String, Value>>,
|
||||
}
|
||||
|
||||
pub struct UpdateServiceAccountOpts {
|
||||
pub session_policy: Option<Policy>,
|
||||
pub secret_key: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
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)
|
||||
}
|
||||
+4
-4
@@ -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<String> {
|
||||
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::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<String> {
|
||||
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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -603,7 +605,13 @@ struct ArgsBuilder {
|
||||
fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
|
||||
policy.is_allowed(&Args {
|
||||
account: &args.account,
|
||||
groups: &args.groups,
|
||||
groups: &{
|
||||
if args.groups.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(args.groups.clone())
|
||||
}
|
||||
},
|
||||
action: args.action.as_str().try_into().unwrap(),
|
||||
bucket: &args.bucket,
|
||||
conditions: &args.conditions,
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-1
@@ -1,5 +1,6 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -10,10 +11,20 @@ pub enum GroupStatus {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GroupAddRemove {
|
||||
group: String,
|
||||
pub group: String,
|
||||
pub members: Vec<String>,
|
||||
#[serde(rename = "groupStatus")]
|
||||
pub status: GroupStatus,
|
||||
#[serde(rename = "isRemove")]
|
||||
pub is_remove: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GroupDesc {
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
pub members: Vec<String>,
|
||||
pub policy: String,
|
||||
#[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none")]
|
||||
pub updated_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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<OffsetDateTime>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub update_date: Option<OffsetDateTime>,
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
+85
-2
@@ -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,
|
||||
@@ -129,7 +129,7 @@ pub struct AddServiceAccountReq {
|
||||
pub secret_key: String,
|
||||
|
||||
#[serde(rename = "name")]
|
||||
pub name: String,
|
||||
pub name: Option<String>,
|
||||
|
||||
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
@@ -137,3 +137,86 @@ pub struct AddServiceAccountReq {
|
||||
#[serde(rename = "expiration", skip_serializing_if = "Option::is_none")]
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl AddServiceAccountReq {
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
if self.access_key.is_empty() {
|
||||
return Err("accessKey is empty".to_string());
|
||||
}
|
||||
|
||||
if self.secret_key.is_empty() {
|
||||
return Err("secretKey is empty".to_string());
|
||||
}
|
||||
|
||||
if self.name.is_none() {
|
||||
return Err("name is empty".to_string());
|
||||
}
|
||||
|
||||
// TODO: validate
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Credentials<'a> {
|
||||
pub access_key: &'a str,
|
||||
pub secret_key: &'a str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub session_token: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AddServiceAccountResp<'a> {
|
||||
pub credentials: Credentials<'a>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InfoServiceAccountResp {
|
||||
pub parent_user: String,
|
||||
pub account_status: String,
|
||||
pub implied_policy: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub policy: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdateServiceAccountReq {
|
||||
#[serde(rename = "newPolicy", skip_serializing_if = "Option::is_none")]
|
||||
pub new_policy: Option<String>,
|
||||
|
||||
#[serde(rename = "newSecretKey", skip_serializing_if = "Option::is_none")]
|
||||
pub new_secret_key: Option<String>,
|
||||
|
||||
#[serde(rename = "newStatus", skip_serializing_if = "Option::is_none")]
|
||||
pub new_status: Option<String>,
|
||||
|
||||
#[serde(rename = "newName", skip_serializing_if = "Option::is_none")]
|
||||
pub new_name: Option<String>,
|
||||
|
||||
#[serde(rename = "newDescription", skip_serializing_if = "Option::is_none")]
|
||||
pub new_description: Option<String>,
|
||||
|
||||
#[serde(rename = "newExpiration", skip_serializing_if = "Option::is_none")]
|
||||
pub new_expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl UpdateServiceAccountReq {
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
// TODO: validate
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+123
-104
@@ -1,6 +1,7 @@
|
||||
use super::router::Operation;
|
||||
use crate::storage::error::to_s3_error;
|
||||
use bytes::Bytes;
|
||||
use const_str::from_utf8;
|
||||
use ecstore::admin_server_info::get_server_info;
|
||||
use ecstore::bucket::policy::action::{Action, ActionSet};
|
||||
use ecstore::bucket::policy::bucket_policy::{BPStatement, BucketPolicy};
|
||||
@@ -16,13 +17,17 @@ use ecstore::new_object_layer_fn;
|
||||
use ecstore::peer::is_reserved_or_invalid_bucket;
|
||||
use ecstore::store::is_valid_object_prefix;
|
||||
use ecstore::store_api::StorageAPI;
|
||||
use ecstore::utils::crypto::base64_encode;
|
||||
use ecstore::utils::path::path_join;
|
||||
use ecstore::utils::xml;
|
||||
use ecstore::GLOBAL_Endpoints;
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::{HeaderMap, Uri};
|
||||
use hyper::StatusCode;
|
||||
use iam::auth::{create_new_credentials_with_metadata, get_claims_from_token_with_secret};
|
||||
use iam::auth::{get_claims_from_token_with_secret, get_new_credentials_with_metadata};
|
||||
use iam::error::Error as IamError;
|
||||
use iam::policy::Policy;
|
||||
use iam::sys::SESSION_POLICY_NAME;
|
||||
use iam::{auth, get_global_action_cred};
|
||||
use madmin::metrics::RealtimeMetrics;
|
||||
use madmin::utils::parse_duration;
|
||||
@@ -35,6 +40,7 @@ use s3s::{
|
||||
};
|
||||
use s3s::{S3ErrorCode, StdError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
@@ -69,49 +75,6 @@ pub struct AssumeRoleRequest {
|
||||
pub external_id: String,
|
||||
}
|
||||
|
||||
// #[derive(Debug, Serialize, Default)]
|
||||
// #[serde(rename_all = "PascalCase", default)]
|
||||
// pub struct AssumeRoleResponse {
|
||||
// #[serde(rename = "AssumeRoleResult")]
|
||||
// pub result: AssumeRoleResult,
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Serialize, Default)]
|
||||
// #[serde(rename_all = "PascalCase", default)]
|
||||
// pub struct AssumeRoleResult {
|
||||
// pub credentials: Credentials,
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Serialize, Default)]
|
||||
// #[serde(rename_all = "PascalCase", default)]
|
||||
// pub struct Credentials {
|
||||
// #[serde(rename = "AccessKeyId")]
|
||||
// pub access_key: String,
|
||||
// #[serde(rename = "SecretAccessKey")]
|
||||
// pub secret_key: String,
|
||||
// pub status: String,
|
||||
// pub expiration: usize,
|
||||
// pub session_token: String,
|
||||
// pub parent_user: String,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct STSClaims {
|
||||
pub parent: String,
|
||||
pub exp: usize,
|
||||
pub access_key: String,
|
||||
}
|
||||
|
||||
impl STSClaims {
|
||||
pub fn to_map(&self) -> HashMap<String, String> {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("parent".to_string(), self.parent.clone());
|
||||
m.insert("exp".to_string(), self.exp.to_string());
|
||||
m.insert("access_key".to_string(), self.access_key.clone());
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
fn get_token_signing_key() -> Option<String> {
|
||||
if let Some(s) = get_global_action_cred() {
|
||||
Some(s.secret_key.clone())
|
||||
@@ -120,51 +83,65 @@ fn get_token_signing_key() -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_key_valid(token: Option<String>, ak: &str) -> S3Result<(auth::Credentials, bool)> {
|
||||
// check_key_valid get auth.cred
|
||||
pub async fn check_key_valid(security_token: Option<String>, 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) => {
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?;
|
||||
|
||||
if !ok {
|
||||
if let Some(u) = u {
|
||||
if u.credentials.status == "off" {
|
||||
return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled"));
|
||||
}
|
||||
}
|
||||
|
||||
return Err(s3_error!(InvalidRequest, "check key failed"));
|
||||
}
|
||||
_ => {
|
||||
return Err(s3_error!(InvalidRequest, "check key failed"));
|
||||
}
|
||||
return Err(s3_error!(InvalidRequest, "check key failed"));
|
||||
}
|
||||
|
||||
let Some(u) = u else {
|
||||
return Err(s3_error!(InvalidRequest, "check key failed"));
|
||||
};
|
||||
|
||||
cred = u.credentials;
|
||||
}
|
||||
|
||||
let claims = check_claims_from_token(&security_token.unwrap_or_default(), &cred)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed {}", e)))?;
|
||||
|
||||
cred.claims = if !claims.is_empty() { Some(claims) } else { None };
|
||||
|
||||
let mut owner = sys_cred.access_key == cred.access_key || cred.parent_user == sys_cred.access_key;
|
||||
|
||||
// permitRootAccess
|
||||
if let Some(claims) = &cred.claims {
|
||||
if claims.contains_key(SESSION_POLICY_NAME) {
|
||||
owner = false
|
||||
}
|
||||
}
|
||||
|
||||
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<STSClaims> {
|
||||
pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Result<HashMap<String, Value>> {
|
||||
if !token.is_empty() && cred.access_key.is_empty() {
|
||||
return Err(s3_error!(InvalidRequest, "no access key"));
|
||||
}
|
||||
@@ -191,6 +168,8 @@ pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Resul
|
||||
|
||||
let mut secret = sys_cred.secret_key;
|
||||
|
||||
// TODO: REPLICATION
|
||||
|
||||
let mut token = token;
|
||||
|
||||
if cred.is_service_account() {
|
||||
@@ -199,12 +178,12 @@ pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Resul
|
||||
}
|
||||
|
||||
if !token.is_empty() {
|
||||
let claims: STSClaims =
|
||||
let claims: HashMap<String, Value> =
|
||||
get_claims_from_token_with_secret(token, &secret).map_err(|_e| s3_error!(InvalidRequest, "invalid token"))?;
|
||||
return Ok(claims);
|
||||
}
|
||||
|
||||
Ok(STSClaims::default())
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn get_session_token(hds: &HeaderMap) -> Option<String> {
|
||||
@@ -212,6 +191,27 @@ pub fn get_session_token(hds: &HeaderMap) -> Option<String> {
|
||||
.map(|v| v.to_str().unwrap_or_default().to_string())
|
||||
}
|
||||
|
||||
pub fn populate_session_policy(claims: &mut HashMap<String, Value>, policy: &str) -> S3Result<()> {
|
||||
if !policy.is_empty() {
|
||||
let session_policy = Policy::parse_config(policy.as_bytes())
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("parse policy err {}", e)))?;
|
||||
if session_policy.version.is_empty() {
|
||||
return Err(s3_error!(InvalidRequest, "invalid policy"));
|
||||
}
|
||||
|
||||
let policy_buf = serde_json::to_vec(&session_policy)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal policy err {}", e)))?;
|
||||
|
||||
if policy_buf.len() > 2048 {
|
||||
return Err(s3_error!(InvalidRequest, "policy too large"));
|
||||
}
|
||||
|
||||
claims.insert(SESSION_POLICY_NAME.to_string(), serde_json::Value::String(base64_encode(&policy_buf)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct AssumeRoleHandle {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for AssumeRoleHandle {
|
||||
@@ -220,13 +220,28 @@ impl Operation for AssumeRoleHandle {
|
||||
|
||||
let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
||||
|
||||
// TODO: 判断权限, 不允许sts访问
|
||||
let session_token = get_session_token(&req.headers);
|
||||
if session_token.is_some() {
|
||||
return Err(s3_error!(InvalidRequest, "AccessDenied1"));
|
||||
}
|
||||
|
||||
let (cred, _owner) = check_key_valid(session_token, &user.access_key).await?;
|
||||
|
||||
// // TODO: 判断权限, 不允许sts访问
|
||||
if cred.is_temp() || cred.is_service_account() {
|
||||
return Err(s3_error!(InvalidRequest, "AccessDenied"));
|
||||
}
|
||||
|
||||
let mut input = req.input;
|
||||
|
||||
let Some(bytes) = input.take_bytes() else {
|
||||
return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
let bytes = 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: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
|
||||
|
||||
if body.action.as_str() != ASSUME_ROLE_ACTION {
|
||||
@@ -237,18 +252,9 @@ impl Operation for AssumeRoleHandle {
|
||||
return Err(s3_error!(InvalidArgument, "not suport version"));
|
||||
}
|
||||
|
||||
warn!("AssumeRole get cred {:?}", &user);
|
||||
warn!("AssumeRole get body {:?}", &body);
|
||||
let mut claims = cred.claims.unwrap_or_default();
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
let Some(secret) = get_token_signing_key() else {
|
||||
return Err(s3_error!(InvalidArgument, "sk not init"));
|
||||
};
|
||||
populate_session_policy(&mut claims, &body.policy)?;
|
||||
|
||||
let exp = {
|
||||
if body.duration_seconds > 0 {
|
||||
@@ -258,38 +264,51 @@ impl Operation for AssumeRoleHandle {
|
||||
}
|
||||
};
|
||||
|
||||
let mut claims = STSClaims {
|
||||
parent: user.access_key.clone(),
|
||||
exp,
|
||||
..Default::default()
|
||||
claims.insert(
|
||||
"exp".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(OffsetDateTime::now_utc().unix_timestamp() + exp as i64)),
|
||||
);
|
||||
|
||||
claims.insert("parent".to_string(), serde_json::Value::String(cred.access_key.clone()));
|
||||
|
||||
// warn!("AssumeRole get cred {:?}", &user);
|
||||
// warn!("AssumeRole get body {:?}", &body);
|
||||
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
|
||||
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
|
||||
}
|
||||
|
||||
let Some(secret) = get_token_signing_key() else {
|
||||
return Err(s3_error!(InvalidArgument, "global active sk not init"));
|
||||
};
|
||||
|
||||
let ak = iam::utils::gen_access_key(20).unwrap_or_default();
|
||||
let sk = iam::utils::gen_secret_key(32).unwrap_or_default();
|
||||
info!("AssumeRole get claims {:?}", &claims);
|
||||
|
||||
claims.access_key = ak.clone();
|
||||
let mut new_cred = get_new_credentials_with_metadata(&claims, &secret)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get new cred failed {}", e)))?;
|
||||
|
||||
let mut cred = match create_new_credentials_with_metadata(&ak, &sk, &claims, &secret, Some(exp)) {
|
||||
Ok(res) => res,
|
||||
Err(_er) => return Err(s3_error!(InvalidRequest, "")),
|
||||
};
|
||||
new_cred.parent_user = cred.access_key.clone();
|
||||
|
||||
cred.parent_user = user.access_key.clone();
|
||||
info!("AssumeRole get new_cred {:?}", &new_cred);
|
||||
|
||||
if let Err(err) = iam_store.set_temp_user(&cred.access_key, &cred, "").await {
|
||||
error!("set_temp_user err {:?}", err);
|
||||
if let Err(_err) = iam_store.set_temp_user(&new_cred.access_key, &new_cred, None).await {
|
||||
return Err(s3_error!(InternalError, "set_temp_user failed"));
|
||||
}
|
||||
|
||||
// TODO: globalSiteReplicationSys
|
||||
|
||||
let resp = AssumeRoleOutput {
|
||||
credentials: Some(Credentials {
|
||||
access_key_id: cred.access_key,
|
||||
access_key_id: new_cred.access_key,
|
||||
expiration: Timestamp::from(
|
||||
cred.expiration
|
||||
new_cred
|
||||
.expiration
|
||||
.unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))),
|
||||
),
|
||||
secret_access_key: cred.secret_key,
|
||||
session_token: cred.session_token,
|
||||
secret_access_key: new_cred.secret_key,
|
||||
session_token: new_cred.session_token,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
use http::StatusCode;
|
||||
use iam::{error::is_err_no_such_user, get_global_action_cred};
|
||||
use http::{HeaderMap, StatusCode};
|
||||
use iam::{
|
||||
error::{is_err_no_such_group, is_err_no_such_user},
|
||||
get_global_action_cred,
|
||||
};
|
||||
use madmin::GroupAddRemove;
|
||||
use matchit::Params;
|
||||
use s3s::{s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::admin::router::Operation;
|
||||
use crate::admin::{router::Operation, utils::has_space_be};
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct GroupQuery {
|
||||
pub group: String,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ListGroups {}
|
||||
#[async_trait::async_trait]
|
||||
@@ -13,17 +24,101 @@ impl Operation for ListGroups {
|
||||
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListGroups");
|
||||
|
||||
Err(s3_error!(NotImplemented))
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
|
||||
|
||||
let groups = iam_store.list_groups().await.map_err(|e| {
|
||||
warn!("list groups failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
|
||||
let body = serde_json::to_vec(&groups).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Group {}
|
||||
pub struct GetGroup {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for Group {
|
||||
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle Group");
|
||||
impl Operation for GetGroup {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle GetGroup");
|
||||
|
||||
Err(s3_error!(NotImplemented))
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: GroupQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
input
|
||||
} else {
|
||||
GroupQuery::default()
|
||||
}
|
||||
};
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
|
||||
|
||||
let g = iam_store.get_group_description(&query.group).await.map_err(|e| {
|
||||
warn!("get group failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
|
||||
let body = serde_json::to_vec(&g).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetGroupStatus {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for SetGroupStatus {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle SetGroupStatus");
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: GroupQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
input
|
||||
} else {
|
||||
GroupQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
if query.group.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "group is required"));
|
||||
}
|
||||
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
|
||||
|
||||
if let Some(status) = query.status {
|
||||
match status.as_str() {
|
||||
"enabled" => {
|
||||
iam_store.set_group_status(&query.group, true).await.map_err(|e| {
|
||||
warn!("enable group failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
}
|
||||
"disabled" => {
|
||||
iam_store.set_group_status(&query.group, false).await.map_err(|e| {
|
||||
warn!("enable group failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
}
|
||||
_ => {
|
||||
return Err(s3_error!(InvalidArgument, "invalid status"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(s3_error!(InvalidArgument, "status is required"));
|
||||
}
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,20 +178,31 @@ impl Operation for UpdateGroupMembers {
|
||||
|
||||
if args.is_remove {
|
||||
warn!("remove group members");
|
||||
iam_store
|
||||
.remove_users_from_group(&args.group, args.members)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("remove group members failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
} else {
|
||||
warn!("add group members");
|
||||
|
||||
if let Err(err) = iam_store.get_group_description(&args.group).await {
|
||||
if is_err_no_such_group(&err) && has_space_be(&args.group) {
|
||||
return Err(s3_error!(InvalidArgument, "not such group"));
|
||||
}
|
||||
}
|
||||
|
||||
iam_store.add_users_to_group(&args.group, args.members).await.map_err(|e| {
|
||||
warn!("add group members failed, e: {:?}", e);
|
||||
S3Error::with_message(S3ErrorCode::InternalError, e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
Err(s3_error!(NotImplemented))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetGroupStatus {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for SetGroupStatus {
|
||||
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle SetGroupStatus");
|
||||
|
||||
Err(s3_error!(NotImplemented))
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::admin::{handlers::check_key_valid, utils::has_space_be};
|
||||
use crate::admin::{handlers::get_session_token, router::Operation};
|
||||
use http::HeaderMap;
|
||||
use hyper::StatusCode;
|
||||
use iam::{
|
||||
auth::CredentialsBuilder,
|
||||
policy::{
|
||||
action::{Action, AdminAction::ListServiceAccountsAdminAction},
|
||||
Args,
|
||||
},
|
||||
error::is_err_no_such_service_account,
|
||||
get_global_action_cred,
|
||||
policy::Policy,
|
||||
sys::{NewServiceAccountOpts, UpdateServiceAccountOpts},
|
||||
};
|
||||
use madmin::{
|
||||
AddServiceAccountReq, AddServiceAccountResp, Credentials, InfoServiceAccountResp, ListServiceAccountsResp,
|
||||
ServiceAccountInfo, UpdateServiceAccountReq,
|
||||
};
|
||||
use madmin::{AddServiceAccountReq, ListServiceAccountsResp, ServiceAccountInfo};
|
||||
use matchit::Params;
|
||||
use s3s::S3ErrorCode::InvalidRequest;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use std::collections::HashMap;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::admin::router::Operation;
|
||||
use crate::admin::{
|
||||
handlers::check_key_valid,
|
||||
models::service_account::{AddServiceAccountResp, Credentials, InfoServiceAccountResp},
|
||||
};
|
||||
|
||||
pub struct AddServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for AddServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle AddServiceAccount ");
|
||||
|
||||
let Some(input_cred) = req.credentials else {
|
||||
let Some(req_cred) = req.credentials else {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
let _is_owner = true; // 先按true处理,后期根据请求决定。
|
||||
|
||||
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?;
|
||||
|
||||
let mut input = req.input;
|
||||
let body = match input.store_all_unlimited().await {
|
||||
@@ -43,98 +42,205 @@ impl Operation for AddServiceAccount {
|
||||
|
||||
let mut create_req: AddServiceAccountReq =
|
||||
serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
|
||||
|
||||
create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
|
||||
|
||||
if create_req.access_key.trim().len() != create_req.access_key.len() {
|
||||
if has_space_be(&create_req.access_key) {
|
||||
return Err(s3_error!(InvalidRequest, "access key has spaces"));
|
||||
}
|
||||
|
||||
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")
|
||||
})?;
|
||||
create_req
|
||||
.validate()
|
||||
.map_err(|e| S3Error::with_message(InvalidRequest, e.to_string()))?;
|
||||
|
||||
// TODO check create_req validity
|
||||
let Some(sys_cred) = get_global_action_cred() else {
|
||||
return Err(s3_error!(InvalidRequest, "get sys cred failed"));
|
||||
};
|
||||
|
||||
// 校验合法性, Name, Expiration, Description
|
||||
let target_user = if let Some(u) = create_req.target_user {
|
||||
if sys_cred.access_key == create_req.access_key {
|
||||
return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
|
||||
}
|
||||
|
||||
let mut target_user = if let Some(u) = create_req.target_user {
|
||||
u
|
||||
} else {
|
||||
cred.access_key
|
||||
cred.access_key.clone()
|
||||
};
|
||||
let _deny_only = true;
|
||||
|
||||
// todo 校验权限
|
||||
let req_user = cred.access_key.clone();
|
||||
let mut req_parent_user = cred.access_key.clone();
|
||||
let req_groups = cred.groups.clone();
|
||||
let mut req_is_derived_cred = false;
|
||||
|
||||
// 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 cred.is_owner() || cred.is_service_account() {
|
||||
req_parent_user = cred.parent_user.clone();
|
||||
req_is_derived_cred = true;
|
||||
}
|
||||
|
||||
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,
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
if target_user != cred.access_key {
|
||||
let has_user = iam_store.get_user(&target_user).await;
|
||||
if has_user.is_none() && target_user != sys_cred.access_key {
|
||||
return Err(s3_error!(InvalidRequest, "target user not exist"));
|
||||
}
|
||||
}
|
||||
|
||||
let is_svc_acc = target_user == req_user || target_user == req_parent_user;
|
||||
|
||||
let mut taget_groups = None;
|
||||
let mut opts = NewServiceAccountOpts {
|
||||
access_key: create_req.access_key,
|
||||
secret_key: create_req.secret_key,
|
||||
name: create_req.name,
|
||||
description: create_req.description,
|
||||
expiration: create_req.expiration,
|
||||
session_policy: create_req.policy.and_then(|p| Policy::parse_config(p.as_bytes()).ok()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if is_svc_acc {
|
||||
if req_is_derived_cred {
|
||||
if req_parent_user.is_empty() {
|
||||
return Err(s3_error!(AccessDenied, "only derived cred can create service account"));
|
||||
}
|
||||
})
|
||||
.name(create_req.name)
|
||||
.try_build()
|
||||
.map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?;
|
||||
target_user = req_parent_user;
|
||||
}
|
||||
|
||||
let resp = serde_json::to_vec(&AddServiceAccountResp {
|
||||
taget_groups = req_groups;
|
||||
|
||||
if let Some(claims) = cred.claims {
|
||||
if opts.claims.is_none() {
|
||||
opts.claims = Some(HashMap::new());
|
||||
}
|
||||
|
||||
for (k, v) in claims.iter() {
|
||||
if claims.contains_key("exp") {
|
||||
continue;
|
||||
}
|
||||
|
||||
opts.claims.as_mut().unwrap().insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (new_cred, _) = iam_store
|
||||
.new_service_account(&target_user, taget_groups, opts)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!("create service account failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "create service account failed")
|
||||
})?;
|
||||
|
||||
let resp = AddServiceAccountResp {
|
||||
credentials: Credentials {
|
||||
access_key: &cred.access_key,
|
||||
secret_key: &cred.secret_key,
|
||||
access_key: &new_cred.access_key,
|
||||
secret_key: &new_cred.secret_key,
|
||||
session_token: None,
|
||||
expiration: cred.expiration,
|
||||
expiration: new_cred.expiration,
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
.into();
|
||||
};
|
||||
|
||||
iam::add_service_account(cred).await.map_err(|e| {
|
||||
debug!("add cred failed: {e:?}");
|
||||
s3_error!(InternalError, "add cred failed")
|
||||
})?;
|
||||
let body = serde_json::to_vec(&resp).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, resp)))
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct AccessKeyQuery {
|
||||
#[serde(rename = "accessKey")]
|
||||
pub access_key: String,
|
||||
}
|
||||
|
||||
pub struct UpdateServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for UpdateServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle UpdateServiceAccount");
|
||||
|
||||
let Some(_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
||||
// let Some(req_cred) = req.credentials else {
|
||||
// return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// };
|
||||
|
||||
// return Err(s3_error!(NotImplemented));
|
||||
//
|
||||
// let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?;
|
||||
|
||||
todo!()
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AccessKeyQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
input
|
||||
} else {
|
||||
AccessKeyQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
if query.access_key.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
let access_key = query.access_key;
|
||||
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// let svc_account = iam_store.get_service_account(&access_key).await.map_err(|e| {
|
||||
// debug!("get service account failed, e: {:?}", e);
|
||||
// s3_error!(InternalError, "get service account 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 update_req: UpdateServiceAccountReq =
|
||||
serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
|
||||
|
||||
update_req
|
||||
.validate()
|
||||
.map_err(|e| S3Error::with_message(InvalidRequest, e.to_string()))?;
|
||||
// TODO: is_allowed
|
||||
let sp = {
|
||||
if let Some(policy) = update_req.new_policy {
|
||||
let sp = Policy::parse_config(policy.as_bytes()).map_err(|e| {
|
||||
debug!("parse policy failed, e: {:?}", e);
|
||||
s3_error!(InvalidArgument, "parse policy failed")
|
||||
})?;
|
||||
|
||||
if sp.version.is_empty() && sp.statements.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(sp)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let opts = UpdateServiceAccountOpts {
|
||||
secret_key: update_req.new_secret_key,
|
||||
status: update_req.new_status,
|
||||
name: update_req.new_name,
|
||||
description: update_req.new_description,
|
||||
expiration: update_req.new_expiration,
|
||||
session_policy: sp,
|
||||
};
|
||||
|
||||
let _ = iam_store.update_service_account(&access_key, opts).await.map_err(|e| {
|
||||
debug!("update service account failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "update service account failed")
|
||||
})?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,82 +250,80 @@ impl Operation for InfoServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle InfoServiceAccount");
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
if key != "accessKey" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(value) = x.next() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
return Some(value);
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AccessKeyQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
input
|
||||
} else {
|
||||
AccessKeyQuery::default()
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
if query.access_key.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
// let implied_policy = sp.version.is_empty() && sp.statements.is_empty();
|
||||
// let sva = if implied_policy {
|
||||
// sp
|
||||
// } else {
|
||||
// // 这里使用
|
||||
// todo!();
|
||||
// };
|
||||
let access_key = query.access_key;
|
||||
|
||||
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"))?;
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
Ok(S3Response::new((
|
||||
StatusCode::OK,
|
||||
crypto::encrypt_data(cred.access_key.as_bytes(), &body[..])
|
||||
.map_err(|_| s3_error!(InternalError, "encrypt data failed"))?
|
||||
.into(),
|
||||
)))
|
||||
let (svc_account, session_policy) = iam_store.get_service_account(&access_key).await.map_err(|e| {
|
||||
debug!("get service account failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "get service account failed")
|
||||
})?;
|
||||
|
||||
// TODO: is_allowed
|
||||
|
||||
let implied_policy = if let Some(policy) = session_policy.as_ref() {
|
||||
policy.version.is_empty() && policy.statements.is_empty()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let svc_account_policy = {
|
||||
if !implied_policy {
|
||||
session_policy
|
||||
} else {
|
||||
let policies = iam_store
|
||||
.policy_db_get(&svc_account.parent_user, &svc_account.groups)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!("get service account policy failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "get service account policy failed")
|
||||
})?;
|
||||
|
||||
Some(iam_store.get_combined_policy(&policies).await)
|
||||
}
|
||||
};
|
||||
|
||||
let policy = {
|
||||
if let Some(policy) = svc_account_policy {
|
||||
Some(serde_json::to_string(&policy).map_err(|e| {
|
||||
debug!("marshal policy failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "marshal policy failed")
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let resp = InfoServiceAccountResp {
|
||||
parent_user: svc_account.parent_user,
|
||||
account_status: svc_account.status,
|
||||
implied_policy,
|
||||
name: svc_account.name,
|
||||
description: svc_account.description,
|
||||
expiration: svc_account.expiration,
|
||||
policy,
|
||||
};
|
||||
|
||||
let body = serde_json::to_vec(&resp).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +337,7 @@ pub struct ListServiceAccount {}
|
||||
impl Operation for ListServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListServiceAccount");
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: ListServiceAccountQuery =
|
||||
@@ -243,26 +348,34 @@ impl Operation for ListServiceAccount {
|
||||
}
|
||||
};
|
||||
|
||||
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 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| {
|
||||
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!("check key failed: {e:?}");
|
||||
s3_error!(InternalError, "check key failed")
|
||||
})?;
|
||||
|
||||
if cred.parent_user.is_empty() {
|
||||
let target_account = if let Some(user) = query.user {
|
||||
if user != input_cred.access_key {
|
||||
user
|
||||
} else if cred.parent_user.is_empty() {
|
||||
input_cred.access_key
|
||||
} else {
|
||||
cred.parent_user
|
||||
}
|
||||
} else 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| {
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
let service_accounts = iam_store.list_service_accounts(&target_account).await.map_err(|e| {
|
||||
debug!("list service account failed: {e:?}");
|
||||
s3_error!(InternalError, "list service account failed")
|
||||
})?;
|
||||
@@ -270,9 +383,9 @@ impl Operation for ListServiceAccount {
|
||||
let accounts: Vec<ServiceAccountInfo> = 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
|
||||
parent_user: sa.parent_user.clone(),
|
||||
account_status: sa.status.clone(),
|
||||
implied_policy: sa.is_implied_policy(), // or set according to your logic
|
||||
access_key: sa.access_key,
|
||||
name: sa.name,
|
||||
description: sa.description,
|
||||
@@ -293,15 +406,56 @@ impl Operation for ListServiceAccount {
|
||||
pub struct DeleteServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for DeleteServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle DeleteServiceAccount");
|
||||
|
||||
let Some(_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
||||
|
||||
let Some(_service_account) = params.get("accessKey") else {
|
||||
return Err(s3_error!(InvalidRequest, "Invalid arguments specified."));
|
||||
let Some(input_cred) = req.credentials else {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
todo!()
|
||||
let (_cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!("check key failed: {e:?}");
|
||||
s3_error!(InternalError, "check key failed")
|
||||
})?;
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AccessKeyQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
AccessKeyQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
if query.access_key.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
let _svc_account = match iam_store.get_service_account(&query.access_key).await {
|
||||
Ok((res, _)) => Some(res),
|
||||
Err(err) => {
|
||||
if is_err_no_such_service_account(&err) {
|
||||
return Err(s3_error!(InvalidRequest, "service account not exist"));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: is_allowed
|
||||
|
||||
iam_store.delete_service_account(&query.access_key).await.map_err(|e| {
|
||||
debug!("delete service account failed, e: {:?}", e);
|
||||
s3_error!(InternalError, "delete service account failed")
|
||||
})?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::from_utf8;
|
||||
|
||||
use http::{HeaderMap, StatusCode};
|
||||
use iam::get_global_action_cred;
|
||||
use madmin::{AccountStatus, AddOrUpdateUserReq};
|
||||
@@ -10,6 +12,7 @@ use tracing::warn;
|
||||
use crate::admin::{
|
||||
handlers::{check_key_valid, get_session_token},
|
||||
router::Operation,
|
||||
utils::has_space_be,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
@@ -23,7 +26,6 @@ pub struct AddUser {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for AddUser {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle AddUser");
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
@@ -38,6 +40,8 @@ impl Operation for AddUser {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key).await?;
|
||||
|
||||
let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
|
||||
if ak.is_empty() {
|
||||
@@ -71,24 +75,30 @@ impl Operation for AddUser {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
if let Some(user) = iam_store.get_user(ak).await {
|
||||
if (user.credentials.is_temp() || user.credentials.is_service_account()) && cred.parent_user == ak {
|
||||
return Err(s3_error!(InvalidArgument, "can't create user with service account access key"));
|
||||
}
|
||||
} else if has_space_be(ak) {
|
||||
return Err(s3_error!(InvalidArgument, "access key has space"));
|
||||
}
|
||||
|
||||
let token = get_session_token(&req.headers);
|
||||
|
||||
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 from_utf8(ak.as_bytes()).is_err() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is not utf8"));
|
||||
}
|
||||
|
||||
iam::create_user(ak, &args.secret_key, "enabled")
|
||||
// let check_deny_only = if ak == cred.access_key {
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// };
|
||||
|
||||
// TODO: is_allowed
|
||||
|
||||
iam_store
|
||||
.create_user(ak, &args)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?;
|
||||
|
||||
@@ -132,7 +142,10 @@ impl Operation for SetUserStatus {
|
||||
let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
|
||||
|
||||
iam::set_user_status(ak, status)
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
iam_store
|
||||
.set_user_status(ak, status)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?;
|
||||
|
||||
@@ -143,14 +156,42 @@ impl Operation for SetUserStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct BucketQuery {
|
||||
#[serde(rename = "bucket")]
|
||||
pub bucket: String,
|
||||
}
|
||||
pub struct ListUsers {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for ListUsers {
|
||||
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListUsers");
|
||||
let users = iam::list_users()
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: BucketQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
BucketQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
let users = {
|
||||
if !query.bucket.is_empty() {
|
||||
iam_store
|
||||
.list_bucket_users(query.bucket.as_str())
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?
|
||||
} else {
|
||||
iam_store
|
||||
.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)))?;
|
||||
@@ -174,6 +215,7 @@ pub struct RemoveUser {}
|
||||
impl Operation for RemoveUser {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle RemoveUser");
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
@@ -190,7 +232,10 @@ impl Operation for RemoveUser {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
let (is_temp, _) = iam::is_temp_user(ak)
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
let (is_temp, _) = iam_store
|
||||
.is_temp_user(ak)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?;
|
||||
|
||||
@@ -198,7 +243,17 @@ impl Operation for RemoveUser {
|
||||
return Err(s3_error!(InvalidArgument, "can't remove temp user"));
|
||||
}
|
||||
|
||||
iam::delete_user(ak, true)
|
||||
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), ak).await?;
|
||||
|
||||
let sys_cred = get_global_action_cred()
|
||||
.ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?;
|
||||
|
||||
if ak == sys_cred.access_key || ak == cred.access_key {
|
||||
return Err(s3_error!(InvalidArgument, "can't remove self"));
|
||||
}
|
||||
|
||||
iam_store
|
||||
.delete_user(ak, true)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?;
|
||||
|
||||
@@ -214,6 +269,7 @@ pub struct GetUserInfo {}
|
||||
impl Operation for GetUserInfo {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle GetUserInfo");
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
@@ -230,7 +286,10 @@ impl Operation for GetUserInfo {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
let info = iam::get_user_info(ak)
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
let info = iam_store
|
||||
.get_user_info(ak)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod handlers;
|
||||
pub mod models;
|
||||
pub mod router;
|
||||
pub mod utils;
|
||||
|
||||
use common::error::Result;
|
||||
// use ecstore::global::{is_dist_erasure, is_erasure};
|
||||
@@ -171,7 +171,7 @@ fn regist_user_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/group").as_str(),
|
||||
AdminOperation(&group::Group {}),
|
||||
AdminOperation(&group::GetGroup {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
@@ -192,7 +192,7 @@ fn regist_user_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/update-service-account").as_str(),
|
||||
AdminOperation(&UpdateServiceAccount {}),
|
||||
)?;
|
||||
|
||||
// 1
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/info-service-account").as_str(),
|
||||
@@ -205,13 +205,13 @@ fn regist_user_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/list-service-accounts").as_str(),
|
||||
AdminOperation(&ListServiceAccount {}),
|
||||
)?;
|
||||
|
||||
// 1
|
||||
r.insert(
|
||||
Method::DELETE,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/delete-service-accounts").as_str(),
|
||||
AdminOperation(&DeleteServiceAccount {}),
|
||||
)?;
|
||||
|
||||
// 1
|
||||
r.insert(
|
||||
Method::PUT,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/add-service-accounts").as_str(),
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod service_account;
|
||||
@@ -1,51 +0,0 @@
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// #[derive(Deserialize, Default)]
|
||||
// #[serde(rename_all = "camelCase", default)]
|
||||
// pub struct AddServiceAccountReq {
|
||||
// pub access_key: String,
|
||||
// pub secret_key: String,
|
||||
|
||||
// pub policy: Option<Vec<u8>>,
|
||||
// pub target_user: Option<String>,
|
||||
// pub name: String,
|
||||
// pub description: String,
|
||||
// #[serde(with = "time::serde::rfc3339::option")]
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// pub expiration: Option<OffsetDateTime>,
|
||||
// }
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Credentials<'a> {
|
||||
pub access_key: &'a str,
|
||||
pub secret_key: &'a str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub session_token: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AddServiceAccountResp<'a> {
|
||||
pub credentials: Credentials<'a>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InfoServiceAccountResp {
|
||||
pub parent_user: String,
|
||||
pub account_status: String,
|
||||
pub implied_policy: bool,
|
||||
pub policy: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn has_space_be(s: &str) -> bool {
|
||||
s.trim().len() != s.len()
|
||||
}
|
||||
+1
-6
@@ -1,4 +1,3 @@
|
||||
use iam::cache::CacheInner;
|
||||
use log::warn;
|
||||
use s3s::auth::S3Auth;
|
||||
use s3s::auth::SecretKey;
|
||||
@@ -31,11 +30,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()));
|
||||
}
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user