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:
bestgopher
2024-10-14 23:11:49 +08:00
parent 8519a596ec
commit bb2f765e5d
61 changed files with 5319 additions and 64 deletions
+3 -1
View File
@@ -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
+2
View File
@@ -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!()
}
}
+34
View File
@@ -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)
}
+1
View File
@@ -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
View File
@@ -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");