mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 21:07:43 +00:00
add iam system
add iam store feat: add crypto crate introduce decrypt_data and encrypt_data functions Signed-off-by: bestgopher <84328409@qq.com>
This commit is contained in:
+3
-1
@@ -37,7 +37,7 @@ s3s.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
time = { workspace = true, features = ["parsing", "formatting"] }
|
||||
time = { workspace = true, features = ["parsing", "formatting", "serde"] }
|
||||
tokio-util = { version = "0.7.12", features = ["io", "compat"] }
|
||||
tokio = { workspace = true, features = [
|
||||
"rt-multi-thread",
|
||||
@@ -62,6 +62,8 @@ shadow-rs = "0.36.0"
|
||||
const-str = { version = "0.5.7", features = ["std", "proc"] }
|
||||
atoi = "2.0.0"
|
||||
serde_urlencoded = "0.7.1"
|
||||
crypto = { path = "../crypto" }
|
||||
iam = { path = "../iam" }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build.workspace = true
|
||||
|
||||
@@ -34,6 +34,8 @@ use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub mod service_account;
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "PascalCase", default)]
|
||||
pub struct AssumeRoleRequest {
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hyper::StatusCode;
|
||||
use iam::{
|
||||
auth::CredentialsBuilder,
|
||||
policy::{
|
||||
action::{Action, AdminAction::ListServiceAccountsAdminAction},
|
||||
Args,
|
||||
},
|
||||
};
|
||||
use matchit::Params;
|
||||
use s3s::{s3_error, Body, S3Request, S3Response, S3Result};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::admin::models::service_account::{AddServiceAccountReq, AddServiceAccountResp, Credentials, InfoServiceAccountResp};
|
||||
use crate::admin::router::Operation;
|
||||
|
||||
pub struct AddServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for AddServiceAccount {
|
||||
async fn call(&self, mut req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle AddServiceAccount, req: {req:?}");
|
||||
|
||||
let Some(cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
||||
let is_owner = true; // 先按true处理,后期根据请求决定。
|
||||
let body = req.input.store_all_unlimited().await.unwrap();
|
||||
let body = crypto::decrypt_data(cred.secret_key.expose().as_bytes(), &body[..])
|
||||
.map_err(|_| s3_error!(InternalError, "encrypt data failed"))?;
|
||||
|
||||
debug!("body: {:?}", String::from_utf8_lossy(&body));
|
||||
|
||||
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() {
|
||||
return Err(s3_error!(InvalidRequest, "access key has spaces"));
|
||||
}
|
||||
|
||||
// 校验合法性, Name, Expiration, Description
|
||||
let target_user = create_req.target_user.as_ref().unwrap_or(&cred.access_key);
|
||||
let deny_only = true;
|
||||
|
||||
// todo 校验权限
|
||||
|
||||
// if !iam::is_allowed(Args {
|
||||
// account: &cred.access_key,
|
||||
// groups: &[],
|
||||
// action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction),
|
||||
// bucket: "",
|
||||
// conditions: &HashMap::new(),
|
||||
// is_owner,
|
||||
// object: "",
|
||||
// claims: &HashMap::new(),
|
||||
// deny_only,
|
||||
// })
|
||||
// .await
|
||||
// .unwrap_or(false)
|
||||
// {
|
||||
// return Err(s3_error!(AccessDenied));
|
||||
// }
|
||||
//
|
||||
|
||||
let cred = CredentialsBuilder::new()
|
||||
.parent_user(match create_req.target_user {
|
||||
Some(target_user) => target_user,
|
||||
_ => cred.access_key,
|
||||
})
|
||||
.access_key(create_req.access_key)
|
||||
.secret_key(create_req.secret_key)
|
||||
.description(create_req.description)
|
||||
.expiration(create_req.expiration)
|
||||
.session_policy({
|
||||
match create_req.policy {
|
||||
Some(p) if !p.is_empty() => {
|
||||
Some(serde_json::from_slice(&p).map_err(|_| s3_error!(InvalidRequest, "invalid policy"))?)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.name(create_req.name)
|
||||
.try_build()
|
||||
.map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?;
|
||||
|
||||
let resp = serde_json::to_vec(&AddServiceAccountResp {
|
||||
credentials: Credentials {
|
||||
access_key: &cred.access_key,
|
||||
secret_key: &cred.secret_key,
|
||||
session_token: None,
|
||||
expiration: cred.expiration,
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
iam::add_service_account(cred).await.map_err(|e| {
|
||||
debug!("add cred failed: {e:?}");
|
||||
s3_error!(InternalError, "add cred failed")
|
||||
})?;
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, resp)))
|
||||
}
|
||||
}
|
||||
|
||||
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")) };
|
||||
|
||||
// return Err(s3_error!(NotImplemented));
|
||||
//
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InfoServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
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);
|
||||
}
|
||||
|
||||
None
|
||||
}) else {
|
||||
return Err(s3_error!(InvalidRequest, "access key is not exist"));
|
||||
};
|
||||
|
||||
let (sa, sp) = iam::get_service_account(ak).await.map_err(|e| {
|
||||
debug!("get service account failed, err: {e:?}");
|
||||
s3_error!(InternalError)
|
||||
})?;
|
||||
|
||||
if !iam::is_allowed(Args {
|
||||
account: &sa.access_key,
|
||||
groups: &sa.groups.unwrap_or_default()[..],
|
||||
action: Action::AdminAction(ListServiceAccountsAdminAction),
|
||||
bucket: "",
|
||||
conditions: &HashMap::new(),
|
||||
is_owner: true,
|
||||
object: "",
|
||||
claims: &HashMap::new(),
|
||||
deny_only: false,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| s3_error!(InternalError))?
|
||||
{
|
||||
let req_user = &cred.access_key;
|
||||
if req_user != &sa.parent_user {
|
||||
return Err(s3_error!(AccessDenied));
|
||||
}
|
||||
}
|
||||
|
||||
// let implied_policy = sp.version.is_empty() && sp.statements.is_empty();
|
||||
// let sva = if implied_policy {
|
||||
// sp
|
||||
// } else {
|
||||
// // 这里使用
|
||||
// todo!();
|
||||
// };
|
||||
|
||||
let body = serde_json::to_vec(&InfoServiceAccountResp {
|
||||
parent_user: sa.parent_user,
|
||||
account_status: sa.status,
|
||||
implied_policy: true,
|
||||
// policy: serde_json::to_string_pretty(&sva).map_err(|_| s3_error!(InternalError, "json marshal failed"))?,
|
||||
policy: "".into(),
|
||||
name: sa.name.unwrap_or_default(),
|
||||
description: sa.description.unwrap_or_default(),
|
||||
expiration: sa.expiration,
|
||||
})
|
||||
.map_err(|_| s3_error!(InternalError, "json marshal failed"))?;
|
||||
|
||||
Ok(S3Response::new((
|
||||
StatusCode::OK,
|
||||
crypto::encrypt_data(cred.access_key.as_bytes(), &body[..])
|
||||
.map_err(|_| s3_error!(InternalError, "encrypt data failed"))?
|
||||
.into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for ListServiceAccount {
|
||||
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle ListServiceAccount");
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteServiceAccount {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for DeleteServiceAccount {
|
||||
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."));
|
||||
};
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
pub mod handlers;
|
||||
pub mod models;
|
||||
pub mod router;
|
||||
|
||||
use common::error::Result;
|
||||
// use ecstore::global::{is_dist_erasure, is_erasure};
|
||||
use handlers::service_account::{
|
||||
AddServiceAccount, DeleteServiceAccount, InfoServiceAccount, ListServiceAccount, UpdateServiceAccount,
|
||||
};
|
||||
use hyper::Method;
|
||||
use router::{AdminOperation, S3Router};
|
||||
use s3s::route::S3Route;
|
||||
@@ -105,5 +109,35 @@ pub fn make_admin_route() -> Result<impl S3Route> {
|
||||
)?;
|
||||
// }
|
||||
|
||||
r.insert(
|
||||
Method::POST,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/update-service-account").as_str(),
|
||||
AdminOperation(&UpdateServiceAccount {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/info-service-account").as_str(),
|
||||
AdminOperation(&InfoServiceAccount {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/list-service-accounts").as_str(),
|
||||
AdminOperation(&ListServiceAccount {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::DELETE,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/delete-service-accounts").as_str(),
|
||||
AdminOperation(&DeleteServiceAccount {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::PUT,
|
||||
format!("{}{}", ADMIN_PREFIX, "/v3/add-service-accounts").as_str(),
|
||||
AdminOperation(&AddServiceAccount {}),
|
||||
)?;
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod service_account;
|
||||
@@ -0,0 +1,51 @@
|
||||
use serde::{Deserialize, 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
-1
@@ -21,10 +21,11 @@ use hyper_util::{
|
||||
server::conn::auto::Builder as ConnBuilder,
|
||||
service::TowerToHyperService,
|
||||
};
|
||||
use iam::init_iam_sys;
|
||||
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
|
||||
use s3s::{auth::SimpleAuth, service::S3ServiceBuilder};
|
||||
use service::hybrid;
|
||||
use std::{io::IsTerminal, net::SocketAddr, str::FromStr};
|
||||
use std::{io::IsTerminal, net::SocketAddr, str::FromStr, sync::Arc};
|
||||
use tokio::net::TcpListener;
|
||||
use tonic::{metadata::MetadataValue, Request, Status};
|
||||
use tracing::{debug, error, info, warn};
|
||||
@@ -195,6 +196,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
init_data_scanner().await;
|
||||
// init auto heal
|
||||
init_auto_heal().await;
|
||||
init_iam_sys(store.clone()).await.unwrap();
|
||||
|
||||
info!("server was started");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user