AssumeRoleHandle done

This commit is contained in:
weisd
2024-12-10 16:59:27 +08:00
parent 637d614618
commit 8172c4f06a
21 changed files with 339 additions and 59 deletions
+50 -4
View File
@@ -1,13 +1,21 @@
use crate::policy::{Policy, Validator};
use crate::service_type::ServiceType;
use crate::{utils, Error};
use jsonwebtoken::{encode, Algorithm, Header};
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, OffsetDateTime};
use time::{Date, Duration, OffsetDateTime};
use crate::policy::{Policy, Validator};
use crate::service_type::ServiceType;
use crate::{utils, Error};
const ACCESS_KEY_MIN_LEN: usize = 3;
const ACCESS_KEY_MAX_LEN: usize = 20;
const SECRET_KEY_MIN_LEN: usize = 8;
const SECRET_KEY_MAX_LEN: usize = 40;
const ACCOUNT_ON: &str = "on";
const ACCOUNT_OFF: &str = "off";
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
struct CredentialHeader {
@@ -102,6 +110,44 @@ impl Credentials {
Self::check_key_value(header)
}
pub fn get_new_credentials_with_metadata<T: Serialize>(
claims: &T,
token_secret: &str,
exp: Option<usize>,
) -> crate::Result<Self> {
let ak = utils::gen_access_key(20).unwrap_or_default();
let sk = utils::gen_secret_key(32).unwrap_or_default();
Self::create_new_credentials_with_metadata(&ak, &sk, claims, token_secret, exp)
}
pub fn create_new_credentials_with_metadata<T: Serialize>(
ak: &str,
sk: &str,
claims: &T,
token_secret: &str,
exp: Option<usize>,
) -> crate::Result<Self> {
if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN {
return Err(Error::InvalidAccessKeyLength);
}
if sk.len() < SECRET_KEY_MIN_LEN || sk.len() > SECRET_KEY_MAX_LEN {
return Err(Error::InvalidAccessKeyLength);
}
let token = utils::generate_jwt(claims, token_secret).map_err(Error::JWTError)?;
Ok(Self {
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))),
..Default::default()
})
}
pub fn check_key_value(_header: CredentialHeader) -> crate::Result<Self> {
todo!()
}
+1 -1
View File
@@ -89,7 +89,7 @@ impl Cache {
impl CacheInner {
#[inline]
fn get_user<'a>(&self, user_name: &'a str) -> Option<&UserIdentity> {
pub fn get_user<'a>(&self, user_name: &'a str) -> Option<&UserIdentity> {
self.users.get(user_name).or_else(|| self.sts_accounts.get(user_name))
}
+9
View File
@@ -28,6 +28,15 @@ pub enum Error {
#[error("malformed credential")]
ErrCredMalformed,
#[error("CredNotInitialized")]
CredNotInitialized,
#[error("invalid key length")]
InvalidAccessKeyLength,
#[error("jwt err {0}")]
JWTError(jsonwebtoken::errors::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
+34 -2
View File
@@ -7,7 +7,7 @@ use std::sync::{Arc, OnceLock};
use store::object::ObjectStore;
use time::OffsetDateTime;
mod cache;
pub mod cache;
mod format;
mod handler;
@@ -24,6 +24,38 @@ pub use error::{Error, Result};
static IAM_SYS: OnceLock<Arc<IamCache<ObjectStore>>> = OnceLock::new();
static GLOBAL_ACTIVE_CRED: OnceLock<Credentials> = OnceLock::new();
pub fn init_global_action_cred(ak: Option<String>, sk: Option<String>) -> Result<()> {
let ak = {
if let Some(k) = ak {
k
} else {
utils::gen_access_key(20).unwrap_or_default()
}
};
let sk = {
if let Some(k) = sk {
k
} else {
utils::gen_secret_key(32).unwrap_or_default()
}
};
GLOBAL_ACTIVE_CRED
.set(Credentials {
access_key: ak,
secret_key: sk,
..Default::default()
})
.map_err(|_e| Error::CredNotInitialized)
}
pub fn get_global_action_cred() -> Option<Credentials> {
GLOBAL_ACTIVE_CRED.get().cloned()
}
pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> crate::Result<()> {
debug!("init iam system");
let s = IamCache::new(ObjectStore::new(ecstore)).await;
@@ -33,7 +65,7 @@ pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> crate::Result<()> {
#[inline]
pub fn get() -> crate::Result<Arc<IamCache<ObjectStore>>> {
IAM_SYS.get().map(|x| Arc::clone(x)).ok_or(Error::IamSysNotInitialized)
IAM_SYS.get().map(Arc::clone).ok_or(Error::IamSysNotInitialized)
}
pub async fn is_allowed<'a>(args: Args<'a>) -> crate::Result<bool> {
+50 -8
View File
@@ -109,11 +109,12 @@ where
}
// todo, 判断是否存在,是否可以重试
#[tracing::instrument(level = "debug", skip(self))]
async fn save_iam_formatter(self: Arc<Self>) -> crate::Result<()> {
match self.api.load_iam_config::<Format>(Format::PATH).await {
Ok((format, _)) if format.version >= 1 => return Ok(()),
Err(Error::EcstoreError(e)) if !ecstore::disk::error::is_err_file_not_found(&e) => {
return Err(Error::EcstoreError(e))
return Err(Error::EcstoreError(e));
}
_ => {}
}
@@ -127,13 +128,14 @@ where
Ok(users
.values()
.filter_map(|x| {
if !access_key.is_empty() && x.credentials.parent_user.as_str() == access_key {
if x.credentials.is_service_account() {
let mut c = x.credentials.clone();
c.secret_key = String::new();
c.session_token = String::new();
return Some(c);
}
if !access_key.is_empty()
&& x.credentials.parent_user.as_str() == access_key
&& x.credentials.is_service_account()
{
let mut c = x.credentials.clone();
c.secret_key = String::new();
c.session_token = String::new();
return Some(c);
}
None
@@ -220,4 +222,44 @@ where
_ => Ok(None),
}
}
pub async fn policy_db_get(&self, name: &str, _groups: Option<Vec<String>>) -> crate::Result<Vec<String>> {
// let user = self.cache.users.load();
// let Some(u) = user.get(name) else {
// return Err(Error::StringError("no service account".into()));
// };
let policies = self.cache.user_policies.load();
let user_policies = {
if let Some(p) = policies.get(name) {
p.to_slice()
} else {
Vec::new()
}
};
// TODO: groups
Ok(user_policies)
}
pub async fn set_temp_user(&self, _access_key: &str, cred: &Credentials, _policy_name: &str) -> crate::Result<()> {
let user_entiry = UserIdentity::from(cred.clone());
let path = format!(
"config/iam/{}{}/identity.json",
UserType::Sts.prefix(),
user_entiry.credentials.access_key
);
debug!("save object: {path:?}");
self.api.save_iam_config(&user_entiry, path).await?;
Cache::add_or_update(
&self.cache.users,
&user_entiry.credentials.access_key,
&user_entiry,
OffsetDateTime::now_utc(),
);
Ok(())
}
}
+9 -1
View File
@@ -17,7 +17,7 @@ pub use id::ID;
pub use policy::{default::DEFAULT_POLICIES, Policy};
pub use resource::ResourceSet;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::{to_string, Value};
pub use statement::Statement;
use std::collections::HashMap;
use time::OffsetDateTime;
@@ -37,6 +37,14 @@ impl MappedPolicy {
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 {
+2 -1
View File
@@ -8,7 +8,7 @@ use serde::{de::DeserializeOwned, Serialize};
use crate::{
auth::UserIdentity,
cache::Cache,
policy::{PolicyDoc, UserType, DEFAULT_POLICIES},
policy::{MappedPolicy, PolicyDoc, UserType, DEFAULT_POLICIES},
};
#[async_trait::async_trait]
@@ -40,4 +40,5 @@ pub trait Store: Clone + Send + Sync + 'static {
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>;
}
+39 -23
View File
@@ -8,8 +8,9 @@ use ecstore::{
utils::path::dir,
};
use futures::future::try_join_all;
use log::debug;
use log::{debug, warn};
use serde::{de::DeserializeOwned, Serialize};
use tracing::error;
use super::Store;
use crate::{
@@ -99,14 +100,6 @@ impl ObjectStore {
Ok(Some(user))
}
async fn load_mapped_policy(&self, user_type: UserType, name: &str, _is_group: bool) -> crate::Result<MappedPolicy> {
let (p, _) = self
.load_iam_config::<MappedPolicy>(&format!("{base}{name}.json", base = user_type.prefix(), name = name))
.await?;
Ok(p)
}
}
#[async_trait::async_trait]
@@ -137,6 +130,7 @@ impl Store for ObjectStore {
))
}
#[tracing::instrument(level = "debug", skip(self, item, path))]
async fn save_iam_config<Item: Serialize + Send>(&self, item: Item, path: impl AsRef<str> + Send) -> crate::Result<()> {
let data = serde_json::to_vec(&item).map_err(|e| crate::Error::StringError(e.to_string()))?;
// let data = crypto::encrypt_data(&[], &data)?;
@@ -169,8 +163,8 @@ impl Store for ObjectStore {
.await?;
if policy_doc.version == 0 {
policy_doc.create_date = object_info.mod_time.clone();
policy_doc.update_date = object_info.mod_time.clone();
policy_doc.create_date = object_info.mod_time;
policy_doc.update_date = object_info.mod_time;
}
result.insert(name.to_str().unwrap().to_owned(), policy_doc);
@@ -219,7 +213,7 @@ impl Store for ObjectStore {
"policydb/groups/",
"service-accounts/",
"policydb/sts-users/",
"sts",
"sts/",
],
)
.await?;
@@ -234,12 +228,13 @@ impl Store for ObjectStore {
);
// 一次读取32个元素
let mut iter = items
let iter = items
.iter()
.map(|item| item.trim_start_matches("config/iam/"))
.map(|item| split_path(item, item.starts_with("policydb/")))
.filter_map(|(list_key, trimmed_item)| {
debug!("list_key: {list_key}, trimmed_item: {trimmed_item}");
if list_key == "format.json" {
return None;
}
@@ -255,13 +250,14 @@ impl Store for ObjectStore {
Some(async move {
match list_key {
"policies/" => {
let name = dir(trimmed_item).trim_end_matches('/');
let trimmed_item = dir(trimmed_item);
let name = trimmed_item.trim_end_matches('/');
let policy_doc = self.load_policy(name).await?;
policy_docs.lock().await.insert(name.to_owned(), policy_doc);
}
"users/" => {
let name = dir(trimmed_item);
if let Some(user) = self.load_user_identity(UserType::Reg, name).await? {
if let Some(user) = self.load_user_identity(UserType::Reg, &name).await? {
users.lock().await.insert(name.to_owned(), user);
};
}
@@ -276,7 +272,8 @@ impl Store for ObjectStore {
}
}
"service-accounts/" => {
let name = dir(trimmed_item).trim_end_matches('/');
let trimmed_item = dir(trimmed_item);
let name = trimmed_item.trim_end_matches('/');
let Some(user) = self.load_user_identity(UserType::Svc, name).await? else {
return Ok(());
};
@@ -299,7 +296,8 @@ impl Store for ObjectStore {
}
"sts/" => {
let name = dir(trimmed_item);
if let Some(user) = self.load_user_identity(UserType::Sts, name).await? {
if let Some(user) = self.load_user_identity(UserType::Sts, &name).await? {
warn!("sts_accounts insert {}, user {:?}", name, &user.credentials.access_key);
sts_accounts.lock().await.insert(name.to_owned(), user);
};
}
@@ -319,7 +317,7 @@ impl Store for ObjectStore {
let mut all_futures = Vec::with_capacity(32);
while let Some(f) = iter.next() {
for f in iter {
all_futures.push(f);
if all_futures.len() == 32 {
@@ -332,12 +330,30 @@ impl Store for ObjectStore {
try_join_all(all_futures).await?;
}
Arc::into_inner(users).map(|x| cache.users.store(Arc::new(x.into_inner().update_load_time())));
Arc::into_inner(policy_docs).map(|x| cache.policy_docs.store(Arc::new(x.into_inner().update_load_time())));
Arc::into_inner(user_policies).map(|x| cache.user_policies.store(Arc::new(x.into_inner().update_load_time())));
Arc::into_inner(sts_policies).map(|x| cache.sts_policies.store(Arc::new(x.into_inner().update_load_time())));
Arc::into_inner(sts_accounts).map(|x| cache.sts_accounts.store(Arc::new(x.into_inner().update_load_time())));
if let Some(x) = Arc::into_inner(users) {
cache.users.store(Arc::new(x.into_inner().update_load_time()))
}
if let Some(x) = Arc::into_inner(policy_docs) {
cache.policy_docs.store(Arc::new(x.into_inner().update_load_time()))
}
if let Some(x) = Arc::into_inner(user_policies) {
cache.user_policies.store(Arc::new(x.into_inner().update_load_time()))
}
if let Some(x) = Arc::into_inner(sts_policies) {
cache.sts_policies.store(Arc::new(x.into_inner().update_load_time()))
}
if let Some(x) = Arc::into_inner(sts_accounts) {
cache.sts_accounts.store(Arc::new(x.into_inner().update_load_time()))
}
Ok(())
}
async fn load_mapped_policy(&self, user_type: UserType, name: &str, _is_group: bool) -> crate::Result<MappedPolicy> {
let (p, _) = self
.load_iam_config::<MappedPolicy>(&format!("{base}{name}.json", base = user_type.prefix(), name = name))
.await?;
Ok(p)
}
}
+8 -2
View File
@@ -1,6 +1,7 @@
use rand::{Rng, RngCore};
use crate::Error;
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use rand::{Rng, RngCore};
use serde::Serialize;
pub fn gen_access_key(length: usize) -> crate::Result<String> {
const ALPHA_NUMERIC_TABLE: [char; 36] = [
@@ -39,6 +40,11 @@ pub fn gen_secret_key(length: usize) -> crate::Result<String> {
Ok(key_str)
}
pub fn generate_jwt<T: Serialize>(claims: &T, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
let header = Header::new(Algorithm::HS512);
encode(&header, &claims, &EncodingKey::from_secret(secret.as_bytes()))
}
#[cfg(test)]
mod tests {
use super::{gen_access_key, gen_secret_key};