mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 01:30:33 +00:00
rewrite service_account handler
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::error::Error as IamError;
|
||||
use crate::policy::Policy;
|
||||
use crate::sys::Validator;
|
||||
use crate::sys::{iam_policy_claim_name_sa, Validator, INHERITED_POLICY_TYPE};
|
||||
use crate::utils;
|
||||
use crate::utils::extract_claims;
|
||||
use ecstore::error::{Error, Result};
|
||||
@@ -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;
|
||||
@@ -200,7 +215,7 @@ pub fn create_new_credentials_with_metadata(
|
||||
let expiration = {
|
||||
if let Some(v) = claims.get("exp") {
|
||||
if let Some(expiry) = v.as_i64() {
|
||||
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_local()?.offset()))
|
||||
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_utc().offset()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -132,3 +132,11 @@ pub fn is_err_no_such_group(err: &ecstore::error::Error) -> bool {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use ecstore::{
|
||||
error::{Error, Result},
|
||||
};
|
||||
use log::{debug, warn};
|
||||
use madmin::{AccountStatus, GroupDesc};
|
||||
use madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
@@ -483,12 +483,12 @@ where
|
||||
let mut cr = ui.credentials.clone();
|
||||
let current_secret_key = cr.secret_key.clone();
|
||||
|
||||
if !opts.secret_key.is_empty() {
|
||||
if !is_secret_key_valid(&opts.secret_key) {
|
||||
if let Some(secret) = opts.secret_key {
|
||||
if !is_secret_key_valid(&secret) {
|
||||
return Err(IamError::InvalidSecretKeyLength.into());
|
||||
}
|
||||
|
||||
cr.secret_key = opts.secret_key;
|
||||
cr.secret_key = secret;
|
||||
}
|
||||
|
||||
if opts.name.is_some() {
|
||||
@@ -554,7 +554,7 @@ where
|
||||
Ok(OffsetDateTime::now_utc())
|
||||
}
|
||||
|
||||
pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result<Vec<String>> {
|
||||
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
|
||||
if name.is_empty() {
|
||||
return Err(Error::new(IamError::InvalidArgument));
|
||||
}
|
||||
@@ -562,11 +562,13 @@ where
|
||||
let (mut policies, _) = self.policy_db_get_internal(name, false, false).await?;
|
||||
let present = !policies.is_empty();
|
||||
|
||||
for group in groups.iter() {
|
||||
let (gp, _) = self.policy_db_get_internal(group, true, present).await?;
|
||||
gp.iter().for_each(|v| {
|
||||
policies.push(v.clone());
|
||||
});
|
||||
if let Some(groups) = groups {
|
||||
for group in groups.iter() {
|
||||
let (gp, _) = self.policy_db_get_internal(group, true, present).await?;
|
||||
gp.iter().for_each(|v| {
|
||||
policies.push(v.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(policies)
|
||||
@@ -946,7 +948,7 @@ where
|
||||
m
|
||||
}
|
||||
|
||||
pub async fn add_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result<OffsetDateTime> {
|
||||
pub async fn add_user(&self, access_key: &str, args: &AddOrUpdateUserReq) -> Result<OffsetDateTime> {
|
||||
let users = self.cache.users.load();
|
||||
if let Some(x) = users.get(access_key) {
|
||||
warn!("user already exists: {:?}", x);
|
||||
@@ -956,15 +958,14 @@ where
|
||||
}
|
||||
|
||||
let status = {
|
||||
match status {
|
||||
val if val == AccountStatus::Enabled.as_ref() => auth::ACCOUNT_ON,
|
||||
auth::ACCOUNT_ON => auth::ACCOUNT_ON,
|
||||
match &args.status {
|
||||
AccountStatus::Enabled => auth::ACCOUNT_ON,
|
||||
_ => auth::ACCOUNT_OFF,
|
||||
}
|
||||
};
|
||||
let user_entiry = UserIdentity::from(Credentials {
|
||||
access_key: access_key.to_string(),
|
||||
secret_key: secret_key.to_string(),
|
||||
secret_key: args.secret_key.to_string(),
|
||||
status: status.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ 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;
|
||||
@@ -178,9 +179,9 @@ impl<T: Store> IamSys<T> {
|
||||
pub async fn new_service_account(
|
||||
&self,
|
||||
parent_user: &str,
|
||||
groups: Vec<String>,
|
||||
groups: Option<Vec<String>>,
|
||||
opts: NewServiceAccountOpts,
|
||||
) -> Result<OffsetDateTime> {
|
||||
) -> Result<(Credentials, OffsetDateTime)> {
|
||||
if parent_user.is_empty() {
|
||||
return Err(IamError::InvalidArgument.into());
|
||||
}
|
||||
@@ -236,14 +237,15 @@ impl<T: Store> IamSys<T> {
|
||||
|
||||
let mut cred = create_new_credentials_with_metadata(&access_key, &secret_key, &m, &secret_key)?;
|
||||
cred.parent_user = parent_user.to_owned();
|
||||
cred.groups = Some(groups);
|
||||
cred.groups = groups;
|
||||
cred.status = ACCOUNT_ON.to_owned();
|
||||
cred.name = opts.name;
|
||||
cred.description = opts.description;
|
||||
cred.expiration = opts.expiration;
|
||||
|
||||
self.store.add_service_account(cred).await
|
||||
let create_at = self.store.add_service_account(cred.clone()).await?;
|
||||
|
||||
Ok((cred, create_at))
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
@@ -387,7 +389,7 @@ impl<T: Store> IamSys<T> {
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn create_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result<OffsetDateTime> {
|
||||
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());
|
||||
}
|
||||
@@ -396,11 +398,11 @@ impl<T: Store> IamSys<T> {
|
||||
return Err(IamError::ContainsReservedChars.into());
|
||||
}
|
||||
|
||||
if !is_secret_key_valid(secret_key) {
|
||||
if !is_secret_key_valid(&args.secret_key) {
|
||||
return Err(IamError::InvalidSecretKeyLength.into());
|
||||
}
|
||||
|
||||
self.store.add_user(access_key, secret_key, status).await
|
||||
self.store.add_user(access_key, args).await
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
@@ -470,7 +472,7 @@ impl<T: Store> IamSys<T> {
|
||||
// TODO: notification
|
||||
}
|
||||
|
||||
pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result<Vec<String>> {
|
||||
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
|
||||
self.store.policy_db_get(name, groups).await
|
||||
}
|
||||
|
||||
@@ -580,7 +582,7 @@ impl<T: Store> IamSys<T> {
|
||||
is_owner || combined_policy.is_allowed(&parent_args)
|
||||
}
|
||||
|
||||
async fn get_combined_policy(&self, policies: &[String]) -> Policy {
|
||||
pub async fn get_combined_policy(&self, policies: &[String]) -> Policy {
|
||||
self.store.merge_policies(&policies.join(",")).await.1
|
||||
}
|
||||
|
||||
@@ -676,7 +678,7 @@ pub struct NewServiceAccountOpts {
|
||||
|
||||
pub struct UpdateServiceAccountOpts {
|
||||
pub session_policy: Option<Policy>,
|
||||
pub secret_key: String,
|
||||
pub secret_key: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
@@ -702,7 +704,7 @@ pub trait Validator {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Args<'a> {
|
||||
pub account: &'a str,
|
||||
pub groups: &'a [String],
|
||||
pub groups: &'a Option<Vec<String>>,
|
||||
pub action: Action,
|
||||
pub bucket: &'a str,
|
||||
pub conditions: &'a HashMap<String, Vec<String>>,
|
||||
|
||||
@@ -605,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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +270,7 @@ impl Operation for AssumeRoleHandle {
|
||||
|
||||
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.unwrap_or_default())
|
||||
.await
|
||||
{
|
||||
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
|
||||
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,139 +1,246 @@
|
||||
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},
|
||||
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(req_cred) = req.credentials else {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
unimplemented!()
|
||||
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?;
|
||||
|
||||
// let Some(input_cred) = req.credentials else {
|
||||
// return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// };
|
||||
// let _is_owner = true; // 先按true处理,后期根据请求决定。
|
||||
let mut input = req.input;
|
||||
let body = match input.store_all_unlimited().await {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("get body failed, e: {:?}", e);
|
||||
return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
}
|
||||
};
|
||||
|
||||
// let mut input = req.input;
|
||||
// let body = match input.store_all_unlimited().await {
|
||||
// Ok(b) => b,
|
||||
// Err(e) => {
|
||||
// warn!("get body failed, e: {:?}", e);
|
||||
// return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
// }
|
||||
// };
|
||||
let 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());
|
||||
|
||||
// let mut create_req: AddServiceAccountReq =
|
||||
// serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
|
||||
if has_space_be(&create_req.access_key) {
|
||||
return Err(s3_error!(InvalidRequest, "access key has spaces"));
|
||||
}
|
||||
|
||||
// create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
|
||||
create_req
|
||||
.validate()
|
||||
.map_err(|e| S3Error::with_message(InvalidRequest, e.to_string()))?;
|
||||
|
||||
// if create_req.access_key.trim().len() != create_req.access_key.len() {
|
||||
// return Err(s3_error!(InvalidRequest, "access key has spaces"));
|
||||
// }
|
||||
let Some(sys_cred) = get_global_action_cred() else {
|
||||
return Err(s3_error!(InvalidRequest, "get sys cred failed"));
|
||||
};
|
||||
|
||||
// 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")
|
||||
// })?;
|
||||
if sys_cred.access_key == create_req.access_key {
|
||||
return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
|
||||
}
|
||||
|
||||
// // TODO check create_req validity
|
||||
let mut target_user = if let Some(u) = create_req.target_user {
|
||||
u
|
||||
} else {
|
||||
cred.access_key.clone()
|
||||
};
|
||||
|
||||
// // 校验合法性, Name, Expiration, Description
|
||||
// let target_user = if let Some(u) = create_req.target_user {
|
||||
// u
|
||||
// } else {
|
||||
// cred.access_key
|
||||
// };
|
||||
// let _deny_only = true;
|
||||
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;
|
||||
|
||||
// // todo 校验权限
|
||||
if cred.is_owner() || cred.is_service_account() {
|
||||
req_parent_user = cred.parent_user.clone();
|
||||
req_is_derived_cred = true;
|
||||
}
|
||||
|
||||
// // if !iam::is_allowed(Args {
|
||||
// // account: &cred.access_key,
|
||||
// // groups: &[],
|
||||
// // action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction),
|
||||
// // bucket: "",
|
||||
// // conditions: &HashMap::new(),
|
||||
// // is_owner,
|
||||
// // object: "",
|
||||
// // claims: &HashMap::new(),
|
||||
// // deny_only,
|
||||
// // })
|
||||
// // .await
|
||||
// // .unwrap_or(false)
|
||||
// // {
|
||||
// // return Err(s3_error!(AccessDenied));
|
||||
// // }
|
||||
// //
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// let cred = CredentialsBuilder::new()
|
||||
// .parent_user(target_user)
|
||||
// .access_key(create_req.access_key)
|
||||
// .secret_key(create_req.secret_key)
|
||||
// .description(create_req.description.unwrap_or_default())
|
||||
// .expiration(create_req.expiration)
|
||||
// .session_policy({
|
||||
// match create_req.policy {
|
||||
// Some(p) if !p.is_empty() => {
|
||||
// Some(serde_json::from_slice(p.as_bytes()).map_err(|_| s3_error!(InvalidRequest, "invalid policy"))?)
|
||||
// }
|
||||
// _ => None,
|
||||
// }
|
||||
// })
|
||||
// .name(create_req.name)
|
||||
// .try_build()
|
||||
// .map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?;
|
||||
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 resp = serde_json::to_vec(&AddServiceAccountResp {
|
||||
// credentials: Credentials {
|
||||
// access_key: &cred.access_key,
|
||||
// secret_key: &cred.secret_key,
|
||||
// session_token: None,
|
||||
// expiration: cred.expiration,
|
||||
// },
|
||||
// })
|
||||
// .unwrap()
|
||||
// .into();
|
||||
let is_svc_acc = target_user == req_user || target_user == req_parent_user;
|
||||
|
||||
// iam::add_service_account(cred).await.map_err(|e| {
|
||||
// debug!("add cred failed: {e:?}");
|
||||
// s3_error!(InternalError, "add cred failed")
|
||||
// })?;
|
||||
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()
|
||||
};
|
||||
|
||||
// Ok(S3Response::new((StatusCode::OK, resp)))
|
||||
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"));
|
||||
}
|
||||
target_user = req_parent_user;
|
||||
}
|
||||
|
||||
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: &new_cred.access_key,
|
||||
secret_key: &new_cred.secret_key,
|
||||
session_token: None,
|
||||
expiration: new_cred.expiration,
|
||||
},
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
#[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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,76 +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")) };
|
||||
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()
|
||||
}
|
||||
};
|
||||
|
||||
// //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 query.access_key.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
// if key != "accessKey" {
|
||||
// continue;
|
||||
// }
|
||||
let access_key = query.access_key;
|
||||
|
||||
// let Some(value) = x.next() else {
|
||||
// continue;
|
||||
// };
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// return Some(value);
|
||||
// }
|
||||
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")
|
||||
})?;
|
||||
|
||||
// None
|
||||
// }) else {
|
||||
// return Err(s3_error!(InvalidRequest, "access key is not exist"));
|
||||
// };
|
||||
// TODO: is_allowed
|
||||
|
||||
unimplemented!()
|
||||
let implied_policy = if let Some(policy) = session_policy.as_ref() {
|
||||
policy.version.is_empty() && policy.statements.is_empty()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// let (sa, _sp) = iam::get_service_account(ak).await.map_err(|e| {
|
||||
// debug!("get service account failed, err: {e:?}");
|
||||
// s3_error!(InternalError)
|
||||
// })?;
|
||||
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")
|
||||
})?;
|
||||
|
||||
// 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));
|
||||
// }
|
||||
// }
|
||||
Some(iam_store.get_combined_policy(&policies).await)
|
||||
}
|
||||
};
|
||||
|
||||
// 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 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
|
||||
}
|
||||
};
|
||||
|
||||
// Ok(S3Response::new((
|
||||
// StatusCode::OK,
|
||||
// crypto::encrypt_data(cred.access_key.as_bytes(), &body[..])
|
||||
// .map_err(|_| s3_error!(InternalError, "encrypt data failed"))?
|
||||
// .into(),
|
||||
// )))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,76 +337,125 @@ pub struct ListServiceAccount {}
|
||||
impl Operation for ListServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListServiceAccount");
|
||||
unimplemented!()
|
||||
// let query = {
|
||||
// if let Some(query) = req.uri.query() {
|
||||
// let input: ListServiceAccountQuery =
|
||||
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
// input
|
||||
// } else {
|
||||
// ListServiceAccountQuery::default()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let target_account = if let Some(user) = query.user {
|
||||
// user
|
||||
// } else {
|
||||
// let Some(input_cred) = req.credentials else {
|
||||
// return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// };
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: ListServiceAccountQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
ListServiceAccountQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
// let (cred, _owner) = check_key_valid(None, &input_cred.access_key).await.map_err(|e| {
|
||||
// debug!("check key failed: {e:?}");
|
||||
// s3_error!(InternalError, "check key failed")
|
||||
// })?;
|
||||
let Some(input_cred) = req.credentials else {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
// if cred.parent_user.is_empty() {
|
||||
// input_cred.access_key
|
||||
// } else {
|
||||
// cred.parent_user
|
||||
// }
|
||||
// };
|
||||
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 service_accounts = iam::list_service_accounts(&target_account).await.map_err(|e| {
|
||||
// debug!("list service account failed: {e:?}");
|
||||
// s3_error!(InternalError, "list service account failed")
|
||||
// })?;
|
||||
let 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 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
|
||||
// access_key: sa.access_key,
|
||||
// name: sa.name,
|
||||
// description: sa.description,
|
||||
// expiration: sa.expiration,
|
||||
// })
|
||||
// .collect();
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// let data = serde_json::to_vec(&ListServiceAccountsResp { accounts })
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
|
||||
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")
|
||||
})?;
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
let accounts: Vec<ServiceAccountInfo> = service_accounts
|
||||
.into_iter()
|
||||
.map(|sa| ServiceAccountInfo {
|
||||
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,
|
||||
expiration: sa.expiration,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
let data = serde_json::to_vec(&ListServiceAccountsResp { accounts })
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
}
|
||||
}
|
||||
|
||||
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,80 +26,86 @@ 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");
|
||||
unimplemented!()
|
||||
// let query = {
|
||||
// if let Some(query) = req.uri.query() {
|
||||
// let input: AddUserQuery =
|
||||
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
// input
|
||||
// } else {
|
||||
// AddUserQuery::default()
|
||||
// }
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
|
||||
input
|
||||
} else {
|
||||
AddUserQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
let Some(input_cred) = req.credentials else {
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
let (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() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
let mut input = req.input;
|
||||
let body = match input.store_all_unlimited().await {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("get body failed, e: {:?}", e);
|
||||
return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
}
|
||||
};
|
||||
|
||||
// let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?;
|
||||
|
||||
let args: AddOrUpdateUserReq = serde_json::from_slice(&body)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?;
|
||||
|
||||
warn!("add user args {:?}", args);
|
||||
|
||||
if args.secret_key.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
if let Some(sys_cred) = get_global_action_cred() {
|
||||
if sys_cred.access_key == ak {
|
||||
return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
if from_utf8(ak.as_bytes()).is_err() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is not utf8"));
|
||||
}
|
||||
|
||||
// let check_deny_only = if ak == cred.access_key {
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// };
|
||||
|
||||
// let Some(input_cred) = req.credentials else {
|
||||
// return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// };
|
||||
// TODO: is_allowed
|
||||
|
||||
// let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
iam_store
|
||||
.create_user(ak, &args)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?;
|
||||
|
||||
// if ak.is_empty() {
|
||||
// return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
// }
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
// let mut input = req.input;
|
||||
// let body = match input.store_all_unlimited().await {
|
||||
// Ok(b) => b,
|
||||
// Err(e) => {
|
||||
// warn!("get body failed, e: {:?}", e);
|
||||
// return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
// }
|
||||
// };
|
||||
|
||||
// // let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body)
|
||||
// // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?;
|
||||
|
||||
// let args: AddOrUpdateUserReq = serde_json::from_slice(&body)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?;
|
||||
|
||||
// warn!("add user args {:?}", args);
|
||||
|
||||
// if args.secret_key.is_empty() {
|
||||
// return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
// }
|
||||
|
||||
// if let Some(sys_cred) = get_global_action_cred() {
|
||||
// if sys_cred.access_key == ak {
|
||||
// return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
|
||||
// }
|
||||
// }
|
||||
|
||||
// if let (Some(user), true) = iam::get_user(ak)
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?
|
||||
// {
|
||||
// if user.credentials.is_temp() || user.credentials.is_service_account() {
|
||||
// return Err(s3_error!(InvalidArgument, "can't create user with service account access key"));
|
||||
// }
|
||||
// }
|
||||
|
||||
// let token = get_session_token(&req.headers);
|
||||
|
||||
// let (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"));
|
||||
// }
|
||||
|
||||
// iam::create_user(ak, &args.secret_key, "enabled")
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?;
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,43 +115,44 @@ impl Operation for SetUserStatus {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle SetUserStatus");
|
||||
|
||||
unimplemented!()
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
AddUserQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
// let query = {
|
||||
// if let Some(query) = req.uri.query() {
|
||||
// let input: AddUserQuery =
|
||||
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
// input
|
||||
// } else {
|
||||
// AddUserQuery::default()
|
||||
// }
|
||||
// };
|
||||
let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
|
||||
// let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
if ak.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
// if ak.is_empty() {
|
||||
// return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
// }
|
||||
let 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"));
|
||||
// };
|
||||
if input_cred.access_key == ak {
|
||||
return Err(s3_error!(InvalidArgument, "can't change status of self"));
|
||||
}
|
||||
|
||||
// if input_cred.access_key == ak {
|
||||
// return Err(s3_error!(InvalidArgument, "can't change status of self"));
|
||||
// }
|
||||
let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
|
||||
|
||||
// let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// iam::set_user_status(ak, status)
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?;
|
||||
iam_store
|
||||
.set_user_status(ak, status)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?;
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,25 +161,27 @@ pub struct ListUsers {}
|
||||
impl Operation for ListUsers {
|
||||
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListUsers");
|
||||
unimplemented!()
|
||||
// let users = iam::list_users()
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// let data = serde_json::to_vec(&users)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
|
||||
let users = iam_store
|
||||
.list_users()
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
|
||||
// // let Some(input_cred) = req.credentials else {
|
||||
// // return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// // };
|
||||
let data = serde_json::to_vec(&users)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
|
||||
|
||||
// // let body = encrypt_data(input_cred.secret_key.expose().as_bytes(), &data)
|
||||
// // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("encrypt_data err {}", e)))?;
|
||||
// let Some(input_cred) = req.credentials else {
|
||||
// return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
// };
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
// let body = encrypt_data(input_cred.secret_key.expose().as_bytes(), &data)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("encrypt_data err {}", e)))?;
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,39 +190,52 @@ pub struct RemoveUser {}
|
||||
impl Operation for RemoveUser {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle RemoveUser");
|
||||
unimplemented!()
|
||||
// let query = {
|
||||
// if let Some(query) = req.uri.query() {
|
||||
// let input: AddUserQuery =
|
||||
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
// input
|
||||
// } else {
|
||||
// AddUserQuery::default()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
AddUserQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
// if ak.is_empty() {
|
||||
// return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
// }
|
||||
let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
|
||||
// let (is_temp, _) = iam::is_temp_user(ak)
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?;
|
||||
if ak.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
// if is_temp {
|
||||
// return Err(s3_error!(InvalidArgument, "can't remove temp user"));
|
||||
// }
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// iam::delete_user(ak, true)
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?;
|
||||
let (is_temp, _) = iam_store
|
||||
.is_temp_user(ak)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?;
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
if is_temp {
|
||||
return Err(s3_error!(InvalidArgument, "can't remove temp user"));
|
||||
}
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
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)))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,33 +244,36 @@ pub struct GetUserInfo {}
|
||||
impl Operation for GetUserInfo {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle GetUserInfo");
|
||||
unimplemented!()
|
||||
// let query = {
|
||||
// if let Some(query) = req.uri.query() {
|
||||
// let input: AddUserQuery =
|
||||
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
// input
|
||||
// } else {
|
||||
// AddUserQuery::default()
|
||||
// }
|
||||
// };
|
||||
|
||||
// let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: AddUserQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
|
||||
input
|
||||
} else {
|
||||
AddUserQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
// if ak.is_empty() {
|
||||
// return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
// }
|
||||
let ak = query.access_key.as_deref().unwrap_or_default();
|
||||
|
||||
// let info = iam::get_user_info(ak)
|
||||
// .await
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
if ak.is_empty() {
|
||||
return Err(s3_error!(InvalidArgument, "access key is empty"));
|
||||
}
|
||||
|
||||
// let data = serde_json::to_vec(&info)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?;
|
||||
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||
|
||||
// let mut header = HeaderMap::new();
|
||||
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
let info = iam_store
|
||||
.get_user_info(ak)
|
||||
.await
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
|
||||
|
||||
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
let data = serde_json::to_vec(&info)
|
||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?;
|
||||
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||
|
||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
3
rustfs/src/admin/utils.rs
Normal file
3
rustfs/src/admin/utils.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub fn has_space_be(s: &str) -> bool {
|
||||
s.trim().len() != s.len()
|
||||
}
|
||||
Reference in New Issue
Block a user