mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
AssumeRoleHandle done
This commit is contained in:
Generated
+3
@@ -1287,6 +1287,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"jsonwebtoken",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1296,6 +1297,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.3",
|
"thiserror 2.0.3",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2521,6 +2523,7 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"iam",
|
"iam",
|
||||||
|
"jsonwebtoken",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lock",
|
"lock",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
quorum::CheckErrorFn,
|
quorum::CheckErrorFn,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use tracing::warn;
|
||||||
use tracing_error::{SpanTrace, SpanTraceStatus};
|
use tracing_error::{SpanTrace, SpanTraceStatus};
|
||||||
|
|
||||||
use crate::disk::error::{clone_disk_err, DiskError};
|
use crate::disk::error::{clone_disk_err, DiskError};
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ use std::slice::Iter;
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio::sync::{broadcast, mpsc, RwLock};
|
use tokio::sync::{broadcast, mpsc, RwLock};
|
||||||
use tokio::time::{interval, sleep};
|
use tokio::time::{interval, sleep};
|
||||||
use tokio::{fs, select};
|
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use crate::{
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
use madmin::heal_commands::HealResultItem;
|
use madmin::heal_commands::HealResultItem;
|
||||||
use madmin::info_commands::DiskMetrics;
|
|
||||||
use rmp_serde::Serializer;
|
use rmp_serde::Serializer;
|
||||||
use s3s::{dto::StreamingBlob, Body};
|
use s3s::{dto::StreamingBlob, Body};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
@@ -222,9 +222,9 @@ pub fn split(path: &str) -> (&str, &str) {
|
|||||||
(path, "")
|
(path, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir(path: &str) -> &str {
|
pub fn dir(path: &str) -> String {
|
||||||
let (a, _) = split(path);
|
let (a, _) = split(path);
|
||||||
a
|
clean(a)
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ itertools = "0.13.0"
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
base64-simd = "0.8.0"
|
base64-simd = "0.8.0"
|
||||||
|
jsonwebtoken = "9.3.0"
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-case.workspace = true
|
test-case.workspace = true
|
||||||
|
|||||||
@@ -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::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::cell::LazyCell;
|
use std::cell::LazyCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use time::format_description::BorrowedFormatItem;
|
use time::format_description::BorrowedFormatItem;
|
||||||
use time::{Date, OffsetDateTime};
|
use time::{Date, Duration, OffsetDateTime};
|
||||||
|
|
||||||
use crate::policy::{Policy, Validator};
|
const ACCESS_KEY_MIN_LEN: usize = 3;
|
||||||
use crate::service_type::ServiceType;
|
const ACCESS_KEY_MAX_LEN: usize = 20;
|
||||||
use crate::{utils, Error};
|
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))]
|
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
|
||||||
struct CredentialHeader {
|
struct CredentialHeader {
|
||||||
@@ -102,6 +110,44 @@ impl Credentials {
|
|||||||
Self::check_key_value(header)
|
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> {
|
pub fn check_key_value(_header: CredentialHeader) -> crate::Result<Self> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ impl Cache {
|
|||||||
|
|
||||||
impl CacheInner {
|
impl CacheInner {
|
||||||
#[inline]
|
#[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))
|
self.users.get(user_name).or_else(|| self.sts_accounts.get(user_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("malformed credential")]
|
#[error("malformed credential")]
|
||||||
ErrCredMalformed,
|
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>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|||||||
+34
-2
@@ -7,7 +7,7 @@ use std::sync::{Arc, OnceLock};
|
|||||||
use store::object::ObjectStore;
|
use store::object::ObjectStore;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
mod cache;
|
pub mod cache;
|
||||||
mod format;
|
mod format;
|
||||||
mod handler;
|
mod handler;
|
||||||
|
|
||||||
@@ -24,6 +24,38 @@ pub use error::{Error, Result};
|
|||||||
|
|
||||||
static IAM_SYS: OnceLock<Arc<IamCache<ObjectStore>>> = OnceLock::new();
|
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<()> {
|
pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> crate::Result<()> {
|
||||||
debug!("init iam system");
|
debug!("init iam system");
|
||||||
let s = IamCache::new(ObjectStore::new(ecstore)).await;
|
let s = IamCache::new(ObjectStore::new(ecstore)).await;
|
||||||
@@ -33,7 +65,7 @@ pub async fn init_iam_sys(ecstore: Arc<ECStore>) -> crate::Result<()> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get() -> crate::Result<Arc<IamCache<ObjectStore>>> {
|
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> {
|
pub async fn is_allowed<'a>(args: Args<'a>) -> crate::Result<bool> {
|
||||||
|
|||||||
+50
-8
@@ -109,11 +109,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo, 判断是否存在,是否可以重试
|
// todo, 判断是否存在,是否可以重试
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
async fn save_iam_formatter(self: Arc<Self>) -> crate::Result<()> {
|
async fn save_iam_formatter(self: Arc<Self>) -> crate::Result<()> {
|
||||||
match self.api.load_iam_config::<Format>(Format::PATH).await {
|
match self.api.load_iam_config::<Format>(Format::PATH).await {
|
||||||
Ok((format, _)) if format.version >= 1 => return Ok(()),
|
Ok((format, _)) if format.version >= 1 => return Ok(()),
|
||||||
Err(Error::EcstoreError(e)) if !ecstore::disk::error::is_err_file_not_found(&e) => {
|
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
|
Ok(users
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|x| {
|
.filter_map(|x| {
|
||||||
if !access_key.is_empty() && x.credentials.parent_user.as_str() == access_key {
|
if !access_key.is_empty()
|
||||||
if x.credentials.is_service_account() {
|
&& x.credentials.parent_user.as_str() == access_key
|
||||||
let mut c = x.credentials.clone();
|
&& x.credentials.is_service_account()
|
||||||
c.secret_key = String::new();
|
{
|
||||||
c.session_token = String::new();
|
let mut c = x.credentials.clone();
|
||||||
return Some(c);
|
c.secret_key = String::new();
|
||||||
}
|
c.session_token = String::new();
|
||||||
|
return Some(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@@ -220,4 +222,44 @@ where
|
|||||||
_ => Ok(None),
|
_ => 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
@@ -17,7 +17,7 @@ pub use id::ID;
|
|||||||
pub use policy::{default::DEFAULT_POLICIES, Policy};
|
pub use policy::{default::DEFAULT_POLICIES, Policy};
|
||||||
pub use resource::ResourceSet;
|
pub use resource::ResourceSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::{to_string, Value};
|
||||||
pub use statement::Statement;
|
pub use statement::Statement;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@@ -37,6 +37,14 @@ impl MappedPolicy {
|
|||||||
update_at: OffsetDateTime::now_utc(),
|
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 {
|
pub struct GroupInfo {
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,7 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::UserIdentity,
|
auth::UserIdentity,
|
||||||
cache::Cache,
|
cache::Cache,
|
||||||
policy::{PolicyDoc, UserType, DEFAULT_POLICIES},
|
policy::{MappedPolicy, PolicyDoc, UserType, DEFAULT_POLICIES},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[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_users(&self, user_type: UserType) -> crate::Result<HashMap<String, UserIdentity>>;
|
||||||
|
|
||||||
async fn load_policy_docs(&self) -> crate::Result<HashMap<String, PolicyDoc>>;
|
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
@@ -8,8 +8,9 @@ use ecstore::{
|
|||||||
utils::path::dir,
|
utils::path::dir,
|
||||||
};
|
};
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use log::debug;
|
use log::{debug, warn};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use super::Store;
|
use super::Store;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -99,14 +100,6 @@ impl ObjectStore {
|
|||||||
|
|
||||||
Ok(Some(user))
|
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]
|
#[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<()> {
|
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 = serde_json::to_vec(&item).map_err(|e| crate::Error::StringError(e.to_string()))?;
|
||||||
// let data = crypto::encrypt_data(&[], &data)?;
|
// let data = crypto::encrypt_data(&[], &data)?;
|
||||||
@@ -169,8 +163,8 @@ impl Store for ObjectStore {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if policy_doc.version == 0 {
|
if policy_doc.version == 0 {
|
||||||
policy_doc.create_date = object_info.mod_time.clone();
|
policy_doc.create_date = object_info.mod_time;
|
||||||
policy_doc.update_date = object_info.mod_time.clone();
|
policy_doc.update_date = object_info.mod_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.insert(name.to_str().unwrap().to_owned(), policy_doc);
|
result.insert(name.to_str().unwrap().to_owned(), policy_doc);
|
||||||
@@ -219,7 +213,7 @@ impl Store for ObjectStore {
|
|||||||
"policydb/groups/",
|
"policydb/groups/",
|
||||||
"service-accounts/",
|
"service-accounts/",
|
||||||
"policydb/sts-users/",
|
"policydb/sts-users/",
|
||||||
"sts",
|
"sts/",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -234,12 +228,13 @@ impl Store for ObjectStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 一次读取32个元素
|
// 一次读取32个元素
|
||||||
let mut iter = items
|
let iter = items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.trim_start_matches("config/iam/"))
|
.map(|item| item.trim_start_matches("config/iam/"))
|
||||||
.map(|item| split_path(item, item.starts_with("policydb/")))
|
.map(|item| split_path(item, item.starts_with("policydb/")))
|
||||||
.filter_map(|(list_key, trimmed_item)| {
|
.filter_map(|(list_key, trimmed_item)| {
|
||||||
debug!("list_key: {list_key}, trimmed_item: {trimmed_item}");
|
debug!("list_key: {list_key}, trimmed_item: {trimmed_item}");
|
||||||
|
|
||||||
if list_key == "format.json" {
|
if list_key == "format.json" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -255,13 +250,14 @@ impl Store for ObjectStore {
|
|||||||
Some(async move {
|
Some(async move {
|
||||||
match list_key {
|
match list_key {
|
||||||
"policies/" => {
|
"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?;
|
let policy_doc = self.load_policy(name).await?;
|
||||||
policy_docs.lock().await.insert(name.to_owned(), policy_doc);
|
policy_docs.lock().await.insert(name.to_owned(), policy_doc);
|
||||||
}
|
}
|
||||||
"users/" => {
|
"users/" => {
|
||||||
let name = dir(trimmed_item);
|
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);
|
users.lock().await.insert(name.to_owned(), user);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -276,7 +272,8 @@ impl Store for ObjectStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"service-accounts/" => {
|
"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 {
|
let Some(user) = self.load_user_identity(UserType::Svc, name).await? else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
@@ -299,7 +296,8 @@ impl Store for ObjectStore {
|
|||||||
}
|
}
|
||||||
"sts/" => {
|
"sts/" => {
|
||||||
let name = dir(trimmed_item);
|
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);
|
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);
|
let mut all_futures = Vec::with_capacity(32);
|
||||||
|
|
||||||
while let Some(f) = iter.next() {
|
for f in iter {
|
||||||
all_futures.push(f);
|
all_futures.push(f);
|
||||||
|
|
||||||
if all_futures.len() == 32 {
|
if all_futures.len() == 32 {
|
||||||
@@ -332,12 +330,30 @@ impl Store for ObjectStore {
|
|||||||
try_join_all(all_futures).await?;
|
try_join_all(all_futures).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::into_inner(users).map(|x| cache.users.store(Arc::new(x.into_inner().update_load_time())));
|
if let Some(x) = Arc::into_inner(users) {
|
||||||
Arc::into_inner(policy_docs).map(|x| cache.policy_docs.store(Arc::new(x.into_inner().update_load_time())));
|
cache.users.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(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(())
|
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
@@ -1,6 +1,7 @@
|
|||||||
use rand::{Rng, RngCore};
|
|
||||||
|
|
||||||
use crate::Error;
|
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> {
|
pub fn gen_access_key(length: usize) -> crate::Result<String> {
|
||||||
const ALPHA_NUMERIC_TABLE: [char; 36] = [
|
const ALPHA_NUMERIC_TABLE: [char; 36] = [
|
||||||
@@ -39,6 +40,11 @@ pub fn gen_secret_key(length: usize) -> crate::Result<String> {
|
|||||||
Ok(key_str)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{gen_access_key, gen_secret_key};
|
use super::{gen_access_key, gen_secret_key};
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ atoi = "2.0.0"
|
|||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
crypto = { path = "../crypto" }
|
crypto = { path = "../crypto" }
|
||||||
iam = { path = "../iam" }
|
iam = { path = "../iam" }
|
||||||
|
jsonwebtoken = "9.3.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build.workspace = true
|
prost-build.workspace = true
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use ecstore::GLOBAL_Endpoints;
|
|||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
|
use iam::get_global_action_cred;
|
||||||
use madmin::metrics::RealtimeMetrics;
|
use madmin::metrics::RealtimeMetrics;
|
||||||
use madmin::utils::parse_duration;
|
use madmin::utils::parse_duration;
|
||||||
use matchit::Params;
|
use matchit::Params;
|
||||||
@@ -47,6 +48,9 @@ use tracing::{error, info, warn};
|
|||||||
pub mod service_account;
|
pub mod service_account;
|
||||||
pub mod trace;
|
pub mod trace;
|
||||||
|
|
||||||
|
const ASSUME_ROLE_ACTION: &str = "AssumeRole";
|
||||||
|
const ASSUME_ROLE_VERSION: &str = "2011-06-15";
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
#[serde(rename_all = "PascalCase", default)]
|
#[serde(rename_all = "PascalCase", default)]
|
||||||
pub struct AssumeRoleRequest {
|
pub struct AssumeRoleRequest {
|
||||||
@@ -85,13 +89,30 @@ pub struct AssumeRoleRequest {
|
|||||||
// pub parent_user: String,
|
// pub parent_user: String,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Default)]
|
||||||
|
pub struct STSClaims {
|
||||||
|
parent: String,
|
||||||
|
exp: usize,
|
||||||
|
access_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_signing_key() -> Option<String> {
|
||||||
|
if let Some(s) = get_global_action_cred() {
|
||||||
|
Some(s.secret_key.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AssumeRoleHandle {}
|
pub struct AssumeRoleHandle {}
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Operation for AssumeRoleHandle {
|
impl Operation for AssumeRoleHandle {
|
||||||
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 AssumeRoleHandle");
|
warn!("handle AssumeRoleHandle");
|
||||||
|
|
||||||
let Some(cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
||||||
|
|
||||||
|
// TODO: 判断权限, 不允许sts访问
|
||||||
|
|
||||||
let mut input = req.input;
|
let mut input = req.input;
|
||||||
|
|
||||||
@@ -100,17 +121,67 @@ impl Operation for AssumeRoleHandle {
|
|||||||
};
|
};
|
||||||
let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
|
let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
|
||||||
|
|
||||||
warn!("AssumeRole get body {:?}", body);
|
if body.action.as_str() != ASSUME_ROLE_ACTION {
|
||||||
|
return Err(s3_error!(InvalidArgument, "not suport action"));
|
||||||
|
}
|
||||||
|
|
||||||
let exp = OffsetDateTime::now_utc().saturating_add(Duration::days(1));
|
if body.version.as_str() != ASSUME_ROLE_VERSION {
|
||||||
|
return Err(s3_error!(InvalidArgument, "not suport version"));
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!("AssumeRole get cred {:?}", &user);
|
||||||
|
warn!("AssumeRole get body {:?}", &body);
|
||||||
|
|
||||||
|
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
||||||
|
|
||||||
|
if let Err(_err) = iam_store.policy_db_get(&user.access_key, None).await {
|
||||||
|
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(secret) = get_token_signing_key() else {
|
||||||
|
return Err(s3_error!(InvalidArgument, "sk not init"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let exp = {
|
||||||
|
if body.duration_seconds > 0 {
|
||||||
|
body.duration_seconds
|
||||||
|
} else {
|
||||||
|
3600
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut claims = STSClaims {
|
||||||
|
parent: user.access_key.clone(),
|
||||||
|
exp,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ak = iam::utils::gen_access_key(20).unwrap_or_default();
|
||||||
|
let sk = iam::utils::gen_secret_key(32).unwrap_or_default();
|
||||||
|
|
||||||
|
claims.access_key = ak.clone();
|
||||||
|
|
||||||
|
let mut cred = match iam::auth::Credentials::create_new_credentials_with_metadata(&ak, &sk, &claims, &secret, Some(exp)) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_er) => return Err(s3_error!(InvalidRequest, "")),
|
||||||
|
};
|
||||||
|
|
||||||
|
cred.parent_user = user.access_key.clone();
|
||||||
|
|
||||||
|
if let Err(err) = iam_store.set_temp_user(&cred.access_key, &cred, "").await {
|
||||||
|
error!("set_temp_user err {:?}", err);
|
||||||
|
return Err(s3_error!(InternalError, "set_temp_user failed"));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: create tmp access_key
|
|
||||||
let resp = AssumeRoleOutput {
|
let resp = AssumeRoleOutput {
|
||||||
credentials: Some(Credentials {
|
credentials: Some(Credentials {
|
||||||
access_key_id: cred.access_key,
|
access_key_id: cred.access_key,
|
||||||
expiration: Timestamp::from(exp),
|
expiration: Timestamp::from(
|
||||||
secret_access_key: cred.secret_key.expose().to_string(),
|
cred.expiration
|
||||||
session_token: "sdfsdf".to_owned(),
|
.unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))),
|
||||||
|
),
|
||||||
|
secret_access_key: cred.secret_key,
|
||||||
|
session_token: cred.session_token,
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -119,8 +190,6 @@ impl Operation for AssumeRoleHandle {
|
|||||||
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
|
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
|
||||||
|
|
||||||
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
|
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
|
||||||
|
|
||||||
// return Err(s3_error!(NotImplemented));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use iam::cache::CacheInner;
|
||||||
|
use s3s::auth::S3Auth;
|
||||||
|
use s3s::auth::SecretKey;
|
||||||
|
use s3s::auth::SimpleAuth;
|
||||||
|
use s3s::s3_error;
|
||||||
|
use s3s::S3Result;
|
||||||
|
|
||||||
|
pub struct IAMAuth {
|
||||||
|
simple_auth: SimpleAuth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IAMAuth {
|
||||||
|
pub fn new(ak: impl Into<String>, sk: impl Into<SecretKey>) -> Self {
|
||||||
|
let simple_auth = SimpleAuth::from_single(ak, sk);
|
||||||
|
Self { simple_auth }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl S3Auth for IAMAuth {
|
||||||
|
async fn get_secret_key(&self, access_key: &str) -> S3Result<SecretKey> {
|
||||||
|
if access_key.is_empty() {
|
||||||
|
return Err(s3_error!(NotSignedUp, "Your account is not signed up"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(key) = self.simple_auth.get_secret_key(access_key).await {
|
||||||
|
return Ok(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(iam_store) = iam::get() {
|
||||||
|
let c = CacheInner::from(&iam_store.cache);
|
||||||
|
if let Some(id) = c.get_user(access_key) {
|
||||||
|
return Ok(SecretKey::from(id.credentials.secret_key.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(s3_error!(NotSignedUp, "Your account is not signed up2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-3
@@ -1,8 +1,10 @@
|
|||||||
mod admin;
|
mod admin;
|
||||||
|
mod auth;
|
||||||
mod config;
|
mod config;
|
||||||
mod grpc;
|
mod grpc;
|
||||||
mod service;
|
mod service;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
use crate::auth::IAMAuth;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use common::{
|
use common::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
@@ -26,7 +28,7 @@ use hyper_util::{
|
|||||||
};
|
};
|
||||||
use iam::init_iam_sys;
|
use iam::init_iam_sys;
|
||||||
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
|
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
|
||||||
use s3s::{auth::SimpleAuth, service::S3ServiceBuilder};
|
use s3s::service::S3ServiceBuilder;
|
||||||
use service::hybrid;
|
use service::hybrid;
|
||||||
use std::{io::IsTerminal, net::SocketAddr, str::FromStr};
|
use std::{io::IsTerminal, net::SocketAddr, str::FromStr};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
@@ -87,6 +89,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
|
|
||||||
debug!("server_address {}", &server_address);
|
debug!("server_address {}", &server_address);
|
||||||
|
|
||||||
|
iam::init_global_action_cred(None, None).unwrap();
|
||||||
set_global_rustfs_port(server_port);
|
set_global_rustfs_port(server_port);
|
||||||
|
|
||||||
//监听地址,端口从参数中获取
|
//监听地址,端口从参数中获取
|
||||||
@@ -133,7 +136,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
}
|
}
|
||||||
//显示info信息
|
//显示info信息
|
||||||
info!("authentication is enabled {}, {}", &access_key, &secret_key);
|
info!("authentication is enabled {}, {}", &access_key, &secret_key);
|
||||||
b.set_auth(SimpleAuth::from_single(access_key, secret_key));
|
b.set_auth(IAMAuth::new(access_key, secret_key));
|
||||||
|
|
||||||
b.set_access(store.clone());
|
b.set_access(store.clone());
|
||||||
|
|
||||||
@@ -212,6 +215,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
warn!(" init store success!");
|
warn!(" init store success!");
|
||||||
|
|
||||||
|
init_iam_sys(store.clone()).await.unwrap();
|
||||||
|
|
||||||
new_global_notification_sys(endpoint_pools.clone()).await.map_err(|err| {
|
new_global_notification_sys(endpoint_pools.clone()).await.map_err(|err| {
|
||||||
error!("new_global_notification_sys faild {:?}", &err);
|
error!("new_global_notification_sys faild {:?}", &err);
|
||||||
Error::from_string(err.to_string())
|
Error::from_string(err.to_string())
|
||||||
@@ -221,7 +226,6 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
init_data_scanner().await;
|
init_data_scanner().await;
|
||||||
// init auto heal
|
// init auto heal
|
||||||
init_auto_heal().await;
|
init_auto_heal().await;
|
||||||
init_iam_sys(store.clone()).await.unwrap();
|
|
||||||
|
|
||||||
info!("server was started");
|
info!("server was started");
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ mkdir -p ./target/volume/test{0..4}
|
|||||||
|
|
||||||
|
|
||||||
if [ -z "$RUST_LOG" ]; then
|
if [ -z "$RUST_LOG" ]; then
|
||||||
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,reader=debug,router=debug"
|
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,iam=debug"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# export RUSTFS_ERASURE_SET_DRIVE_COUNT=5
|
# export RUSTFS_ERASURE_SET_DRIVE_COUNT=5
|
||||||
|
|||||||
Reference in New Issue
Block a user