Files
rustfs/crates/iam/src/manager.rs
yxrxy b8aa8214e2 Feat/ftps&sftp (#1308)
[feat] ftp / sftp
2025-12-31 09:01:15 +08:00

2218 lines
76 KiB
Rust

// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::error::{Error, Result, is_err_config_not_found};
use crate::sys::get_claims_from_token_with_secret;
use crate::{
cache::{Cache, CacheEntity},
error::{Error as IamError, is_err_no_such_group, is_err_no_such_policy, is_err_no_such_user},
store::{GroupInfo, MappedPolicy, Store, UserType, object::IAM_CONFIG_PREFIX},
sys::{
MAX_SVCSESSION_POLICY_SIZE, SESSION_POLICY_NAME, SESSION_POLICY_NAME_EXTRACTED, STATUS_DISABLED, STATUS_ENABLED,
UpdateServiceAccountOpts,
},
};
use futures::future::join_all;
use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred};
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
use rustfs_policy::{
arn::ARN,
auth::{self, UserIdentity, is_secret_key_valid, jwt_sign},
format::Format,
policy::{Policy, PolicyDoc, default::DEFAULT_POLICIES, iam_policy_claim_name_sa},
};
use rustfs_utils::path::path_join_buf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::atomic::AtomicU8;
use std::{
collections::{HashMap, HashSet},
sync::{
Arc,
atomic::{AtomicBool, AtomicI64, Ordering},
},
time::Duration,
};
use time::OffsetDateTime;
use tokio::{
select,
sync::{
mpsc,
mpsc::{Receiver, Sender},
},
};
use tracing::warn;
use tracing::{error, info};
const IAM_FORMAT_FILE: &str = "format.json";
const IAM_FORMAT_VERSION_1: i32 = 1;
#[derive(Serialize, Deserialize)]
struct IAMFormat {
version: i32,
}
impl IAMFormat {
fn new_version_1() -> Self {
IAMFormat {
version: IAM_FORMAT_VERSION_1,
}
}
}
fn get_iam_format_file_path() -> String {
path_join_buf(&[&IAM_CONFIG_PREFIX, IAM_FORMAT_FILE])
}
#[repr(u8)]
#[derive(Debug, PartialEq)]
pub enum IamState {
Uninitialized = 0,
Loading = 1,
Ready = 2,
Error = 3,
}
pub struct IamCache<T> {
pub cache: Cache,
pub api: T,
pub state: Arc<AtomicU8>,
pub loading: Arc<AtomicBool>,
pub roles: HashMap<ARN, Vec<String>>,
pub send_chan: Sender<i64>,
pub last_timestamp: AtomicI64,
}
impl<T> IamCache<T>
where
T: Store,
{
/// Create a new IAM system instance
/// # Arguments
/// * `api` - The storage backend implementing the Store trait
///
/// # Returns
/// An Arc-wrapped instance of IamSystem
pub(crate) async fn new(api: T) -> Arc<Self> {
let (sender, receiver) = mpsc::channel::<i64>(100);
let sys = Arc::new(Self {
api,
cache: Cache::default(),
state: Arc::new(AtomicU8::new(IamState::Uninitialized as u8)),
loading: Arc::new(AtomicBool::new(false)),
send_chan: sender,
roles: HashMap::new(),
last_timestamp: AtomicI64::new(0),
});
sys.clone().init(receiver).await.unwrap();
sys
}
/// Initialize the IAM system
async fn init(self: Arc<Self>, receiver: Receiver<i64>) -> Result<()> {
self.state.store(IamState::Loading as u8, Ordering::SeqCst);
// Ensure the IAM format file is persisted first
self.clone().save_iam_formatter().await?;
// Critical: Load all existing users/policies into memory cache
const MAX_RETRIES: usize = 3;
for attempt in 0..MAX_RETRIES {
if let Err(e) = self.clone().load().await {
if attempt == MAX_RETRIES - 1 {
self.state.store(IamState::Error as u8, Ordering::SeqCst);
error!("IAM fail to load initial data after {} attempts: {:?}", MAX_RETRIES, e);
return Err(e);
} else {
warn!("IAM load failed, retrying... attempt {}", attempt + 1);
tokio::time::sleep(Duration::from_secs(1)).await;
}
} else {
break;
}
}
self.state.store(IamState::Ready as u8, Ordering::SeqCst);
info!("IAM System successfully initialized and marked as READY");
// Background ticker for synchronization
// Check if environment variable is set
let skip_background_task = std::env::var("RUSTFS_SKIP_BACKGROUND_TASK").is_ok();
if !skip_background_task {
// Background thread starts periodic updates or receives signal updates
tokio::spawn({
let s = Arc::clone(&self);
async move {
let ticker = tokio::time::interval(Duration::from_secs(120));
tokio::pin!(ticker, receiver);
loop {
select! {
_ = ticker.tick() => {
info!("iam load ticker");
if let Err(err) =s.clone().load().await{
error!("iam load err {:?}", err);
}
},
i = receiver.recv() => {
info!("iam load receiver");
match i {
Some(t) => {
let last = s.last_timestamp.load(Ordering::Relaxed);
if last <= t {
info!("iam load receiver load");
if let Err(err) =s.clone().load().await{
error!("iam load err {:?}", err);
}
ticker.reset();
}
},
None => return,
}
}
}
}
}
});
}
Ok(())
}
/// Check if IAM system is ready
pub fn is_ready(&self) -> bool {
self.state.load(Ordering::SeqCst) == IamState::Ready as u8
}
async fn _notify(&self) {
self.send_chan.send(OffsetDateTime::now_utc().unix_timestamp()).await.unwrap();
}
async fn load(self: Arc<Self>) -> Result<()> {
// debug!("load iam to cache");
self.api.load_all(&self.cache).await?;
self.last_timestamp
.store(OffsetDateTime::now_utc().unix_timestamp(), Ordering::Relaxed);
Ok(())
}
pub async fn load_user(&self, access_key: &str) -> Result<()> {
let mut users_map: HashMap<String, UserIdentity> = HashMap::new();
let mut user_policy_map = HashMap::new();
let mut sts_users_map = HashMap::new();
let mut sts_policy_map = HashMap::new();
let mut policy_docs_map = HashMap::new();
let _ = self.api.load_user(access_key, UserType::Svc, &mut users_map).await;
let parent_user = users_map.get(access_key).map(|svc| svc.credentials.parent_user.clone());
if let Some(parent_user) = parent_user {
let _ = self.api.load_user(&parent_user, UserType::Reg, &mut users_map).await;
let _ = self
.api
.load_mapped_policy(&parent_user, UserType::Reg, false, &mut user_policy_map)
.await;
} else {
let _ = self.api.load_user(access_key, UserType::Reg, &mut users_map).await;
if users_map.contains_key(access_key) {
let _ = self
.api
.load_mapped_policy(access_key, UserType::Reg, false, &mut user_policy_map)
.await;
}
let _ = self.api.load_user(access_key, UserType::Sts, &mut sts_users_map).await;
let has_sts_user = sts_users_map.get(access_key);
let sts_parent = has_sts_user.map(|sts| sts.credentials.parent_user.clone());
if let Some(parent) = sts_parent {
let _ = self
.api
.load_mapped_policy(&parent, UserType::Sts, false, &mut sts_policy_map)
.await;
}
let sts_user = has_sts_user.map(|sts| sts.credentials.access_key.clone());
if let Some(ref sts) = sts_user {
if let Some(plc) = sts_policy_map.get(sts) {
for p in plc.to_slice().iter() {
if !policy_docs_map.contains_key(p) {
let _ = self.api.load_policy_doc(p, &mut policy_docs_map).await;
}
}
}
}
}
if let Some(plc) = user_policy_map.get(access_key) {
for p in plc.to_slice().iter() {
if !policy_docs_map.contains_key(p) {
let _ = self.api.load_policy_doc(p, &mut policy_docs_map).await;
}
}
}
if let Some(user) = users_map.get(access_key) {
Cache::add_or_update(&self.cache.users, access_key, user, OffsetDateTime::now_utc());
}
if let Some(user_policy) = user_policy_map.get(access_key) {
Cache::add_or_update(&self.cache.user_policies, access_key, user_policy, OffsetDateTime::now_utc());
}
if let Some(sts_user) = sts_users_map.get(access_key) {
Cache::add_or_update(&self.cache.sts_accounts, access_key, sts_user, OffsetDateTime::now_utc());
}
if let Some(sts_policy) = sts_policy_map.get(access_key) {
Cache::add_or_update(&self.cache.sts_policies, access_key, sts_policy, OffsetDateTime::now_utc());
}
if let Some(policy_doc) = policy_docs_map.get(access_key) {
Cache::add_or_update(&self.cache.policy_docs, access_key, policy_doc, OffsetDateTime::now_utc());
}
Ok(())
}
// TODO: Check if exists, whether retry is possible
#[tracing::instrument(level = "debug", skip(self))]
async fn save_iam_formatter(self: Arc<Self>) -> Result<()> {
let path = get_iam_format_file_path();
let iam_fmt: Format = match self.api.load_iam_config(&path).await {
Ok(v) => v,
Err(err) => {
if !is_err_config_not_found(&err) {
return Err(err);
}
Format::default()
}
};
if iam_fmt.version >= IAM_FORMAT_VERSION_1 {
return Ok(());
}
self.api.save_iam_config(IAMFormat::new_version_1(), path).await
}
pub async fn get_user(&self, access_key: &str) -> Option<UserIdentity> {
self.cache
.users
.load()
.get(access_key)
.cloned()
.or_else(|| self.cache.sts_accounts.load().get(access_key).cloned())
}
pub async fn get_mapped_policy(&self, name: &str, is_group: bool) -> Option<MappedPolicy> {
if is_group {
self.cache.group_policies.load().get(name).cloned()
} else {
self.cache.user_policies.load().get(name).cloned()
}
}
pub async fn get_policy(&self, name: &str) -> Result<Policy> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
let policies = MappedPolicy::new(name).to_slice();
let mut to_merge = Vec::new();
for policy in policies {
if policy.is_empty() {
continue;
}
let v = self
.cache
.policy_docs
.load()
.get(&policy)
.cloned()
.ok_or(Error::NoSuchPolicy)?;
to_merge.push(v.policy);
}
if to_merge.is_empty() {
return Err(Error::NoSuchPolicy);
}
Ok(Policy::merge_policies(to_merge))
}
pub async fn get_policy_doc(&self, name: &str) -> Result<PolicyDoc> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
self.cache.policy_docs.load().get(name).cloned().ok_or(Error::NoSuchPolicy)
}
pub async fn delete_policy(&self, name: &str, is_from_notify: bool) -> Result<()> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
if is_from_notify {
let user_policy_cache = self.cache.user_policies.load();
let group_policy_cache = self.cache.group_policies.load();
let users_cache = self.cache.users.load();
let mut users = Vec::new();
user_policy_cache.iter().for_each(|(k, v)| {
if !users_cache.contains_key(k) {
Cache::delete(&self.cache.user_policies, k, OffsetDateTime::now_utc());
return;
}
if v.policy_set().contains(name) {
users.push(k.to_owned());
}
});
let mut groups = Vec::new();
group_policy_cache.iter().for_each(|(k, v)| {
if v.policy_set().contains(name) {
groups.push(k.to_owned());
}
});
if !users.is_empty() || !groups.is_empty() {
return Err(Error::PolicyInUse);
}
if let Err(err) = self.api.delete_policy_doc(name).await {
if !is_err_no_such_policy(&err) {
Cache::delete(&self.cache.policy_docs, name, OffsetDateTime::now_utc());
return Ok(());
}
return Err(err);
}
}
Cache::delete(&self.cache.policy_docs, name, OffsetDateTime::now_utc());
Ok(())
}
pub async fn set_policy(&self, name: &str, policy: Policy) -> Result<OffsetDateTime> {
if name.is_empty() || policy.is_empty() {
return Err(Error::InvalidArgument);
}
let policy_doc = self
.cache
.policy_docs
.load()
.get(name)
.map(|v| {
let mut p = v.clone();
p.update(policy.clone());
p
})
.unwrap_or(PolicyDoc::new(policy));
self.api.save_policy_doc(name, policy_doc.clone()).await?;
let now = OffsetDateTime::now_utc();
Cache::add_or_update(&self.cache.policy_docs, name, &policy_doc, now);
Ok(now)
}
pub async fn list_polices(&self, bucket_name: &str) -> Result<HashMap<String, Policy>> {
let mut m = HashMap::new();
self.api.load_policy_docs(&mut m).await?;
set_default_canned_policies(&mut m);
let cache = CacheEntity::new(m.clone()).update_load_time();
self.cache.policy_docs.store(Arc::new(cache));
let items: Vec<_> = m.into_iter().map(|(k, v)| (k, v.policy.clone())).collect();
let futures: Vec<_> = items.iter().map(|(_, policy)| policy.match_resource(bucket_name)).collect();
let results = join_all(futures).await;
let filtered = items
.into_iter()
.zip(results)
.filter_map(|((k, policy), matches)| {
if bucket_name.is_empty() || matches {
Some((k, policy))
} else {
None
}
})
.collect();
Ok(filtered)
}
pub async fn merge_policies(&self, name: &str) -> (String, Policy) {
let mut policies = Vec::new();
let mut to_merge = Vec::new();
let mut miss_policies = Vec::new();
for policy in MappedPolicy::new(name).to_slice() {
if policy.is_empty() {
continue;
}
if let Some(v) = self.cache.policy_docs.load().get(&policy).cloned() {
policies.push(policy);
to_merge.push(v.policy);
} else {
miss_policies.push(policy);
}
}
if !miss_policies.is_empty() {
let mut m = HashMap::new();
for policy in miss_policies {
let _ = self.api.load_policy_doc(&policy, &mut m).await;
}
for (k, v) in m.iter() {
Cache::add_or_update(&self.cache.policy_docs, k, v, OffsetDateTime::now_utc());
policies.push(k.clone());
to_merge.push(v.policy.clone());
}
}
(policies.join(","), Policy::merge_policies(to_merge))
}
pub async fn list_policy_docs(&self, bucket_name: &str) -> Result<HashMap<String, PolicyDoc>> {
let mut m = HashMap::new();
self.api.load_policy_docs(&mut m).await?;
set_default_canned_policies(&mut m);
let cache = CacheEntity::new(m.clone()).update_load_time();
self.cache.policy_docs.store(Arc::new(cache));
let items: Vec<_> = m.into_iter().map(|(k, v)| (k, v.clone())).collect();
let futures: Vec<_> = items
.iter()
.map(|(_, policy_doc)| policy_doc.policy.match_resource(bucket_name))
.collect();
let results = join_all(futures).await;
let filtered = items
.into_iter()
.zip(results)
.filter_map(|((k, policy_doc), matches)| {
if bucket_name.is_empty() || matches {
Some((k, policy_doc))
} else {
None
}
})
.collect();
Ok(filtered)
}
pub async fn list_policy_docs_internal(&self, bucket_name: &str) -> Result<HashMap<String, PolicyDoc>> {
let cache = self.cache.policy_docs.load();
let items: Vec<_> = cache.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let futures: Vec<_> = items
.iter()
.map(|(_, policy_doc)| policy_doc.policy.match_resource(bucket_name))
.collect();
let results = join_all(futures).await;
let ret = items
.into_iter()
.zip(results)
.filter_map(|((k, policy_doc), matches)| {
if bucket_name.is_empty() || matches {
Some((k, policy_doc))
} else {
None
}
})
.collect();
Ok(ret)
}
pub async fn list_temp_accounts(&self, access_key: &str) -> Result<Vec<UserIdentity>> {
let users = self.cache.users.load();
let mut user_exists = false;
let mut ret = Vec::new();
for (_, v) in users.iter() {
let is_derived = v.credentials.is_service_account() || v.credentials.is_temp();
if !is_derived && v.credentials.access_key.as_str() == access_key {
user_exists = true;
} else if is_derived && v.credentials.parent_user == access_key {
user_exists = true;
if v.credentials.is_temp() {
let mut u = v.clone();
u.credentials.secret_key = String::new();
u.credentials.session_token = String::new();
ret.push(u);
}
}
}
if !user_exists {
return Err(Error::NoSuchUser(access_key.to_string()));
}
Ok(ret)
}
pub async fn list_sts_accounts(&self, access_key: &str) -> Result<Vec<Credentials>> {
let users = self.cache.users.load();
Ok(users
.values()
.filter_map(|x| {
if !access_key.is_empty() && x.credentials.parent_user.as_str() == access_key && x.credentials.is_temp() {
let mut c = x.credentials.clone();
c.secret_key = String::new();
c.session_token = String::new();
return Some(c);
}
None
})
.collect())
}
pub async fn list_service_accounts(&self, access_key: &str) -> Result<Vec<Credentials>> {
let users = self.cache.users.load();
Ok(users
.values()
.filter_map(|x| {
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
})
.collect())
}
/// create a service account and update cache
pub async fn add_service_account(&self, cred: Credentials) -> Result<OffsetDateTime> {
if cred.access_key.is_empty() || cred.parent_user.is_empty() {
return Err(Error::InvalidArgument);
}
let users = self.cache.users.load();
if let Some(x) = users.get(&cred.access_key) {
if x.credentials.is_service_account() {
return Err(Error::IAMActionNotAllowed);
}
}
let u = UserIdentity::new(cred);
self.api
.save_user_identity(&u.credentials.access_key, UserType::Svc, u.clone(), None)
.await?;
self.update_user_with_claims(&u.credentials.access_key, u.clone())?;
Ok(OffsetDateTime::now_utc())
}
pub async fn update_service_account(&self, name: &str, opts: UpdateServiceAccountOpts) -> Result<OffsetDateTime> {
let Some(ui) = self.cache.users.load().get(name).cloned() else {
return Err(Error::NoSuchServiceAccount(name.to_string()));
};
if !ui.credentials.is_service_account() {
return Err(Error::NoSuchServiceAccount(name.to_string()));
}
let mut cr = ui.credentials.clone();
let current_secret_key = cr.secret_key.clone();
if let Some(secret) = opts.secret_key {
if !is_secret_key_valid(&secret) {
return Err(Error::InvalidSecretKeyLength);
}
cr.secret_key = secret;
}
if opts.name.is_some() {
cr.name = opts.name;
}
if opts.description.is_some() {
cr.description = opts.description;
}
if opts.expiration.is_some() {
// TODO: check expiration
cr.expiration = opts.expiration;
}
if let Some(status) = opts.status {
match status.as_str() {
val if val == AccountStatus::Enabled.as_ref() => cr.status = auth::ACCOUNT_ON.to_owned(),
val if val == AccountStatus::Disabled.as_ref() => cr.status = auth::ACCOUNT_OFF.to_owned(),
auth::ACCOUNT_ON => cr.status = auth::ACCOUNT_ON.to_owned(),
auth::ACCOUNT_OFF => cr.status = auth::ACCOUNT_OFF.to_owned(),
_ => cr.status = auth::ACCOUNT_OFF.to_owned(),
}
}
let mut m: HashMap<String, Value> = get_claims_from_token_with_secret(&cr.session_token, &current_secret_key)?;
m.remove(SESSION_POLICY_NAME_EXTRACTED);
let nosp = if let Some(policy) = &opts.session_policy {
policy.version.is_empty() && policy.statements.is_empty()
} else {
false
};
if m.contains_key(SESSION_POLICY_NAME) && nosp {
m.remove(SESSION_POLICY_NAME);
m.insert(iam_policy_claim_name_sa(), Value::String(INHERITED_POLICY_TYPE.to_owned()));
}
if let Some(session_policy) = &opts.session_policy {
session_policy.validate()?;
if !session_policy.version.is_empty() && !session_policy.statements.is_empty() {
let policy_buf = serde_json::to_vec(&session_policy)?;
if policy_buf.len() > MAX_SVCSESSION_POLICY_SIZE {
return Err(Error::PolicyTooLarge);
}
m.insert(
SESSION_POLICY_NAME.to_owned(),
Value::String(base64_simd::URL_SAFE_NO_PAD.encode_to_string(&policy_buf)),
);
m.insert(iam_policy_claim_name_sa(), Value::String(EMBEDDED_POLICY_TYPE.to_owned()));
}
}
m.insert("accessKey".to_owned(), Value::String(name.to_owned()));
cr.session_token = jwt_sign(&m, &cr.secret_key)?;
let u = UserIdentity::new(cr);
self.api
.save_user_identity(&u.credentials.access_key, UserType::Svc, u.clone(), None)
.await?;
self.update_user_with_claims(&u.credentials.access_key, u.clone())?;
Ok(OffsetDateTime::now_utc())
}
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
let (mut policies, _) = self.policy_db_get_internal(name, false, false).await?;
let present = !policies.is_empty();
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)
}
async fn policy_db_get_internal(
&self,
name: &str,
is_group: bool,
policy_present: bool,
) -> Result<(Vec<String>, OffsetDateTime)> {
if is_group {
let groups = self.cache.groups.load();
let g = match groups.get(name) {
Some(p) => p.clone(),
None => {
let mut m = HashMap::new();
self.api.load_group(name, &mut m).await?;
if let Some(p) = m.get(name) {
Cache::add_or_update(&self.cache.groups, name, p, OffsetDateTime::now_utc());
}
m.get(name).cloned().ok_or(Error::NoSuchGroup(name.to_string()))?
}
};
if g.status == STATUS_DISABLED {
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
if let Some(policy) = self.cache.group_policies.load().get(name) {
return Ok((policy.to_slice(), policy.update_at));
}
if !policy_present {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(name, UserType::Reg, true, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Some(p) = m.get(name) {
Cache::add_or_update(&self.cache.group_policies, name, p, OffsetDateTime::now_utc());
return Ok((p.to_slice(), p.update_at));
}
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
let users = self.cache.users.load();
let u = users.get(name).cloned().unwrap_or_default();
if !u.credentials.is_valid() {
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
let mp = match self.cache.user_policies.load().get(name) {
Some(p) => p.clone(),
None => {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(name, UserType::Reg, false, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Some(p) = m.get(name) {
Cache::add_or_update(&self.cache.user_policies, name, p, OffsetDateTime::now_utc());
p.clone()
} else {
match self.cache.sts_policies.load().get(name) {
Some(p) => p.clone(),
None => {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(name, UserType::Sts, false, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Some(p) = m.get(name) {
Cache::add_or_update(&self.cache.sts_policies, name, p, OffsetDateTime::now_utc());
p.clone()
} else {
MappedPolicy::default()
}
}
}
}
}
};
let mut policies: HashSet<String> = mp.to_slice().into_iter().collect();
if let Some(groups) = u.credentials.groups.as_ref() {
for group in groups.iter() {
if self
.cache
.groups
.load()
.get(group)
.filter(|v| v.status == STATUS_DISABLED)
.is_some()
{
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
let mp = match self.cache.group_policies.load().get(group) {
Some(p) => p.clone(),
None => {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(group, UserType::Reg, true, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Some(p) = m.get(group) {
Cache::add_or_update(&self.cache.group_policies, group, p, OffsetDateTime::now_utc());
p.clone()
} else {
MappedPolicy::default()
}
}
};
mp.to_slice().iter().for_each(|v| {
policies.insert(v.clone());
});
}
}
let update_at = mp.update_at;
for group in self
.cache
.user_group_memberships
.load()
.get(name)
.cloned()
.unwrap_or_default()
.iter()
{
if self
.cache
.groups
.load()
.get(group)
.filter(|v| v.status == STATUS_DISABLED)
.is_some()
{
return Ok((Vec::new(), OffsetDateTime::now_utc()));
}
let mp = match self.cache.group_policies.load().get(group) {
Some(p) => p.clone(),
None => {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(group, UserType::Reg, true, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Some(p) = m.get(group) {
Cache::add_or_update(&self.cache.group_policies, group, p, OffsetDateTime::now_utc());
p.clone()
} else {
MappedPolicy::default()
}
}
};
mp.to_slice().iter().for_each(|v| {
policies.insert(v.clone());
});
}
Ok((policies.into_iter().collect(), update_at))
}
pub async fn policy_db_set(&self, name: &str, user_type: UserType, is_group: bool, policy: &str) -> Result<OffsetDateTime> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
if policy.is_empty() {
if let Err(err) = self.api.delete_mapped_policy(name, user_type, is_group).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if is_group {
Cache::delete(&self.cache.group_policies, name, OffsetDateTime::now_utc());
} else if user_type == UserType::Sts {
Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc());
} else {
Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc());
}
return Ok(OffsetDateTime::now_utc());
}
let mp = MappedPolicy::new(policy);
let policy_docs_cache = self.cache.policy_docs.load();
for p in mp.to_slice() {
if !policy_docs_cache.contains_key(&p) {
return Err(Error::NoSuchPolicy);
}
}
self.api
.save_mapped_policy(name, user_type, is_group, mp.clone(), None)
.await?;
if is_group {
Cache::add_or_update(&self.cache.group_policies, name, &mp, OffsetDateTime::now_utc());
} else if user_type == UserType::Sts {
Cache::add_or_update(&self.cache.sts_policies, name, &mp, OffsetDateTime::now_utc());
} else {
Cache::add_or_update(&self.cache.user_policies, name, &mp, OffsetDateTime::now_utc());
}
Ok(OffsetDateTime::now_utc())
}
pub async fn set_temp_user(&self, access_key: &str, cred: &Credentials, policy_name: Option<&str>) -> Result<OffsetDateTime> {
if access_key.is_empty() || !cred.is_temp() || cred.is_expired() || cred.parent_user.is_empty() {
error!(
"set temp user invalid argument, access_key: {}, is_temp: {}, is_expired: {}, parent_user_empty: {}",
access_key,
cred.is_temp(),
cred.is_expired(),
cred.parent_user.is_empty()
);
return Err(Error::InvalidArgument);
}
if let Some(policy) = policy_name {
let mp = MappedPolicy::new(policy);
let (_, combined_policy_stmt) = filter_policies(&self.cache, &mp.policies, "temp");
if combined_policy_stmt.is_empty() {
return Err(Error::other(format!("Required policy not found: {}", IamError::NoSuchPolicy)));
}
self.api
.save_mapped_policy(&cred.parent_user, UserType::Sts, false, mp.clone(), None)
.await?;
Cache::add_or_update(&self.cache.sts_policies, &cred.parent_user, &mp, OffsetDateTime::now_utc());
}
let u = UserIdentity::new(cred.clone());
self.api
.save_user_identity(access_key, UserType::Sts, u.clone(), None)
.await?;
Cache::add_or_update(&self.cache.sts_accounts, access_key, &u, OffsetDateTime::now_utc());
Ok(OffsetDateTime::now_utc())
}
pub async fn get_user_info(&self, name: &str) -> Result<rustfs_madmin::UserInfo> {
let users = self.cache.users.load();
let policies = self.cache.user_policies.load();
let group_members = self.cache.user_group_memberships.load();
let u = match users.get(name) {
Some(u) => u,
None => return Err(Error::NoSuchUser(name.to_string())),
};
if u.credentials.is_temp() || u.credentials.is_service_account() {
return Err(Error::IAMActionNotAllowed);
}
let mut uinfo = rustfs_madmin::UserInfo {
status: if u.credentials.is_valid() {
AccountStatus::Enabled
} else {
AccountStatus::Disabled
},
updated_at: u.update_at,
..Default::default()
};
if let Some(p) = policies.get(name) {
uinfo.policy_name = Some(p.policies.clone());
uinfo.updated_at = Some(p.update_at);
}
if let Some(members) = group_members.get(name) {
uinfo.member_of = Some(members.iter().cloned().collect());
}
Ok(uinfo)
}
// returns all users (not STS or service accounts)
pub async fn get_users(&self) -> Result<HashMap<String, rustfs_madmin::UserInfo>> {
let mut m = HashMap::new();
let users = self.cache.users.load();
let policies = self.cache.user_policies.load();
let group_members = self.cache.user_group_memberships.load();
for (k, v) in users.iter() {
if v.credentials.is_temp() || v.credentials.is_service_account() {
continue;
}
let mut u = rustfs_madmin::UserInfo {
status: if v.credentials.is_valid() {
AccountStatus::Enabled
} else {
AccountStatus::Disabled
},
updated_at: v.update_at,
..Default::default()
};
if let Some(p) = policies.get(k) {
u.policy_name = Some(p.policies.clone());
u.updated_at = Some(p.update_at);
}
if let Some(members) = group_members.get(k) {
u.member_of = Some(members.iter().cloned().collect());
}
m.insert(k.clone(), u);
}
Ok(m)
}
pub async fn get_bucket_users(&self, bucket_name: &str) -> Result<HashMap<String, rustfs_madmin::UserInfo>> {
let users = self.cache.users.load();
let policies_cache = self.cache.user_policies.load();
let group_members = self.cache.user_group_memberships.load();
let group_policy_cache = self.cache.group_policies.load();
let mut ret = HashMap::new();
for (k, v) in users.iter() {
if v.credentials.is_temp() || v.credentials.is_service_account() {
continue;
}
let mut policies = Vec::new();
if let Some(p) = policies_cache.get(k) {
policies.push(p.policies.clone());
if let Some(groups) = group_members.get(k) {
for group in groups {
if let Some(p) = group_policy_cache.get(group) {
policies.push(p.policies.clone());
}
}
}
}
let matched_policies = filter_policies(&self.cache, &policies.join(","), bucket_name).0;
if matched_policies.is_empty() {
continue;
}
let mut u = rustfs_madmin::UserInfo {
policy_name: Some(matched_policies),
status: if v.credentials.is_valid() {
AccountStatus::Enabled
} else {
AccountStatus::Disabled
},
updated_at: v.update_at,
..Default::default()
};
if let Some(members) = group_members.get(k) {
u.member_of = Some(members.iter().cloned().collect());
}
ret.insert(k.clone(), u);
}
Ok(ret)
}
pub async fn get_users_with_mapped_policies(&self) -> HashMap<String, String> {
let mut m = HashMap::new();
self.cache.user_policies.load().iter().for_each(|(k, v)| {
m.insert(k.clone(), v.policies.clone());
});
self.cache.sts_policies.load().iter().for_each(|(k, v)| {
m.insert(k.clone(), v.policies.clone());
});
m
}
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);
if x.credentials.is_temp() {
return Err(Error::IAMActionNotAllowed);
}
}
let status = {
match &args.status {
AccountStatus::Enabled => auth::ACCOUNT_ON,
_ => auth::ACCOUNT_OFF,
}
};
let user_entry = UserIdentity::from(Credentials {
access_key: access_key.to_string(),
secret_key: args.secret_key.to_string(),
status: status.to_owned(),
..Default::default()
});
self.api
.save_user_identity(access_key, UserType::Reg, user_entry.clone(), None)
.await?;
self.update_user_with_claims(access_key, user_entry)?;
Ok(OffsetDateTime::now_utc())
}
pub async fn delete_user(&self, access_key: &str, utype: UserType) -> Result<()> {
if access_key.is_empty() {
return Err(Error::InvalidArgument);
}
if utype == UserType::Reg {
if let Some(member_of) = self.cache.user_group_memberships.load().get(access_key) {
for member in member_of.iter() {
let _ = self
.remove_members_from_group(member, vec![access_key.to_string()], false)
.await?;
}
}
let users_cache = self.cache.users.load();
for (_, v) in users_cache.iter() {
let u = &v.credentials;
if u.parent_user.as_str() == access_key {
if u.is_service_account() {
let _ = self.api.delete_user_identity(&u.access_key, UserType::Svc).await;
Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc());
}
if u.is_temp() {
let _ = self.api.delete_user_identity(&u.access_key, UserType::Sts).await;
Cache::delete(&self.cache.sts_accounts, &u.access_key, OffsetDateTime::now_utc());
Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc());
}
}
}
}
let _ = self.api.delete_mapped_policy(access_key, utype, false).await;
Cache::delete(&self.cache.user_policies, access_key, OffsetDateTime::now_utc());
if let Err(err) = self.api.delete_user_identity(access_key, utype).await {
if !is_err_no_such_user(&err) {
return Err(err);
}
}
if utype == UserType::Sts {
Cache::delete(&self.cache.sts_accounts, access_key, OffsetDateTime::now_utc());
}
Cache::delete(&self.cache.users, access_key, OffsetDateTime::now_utc());
Ok(())
}
pub async fn update_user_secret_key(&self, access_key: &str, secret_key: &str) -> Result<()> {
if access_key.is_empty() || secret_key.is_empty() {
return Err(Error::InvalidArgument);
}
let users = self.cache.users.load();
let u = match users.get(access_key) {
Some(u) => u,
None => return Err(Error::NoSuchUser(access_key.to_string())),
};
let mut cred = u.credentials.clone();
cred.secret_key = secret_key.to_string();
let u = UserIdentity::from(cred);
self.api
.save_user_identity(access_key, UserType::Reg, u.clone(), None)
.await?;
self.update_user_with_claims(access_key, u)
}
/// Add SSH public key for a user (for SFTP authentication)
pub async fn add_user_ssh_public_key(&self, access_key: &str, public_key: &str) -> Result<()> {
if access_key.is_empty() || public_key.is_empty() {
return Err(Error::InvalidArgument);
}
let users = self.cache.users.load();
let u = match users.get(access_key) {
Some(u) => u,
None => return Err(Error::NoSuchUser(access_key.to_string())),
};
let mut user_identity = u.clone();
user_identity.add_ssh_public_key(public_key);
self.api
.save_user_identity(access_key, UserType::Reg, user_identity.clone(), None)
.await?;
self.update_user_with_claims(access_key, user_identity)
}
pub async fn set_user_status(&self, access_key: &str, status: AccountStatus) -> Result<OffsetDateTime> {
if access_key.is_empty() {
return Err(Error::InvalidArgument);
}
if !access_key.is_empty() && status != AccountStatus::Enabled && status != AccountStatus::Disabled {
return Err(Error::InvalidArgument);
}
let users = self.cache.users.load();
let u = match users.get(access_key) {
Some(u) => u,
None => return Err(Error::NoSuchUser(access_key.to_string())),
};
if u.credentials.is_temp() || u.credentials.is_service_account() {
return Err(Error::IAMActionNotAllowed);
}
let status = {
match status {
AccountStatus::Enabled => auth::ACCOUNT_ON,
_ => auth::ACCOUNT_OFF,
}
};
let user_entry = UserIdentity::from(Credentials {
access_key: access_key.to_string(),
secret_key: u.credentials.secret_key.clone(),
status: status.to_owned(),
..Default::default()
});
self.api
.save_user_identity(access_key, UserType::Reg, user_entry.clone(), None)
.await?;
self.update_user_with_claims(access_key, user_entry)?;
Ok(OffsetDateTime::now_utc())
}
fn update_user_with_claims(&self, k: &str, u: UserIdentity) -> Result<()> {
let mut u = u;
if !u.credentials.session_token.is_empty() {
u.credentials.claims = Some(extract_jwt_claims(&u)?);
}
if u.credentials.is_temp() && !u.credentials.is_service_account() {
Cache::add_or_update(&self.cache.sts_accounts, k, &u, OffsetDateTime::now_utc());
} else {
Cache::add_or_update(&self.cache.users, k, &u, OffsetDateTime::now_utc());
}
Ok(())
}
pub async fn is_temp_user(&self, access_key: &str) -> Result<(bool, String)> {
let users = self.cache.users.load();
let u = match users.get(access_key) {
Some(u) => u,
None => return Err(Error::NoSuchUser(access_key.to_string())),
};
if u.credentials.is_temp() {
Ok((true, u.credentials.parent_user.clone()))
} else {
Ok((false, String::new()))
}
}
pub async fn add_users_to_group(&self, group: &str, members: Vec<String>) -> Result<OffsetDateTime> {
if group.is_empty() {
return Err(Error::InvalidArgument);
}
let users_cache = self.cache.users.load();
for member in members.iter() {
if let Some(u) = users_cache.get(member) {
if u.credentials.is_temp() || u.credentials.is_service_account() {
return Err(Error::IAMActionNotAllowed);
}
} else {
return Err(Error::NoSuchUser(member.to_string()));
}
}
let gi = match self.cache.groups.load().get(group) {
Some(res) => {
let mut gi = res.clone();
let mut uniq_set: HashSet<String, std::collections::hash_map::RandomState> =
HashSet::from_iter(gi.members.iter().cloned());
uniq_set.extend(members.iter().cloned());
gi.members = uniq_set.into_iter().collect();
gi
}
None => GroupInfo::new(members.clone()),
};
self.api.save_group_info(group, gi.clone()).await?;
Cache::add_or_update(&self.cache.groups, group, &gi, OffsetDateTime::now_utc());
let user_group_memberships = self.cache.user_group_memberships.load();
members.iter().for_each(|member| {
if let Some(m) = user_group_memberships.get(member) {
let mut m = m.clone();
m.insert(group.to_string());
Cache::add_or_update(&self.cache.user_group_memberships, member, &m, OffsetDateTime::now_utc());
}
});
Ok(OffsetDateTime::now_utc())
}
pub async fn set_group_status(&self, name: &str, enable: bool) -> Result<OffsetDateTime> {
if name.is_empty() {
return Err(Error::InvalidArgument);
}
let groups = self.cache.groups.load();
let mut gi = match groups.get(name) {
Some(gi) => gi.clone(),
None => return Err(Error::NoSuchGroup(name.to_string())),
};
if enable {
gi.status = STATUS_ENABLED.to_owned();
} else {
gi.status = STATUS_DISABLED.to_owned();
}
self.api.save_group_info(name, gi.clone()).await?;
Cache::add_or_update(&self.cache.groups, name, &gi, OffsetDateTime::now_utc());
Ok(OffsetDateTime::now_utc())
}
pub async fn get_group_description(&self, name: &str) -> Result<GroupDesc> {
let (ps, updated_at) = self.policy_db_get_internal(name, true, false).await?;
let policy = ps.join(",");
let gi = self
.cache
.groups
.load()
.get(name)
.cloned()
.ok_or(Error::NoSuchGroup(name.to_string()))?;
Ok(GroupDesc {
name: name.to_string(),
policy,
members: gi.members,
updated_at: Some(updated_at),
status: gi.status,
})
}
pub async fn list_groups(&self) -> Result<Vec<String>> {
Ok(self.cache.groups.load().keys().cloned().collect())
}
pub async fn update_groups(&self) -> Result<Vec<String>> {
let mut groups_set = HashSet::new();
let mut m = HashMap::new();
self.api.load_groups(&mut m).await?;
for (group, gi) in m.iter() {
Cache::add_or_update(&self.cache.groups, group, gi, OffsetDateTime::now_utc());
groups_set.insert(group.to_string());
}
let mut m = HashMap::new();
self.api.load_mapped_policies(UserType::Reg, true, &mut m).await?;
for (group, gi) in m.iter() {
Cache::add_or_update(&self.cache.group_policies, group, gi, OffsetDateTime::now_utc());
groups_set.insert(group.to_string());
}
Ok(groups_set.into_iter().collect())
}
pub async fn remove_members_from_group(
&self,
name: &str,
members: Vec<String>,
update_cache_only: bool,
) -> Result<OffsetDateTime> {
let mut gi = self
.cache
.groups
.load()
.get(name)
.cloned()
.ok_or(Error::NoSuchGroup(name.to_string()))?;
let s: HashSet<&String> = HashSet::from_iter(gi.members.iter());
let d: HashSet<&String> = HashSet::from_iter(members.iter());
gi.members = s.difference(&d).map(|v| v.to_string()).collect::<Vec<String>>();
if !update_cache_only {
self.api.save_group_info(name, gi.clone()).await?;
}
Cache::add_or_update(&self.cache.groups, name, &gi, OffsetDateTime::now_utc());
let user_group_memberships = self.cache.user_group_memberships.load();
members.iter().for_each(|member| {
if let Some(m) = user_group_memberships.get(member) {
let mut m = m.clone();
m.remove(name);
Cache::add_or_update(&self.cache.user_group_memberships, member, &m, OffsetDateTime::now_utc());
}
});
Ok(OffsetDateTime::now_utc())
}
pub async fn remove_users_from_group(&self, group: &str, members: Vec<String>) -> Result<OffsetDateTime> {
if group.is_empty() {
return Err(Error::InvalidArgument);
}
let users_cache = self.cache.users.load();
for member in members.iter() {
if let Some(u) = users_cache.get(member) {
if u.credentials.is_temp() || u.credentials.is_service_account() {
return Err(Error::IAMActionNotAllowed);
}
} else {
return Err(Error::NoSuchUser(member.to_string()));
}
}
let gi = self
.cache
.groups
.load()
.get(group)
.cloned()
.ok_or(Error::NoSuchGroup(group.to_string()))?;
if members.is_empty() && !gi.members.is_empty() {
return Err(Error::GroupNotEmpty);
}
if members.is_empty() {
if let Err(err) = self.api.delete_mapped_policy(group, UserType::Reg, true).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
}
if let Err(err) = self.api.delete_group_info(group).await {
if !is_err_no_such_group(&err) {
return Err(err);
}
}
Cache::delete(&self.cache.groups, group, OffsetDateTime::now_utc());
Cache::delete(&self.cache.group_policies, group, OffsetDateTime::now_utc());
return Ok(OffsetDateTime::now_utc());
}
self.remove_members_from_group(group, members, false).await
}
fn remove_group_from_memberships_map(&self, group: &str) {
let user_group_memberships = self.cache.user_group_memberships.load();
for (k, v) in user_group_memberships.iter() {
if v.contains(group) {
let mut m = v.clone();
m.remove(group);
Cache::add_or_update(&self.cache.user_group_memberships, k, &m, OffsetDateTime::now_utc());
}
}
}
fn update_group_memberships_map(&self, group: &str, gi: &GroupInfo) {
let user_group_memberships = self.cache.user_group_memberships.load();
for member in gi.members.iter() {
if let Some(m) = user_group_memberships.get(member) {
let mut m = m.clone();
m.insert(group.to_string());
Cache::add_or_update(&self.cache.user_group_memberships, member, &m, OffsetDateTime::now_utc());
}
}
}
pub async fn group_notification_handler(&self, group: &str) -> Result<()> {
let mut m = HashMap::new();
if let Err(err) = self.api.load_group(group, &mut m).await {
if !is_err_no_such_group(&err) {
return Err(err);
}
self.remove_group_from_memberships_map(group);
Cache::delete(&self.cache.groups, group, OffsetDateTime::now_utc());
Cache::delete(&self.cache.group_policies, group, OffsetDateTime::now_utc());
return Ok(());
}
let gi = m[group].clone();
Cache::add_or_update(&self.cache.groups, group, &gi, OffsetDateTime::now_utc());
self.remove_group_from_memberships_map(group);
self.update_group_memberships_map(group, &gi);
Ok(())
}
pub async fn policy_notification_handler(&self, policy: &str) -> Result<()> {
let mut m = HashMap::new();
if let Err(err) = self.api.load_policy_doc(policy, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
Cache::delete(&self.cache.policy_docs, policy, OffsetDateTime::now_utc());
let user_policy_cache = self.cache.user_policies.load();
let users_cache = self.cache.users.load();
for (k, v) in user_policy_cache.iter() {
let mut set = v.policy_set();
if set.contains(policy) {
if !users_cache.contains_key(k) {
Cache::delete(&self.cache.user_policies, k, OffsetDateTime::now_utc());
continue;
}
set.remove(policy);
let mp = MappedPolicy::new(&set.iter().cloned().collect::<Vec<_>>().join(","));
Cache::add_or_update(&self.cache.user_policies, k, &mp, OffsetDateTime::now_utc());
}
}
let group_policy_cache = self.cache.group_policies.load();
for (k, v) in group_policy_cache.iter() {
let mut set = v.policy_set();
if set.contains(policy) {
set.remove(policy);
let mp = MappedPolicy::new(&set.iter().cloned().collect::<Vec<_>>().join(","));
Cache::add_or_update(&self.cache.group_policies, k, &mp, OffsetDateTime::now_utc());
}
}
return Ok(());
}
Cache::add_or_update(&self.cache.policy_docs, policy, &m[policy], OffsetDateTime::now_utc());
Ok(())
}
pub async fn policy_mapping_notification_handler(&self, name: &str, user_type: UserType, is_group: bool) -> Result<()> {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(name, user_type, is_group, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
if is_group {
Cache::delete(&self.cache.group_policies, name, OffsetDateTime::now_utc());
} else if user_type == UserType::Sts {
Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc());
} else {
Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc());
}
return Ok(());
}
let mp = m[name].clone();
if is_group {
Cache::add_or_update(&self.cache.group_policies, name, &mp, OffsetDateTime::now_utc());
} else if user_type == UserType::Sts {
Cache::delete(&self.cache.sts_policies, name, OffsetDateTime::now_utc());
} else {
Cache::add_or_update(&self.cache.user_policies, name, &mp, OffsetDateTime::now_utc());
}
Ok(())
}
pub async fn user_notification_handler(&self, name: &str, user_type: UserType) -> Result<()> {
let mut m = HashMap::new();
if let Err(err) = self.api.load_user(name, user_type, &mut m).await {
if !is_err_no_such_user(&err) {
return Err(err);
}
if user_type == UserType::Sts {
Cache::delete(&self.cache.sts_accounts, name, OffsetDateTime::now_utc());
} else {
Cache::delete(&self.cache.users, name, OffsetDateTime::now_utc());
}
let member_of = self.cache.user_group_memberships.load();
if let Some(m) = member_of.get(name) {
for group in m.iter() {
if let Err(err) = self.remove_members_from_group(group, vec![name.to_string()], true).await {
if !is_err_no_such_group(&err) {
return Err(err);
}
}
}
}
if user_type == UserType::Reg {
let users_cache = self.cache.users.load();
for (_, v) in users_cache.iter() {
let u = &v.credentials;
if u.parent_user.as_str() == name && u.is_service_account() {
let _ = self.api.delete_user_identity(&u.access_key, UserType::Svc).await;
Cache::delete(&self.cache.users, &u.access_key, OffsetDateTime::now_utc());
}
}
let sts_accounts = self.cache.sts_accounts.load();
if let Some(u) = sts_accounts.get(name) {
let u = &u.credentials;
if u.parent_user.as_str() == name {
let _ = self.api.delete_user_identity(&u.access_key, UserType::Sts).await;
Cache::delete(&self.cache.sts_accounts, &u.access_key, OffsetDateTime::now_utc());
}
}
}
Cache::delete(&self.cache.user_policies, name, OffsetDateTime::now_utc());
return Ok(());
}
let u = m[name].clone();
match user_type {
UserType::Sts => {
let name = u.credentials.parent_user;
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(&name, user_type, false, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
return Ok(());
}
Cache::add_or_update(&self.cache.sts_policies, &name, &m[&name], OffsetDateTime::now_utc());
}
UserType::Reg => {
let mut m = HashMap::new();
if let Err(err) = self.api.load_mapped_policy(name, user_type, false, &mut m).await {
if !is_err_no_such_policy(&err) {
return Err(err);
}
return Ok(());
}
Cache::add_or_update(&self.cache.sts_policies, name, &m[name], OffsetDateTime::now_utc());
}
UserType::Svc => {
let users_cache = self.cache.users.load();
if let Some(u) = users_cache.get(&u.credentials.parent_user) {
let mut m = HashMap::new();
if let Err(err) = self
.api
.load_mapped_policy(&u.credentials.parent_user, UserType::Reg, false, &mut m)
.await
{
if !is_err_no_such_policy(&err) {
return Err(err);
}
return Ok(());
}
Cache::add_or_update(
&self.cache.user_policies,
&u.credentials.parent_user,
&m[&u.credentials.parent_user],
OffsetDateTime::now_utc(),
);
} else {
let mut m = HashMap::new();
if let Err(err) = self
.api
.load_mapped_policy(&u.credentials.parent_user, UserType::Sts, false, &mut m)
.await
{
if !is_err_no_such_policy(&err) {
return Err(err);
}
return Ok(());
}
Cache::add_or_update(
&self.cache.sts_policies,
&u.credentials.parent_user,
&m[&u.credentials.parent_user],
OffsetDateTime::now_utc(),
);
}
}
UserType::None => {}
}
Ok(())
}
}
pub fn get_default_policyes() -> HashMap<String, PolicyDoc> {
let default_policies = &DEFAULT_POLICIES;
default_policies
.iter()
.map(|(n, p)| {
(
n.to_string(),
PolicyDoc {
version: 1,
policy: p.clone(),
..Default::default()
},
)
})
.collect()
}
fn set_default_canned_policies(policies: &mut HashMap<String, PolicyDoc>) {
let default_policies = &DEFAULT_POLICIES;
for (k, v) in default_policies.iter() {
let name = k.to_string();
policies.entry(name).or_insert_with(|| PolicyDoc::default_policy(v.clone()));
}
}
pub fn get_token_signing_key() -> Option<String> {
if let Some(s) = get_global_action_cred() {
Some(s.secret_key.clone())
} else {
None
}
}
pub fn extract_jwt_claims(u: &UserIdentity) -> Result<HashMap<String, Value>> {
let Some(sys_key) = get_token_signing_key() else {
return Err(Error::other("global active sk not init"));
};
let keys = vec![&sys_key, &u.credentials.secret_key];
for key in keys {
if let Ok(claims) = get_claims_from_token_with_secret(&u.credentials.session_token, key) {
return Ok(claims);
}
}
Err(Error::other("unable to extract claims"))
}
fn filter_policies(cache: &Cache, policy_name: &str, bucket_name: &str) -> (String, Policy) {
let mp = MappedPolicy::new(policy_name).to_slice();
let mut policies = Vec::new();
let mut to_merge = Vec::new();
for policy in mp {
if policy.is_empty() {
continue;
}
if let Some(p) = cache.policy_docs.load().get(&policy) {
if bucket_name.is_empty() || pollster::block_on(p.policy.match_resource(bucket_name)) {
policies.push(policy);
to_merge.push(p.policy.clone());
}
}
}
(policies.join(","), Policy::merge_policies(to_merge))
}
#[cfg(test)]
mod tests {
use super::*;
use rustfs_policy::policy::{Policy, PolicyDoc};
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_iam_format_new_version_1() {
let format = IAMFormat::new_version_1();
assert_eq!(format.version, IAM_FORMAT_VERSION_1);
assert_eq!(format.version, 1);
}
#[test]
fn test_get_iam_format_file_path() {
let path = get_iam_format_file_path();
assert!(path.contains(IAM_FORMAT_FILE));
assert!(path.contains(&*IAM_CONFIG_PREFIX));
assert_eq!(path, format!("{}/{}", *IAM_CONFIG_PREFIX, IAM_FORMAT_FILE));
}
#[test]
fn test_get_default_policies() {
let policies = get_default_policyes();
// Should contain some default policies
assert!(!policies.is_empty());
// Check that all values are PolicyDoc
for (name, policy_doc) in &policies {
assert!(!name.is_empty());
// PolicyDoc.version is i64, not String
assert!(policy_doc.version >= 0);
}
}
#[test]
fn test_get_token_signing_key() {
// This function returns the global action credential's secret key
// In test environment, it might be None
let key = get_token_signing_key();
// Just verify it doesn't panic and returns an Option
if let Some(k) = key {
assert!(!k.is_empty());
} // This is acceptable in test environment when None
}
#[test]
fn test_extract_jwt_claims_basic() {
let user_identity = UserIdentity {
version: 1,
credentials: Credentials {
access_key: "test-access-key".to_string(),
secret_key: "test-secret-key".to_string(),
session_token: "invalid-token".to_string(), // Invalid token for testing error handling
expiration: None,
status: "enabled".to_string(),
parent_user: "".to_string(),
groups: None,
claims: Some({
let mut claims = HashMap::new();
claims.insert("sub".to_string(), json!("test-user"));
claims.insert("aud".to_string(), json!("test-audience"));
claims
}),
name: None,
description: None,
},
update_at: Some(OffsetDateTime::now_utc()),
};
let result = extract_jwt_claims(&user_identity);
// In test environment without proper JWT setup, this should fail
assert!(result.is_err());
}
#[test]
fn test_extract_jwt_claims_no_claims() {
let user_identity = UserIdentity {
version: 1,
credentials: Credentials {
access_key: "test-access-key".to_string(),
secret_key: "test-secret-key".to_string(),
session_token: "".to_string(), // Empty token
expiration: None,
status: "enabled".to_string(),
parent_user: "".to_string(),
groups: None,
claims: None,
name: None,
description: None,
},
update_at: Some(OffsetDateTime::now_utc()),
};
let result = extract_jwt_claims(&user_identity);
// Should fail with empty session token
assert!(result.is_err());
}
#[test]
fn test_filter_policies_empty_bucket() {
let cache = Cache::default();
let policy_name = "test-policy";
let bucket_name = "";
let (name, policy) = filter_policies(&cache, policy_name, bucket_name);
// When cache is empty, should return empty name and empty policy
assert_eq!(name, "");
assert!(policy.statements.is_empty());
}
#[test]
fn test_filter_policies_with_bucket() {
let cache = Cache::default();
let policy_name = "test-policy";
let bucket_name = "test-bucket";
let (name, policy) = filter_policies(&cache, policy_name, bucket_name);
// When cache is empty, should return empty name and empty policy regardless of bucket
assert_eq!(name, "");
assert!(policy.statements.is_empty());
}
#[test]
fn test_constants() {
// Test that constants are properly defined
assert_eq!(IAM_FORMAT_FILE, "format.json");
assert_eq!(IAM_FORMAT_VERSION_1, 1);
}
#[test]
fn test_iam_format_serialization() {
let format = IAMFormat::new_version_1();
// Test serialization
let serialized = serde_json::to_string(&format).unwrap();
assert!(serialized.contains("\"version\":1"));
// Test deserialization
let deserialized: IAMFormat = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.version, format.version);
}
#[test]
fn test_mapped_policy_operations() {
let policy_name = "test-policy";
let mapped_policy = MappedPolicy::new(policy_name);
// Test that MappedPolicy can be created
let policies = mapped_policy.to_slice();
assert!(!policies.is_empty());
assert!(policies.iter().any(|p| p.contains(policy_name)));
}
#[test]
fn test_user_identity_structure() {
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
session_token: "".to_string(),
expiration: None,
status: "enabled".to_string(),
parent_user: "parent-user".to_string(),
groups: Some(vec!["group1".to_string(), "group2".to_string()]),
claims: None,
name: None,
description: None,
};
let user_identity = UserIdentity {
version: 1,
credentials,
update_at: Some(OffsetDateTime::now_utc()),
};
// Test basic structure
assert_eq!(user_identity.version, 1);
assert_eq!(user_identity.credentials.access_key, "AKIAIOSFODNN7EXAMPLE");
assert_eq!(user_identity.credentials.secret_key, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
assert_eq!(user_identity.credentials.status, "enabled");
assert_eq!(user_identity.credentials.parent_user, "parent-user");
assert_eq!(user_identity.credentials.groups, Some(vec!["group1".to_string(), "group2".to_string()]));
}
#[test]
fn test_policy_structure() {
let policy = Policy {
id: Default::default(),
version: "2012-10-17".to_string(),
statements: vec![],
};
// Test basic structure
assert_eq!(policy.version, "2012-10-17");
assert_eq!(policy.statements.len(), 0);
assert!(policy.is_empty());
}
#[test]
fn test_policy_doc_structure() {
let policy = Policy {
id: Default::default(),
version: "2012-10-17".to_string(),
statements: vec![],
};
let policy_doc = PolicyDoc {
version: 1,
policy,
create_date: Some(OffsetDateTime::now_utc()),
update_date: Some(OffsetDateTime::now_utc()),
};
// Test basic structure
assert_eq!(policy_doc.version, 1);
assert_eq!(policy_doc.policy.version, "2012-10-17");
assert!(policy_doc.policy.statements.is_empty());
}
#[test]
fn test_group_info_basic() {
// Test that GroupInfo can be created and used
let group_info = GroupInfo {
version: 1,
status: STATUS_ENABLED.to_string(),
members: vec!["user1".to_string(), "user2".to_string()],
update_at: Some(OffsetDateTime::now_utc()),
};
assert_eq!(group_info.version, 1);
assert_eq!(group_info.status, STATUS_ENABLED);
assert_eq!(group_info.members.len(), 2);
assert!(group_info.members.contains(&"user1".to_string()));
assert!(group_info.members.contains(&"user2".to_string()));
}
#[test]
fn test_update_service_account_opts() {
let policy = Policy {
id: Default::default(),
version: "2012-10-17".to_string(),
statements: vec![],
};
let opts = UpdateServiceAccountOpts {
secret_key: Some("new-secret-key".to_string()),
status: Some(STATUS_ENABLED.to_string()),
name: Some("service-account-name".to_string()),
description: Some("Updated service account".to_string()),
expiration: None,
session_policy: Some(policy.clone()),
};
assert_eq!(opts.secret_key, Some("new-secret-key".to_string()));
assert_eq!(opts.status, Some(STATUS_ENABLED.to_string()));
assert_eq!(opts.name, Some("service-account-name".to_string()));
assert_eq!(opts.description, Some("Updated service account".to_string()));
assert!(opts.session_policy.is_some());
assert!(opts.expiration.is_none());
}
#[test]
fn test_status_constants() {
// Test that status constants are properly defined
assert_eq!(STATUS_ENABLED, "enabled");
assert_eq!(STATUS_DISABLED, "disabled");
}
#[test]
fn test_session_policy_constants() {
// Test session policy related constants - these are compile-time constants
// so we just verify they exist and have expected values
assert_eq!(SESSION_POLICY_NAME, "sessionPolicy");
assert_eq!(SESSION_POLICY_NAME_EXTRACTED, "sessionPolicy-extracted");
// MAX_SVCSESSION_POLICY_SIZE is a positive constant defined at compile time
assert_eq!(MAX_SVCSESSION_POLICY_SIZE, 4096); // Verify the actual expected value
}
#[test]
fn test_credentials_validation() {
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
session_token: "".to_string(),
expiration: None,
status: "on".to_string(),
parent_user: "".to_string(),
groups: None,
claims: None,
name: None,
description: None,
};
// Test validation methods
assert!(credentials.is_valid());
assert!(!credentials.is_expired());
assert!(!credentials.is_temp());
assert!(!credentials.is_service_account());
}
#[test]
fn test_credentials_with_session_token() {
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
session_token: "session-token".to_string(),
expiration: Some(OffsetDateTime::now_utc() + time::Duration::hours(1)),
status: "on".to_string(),
parent_user: "".to_string(),
groups: None,
claims: None,
name: None,
description: None,
};
// Test temp credentials
assert!(credentials.is_valid());
assert!(!credentials.is_expired());
assert!(credentials.is_temp());
}
#[test]
fn test_policy_merge() {
let policy1 = Policy {
id: Default::default(),
version: "2012-10-17".to_string(),
statements: vec![],
};
let policy2 = Policy {
id: Default::default(),
version: "2012-10-17".to_string(),
statements: vec![],
};
let merged = Policy::merge_policies(vec![policy1, policy2]);
assert_eq!(merged.version, "2012-10-17");
assert!(merged.statements.is_empty());
assert!(merged.is_empty());
}
}