// 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}; use manager::IamCache; use oidc::OidcSys; use std::sync::{Arc, OnceLock}; use store::object::ObjectStore; use sys::IamSys; use tracing::{debug, error, info, instrument, warn}; const LOG_COMPONENT_IAM: &str = "iam"; const LOG_SUBSYSTEM_RUNTIME: &str = "runtime"; const LOG_SUBSYSTEM_OIDC: &str = "oidc"; const EVENT_IAM_STATE: &str = "iam_state"; const EVENT_OIDC_STATE: &str = "oidc_state"; pub mod cache; pub mod error; pub mod keyring; pub mod manager; pub mod oidc; pub mod oidc_state; mod root_credentials; mod runtime_sources; mod server_config; mod storage_api; pub mod store; pub mod sys; pub mod utils; pub(crate) use storage_api::crate_boundary::{ IAM_CONFIG_ROOT_PREFIX, IamEcstoreError, IamStorageError, IamStore, classify_iam_system_path_failure_reason, delete_iam_config, is_iam_first_cluster_node_local, read_iam_config_no_lock, read_iam_config_with_metadata, save_iam_config, save_iam_config_with_opts, }; pub fn is_root_access_key(access_key: &str) -> bool { root_credentials::is_root_access_key(access_key) } /// Decrypts an at-rest IAM config blob using the same key sources as the IAM load /// path (RustFS master keys and MinIO-compatible legacy keys derived from the root /// credentials). Used by the MinIO -> RustFS migration path. See /// [`store::object::try_decrypt_iam_blob`]. pub use store::object::try_decrypt_iam_blob; pub(crate) struct IamNotificationPeerErr { pub(crate) err: Option, } impl From for IamNotificationPeerErr { fn from(value: storage_api::crate_boundary::IamEcstoreNotificationPeerErr) -> Self { Self { err: value.err } } } pub(crate) async fn notify_iam_delete_policy(policy_name: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .delete_policy(policy_name) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_load_policy(policy_name: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .load_policy(policy_name) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_delete_user(access_key: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .delete_user(access_key) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_load_user(access_key: &str, temp: bool) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .load_user(access_key, temp) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_load_service_account(access_key: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .load_service_account(access_key) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_delete_service_account(access_key: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .delete_service_account(access_key) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_load_group(group: &str) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys.load_group(group).await.into_iter().map(Into::into).collect(), None => Vec::new(), } } pub(crate) async fn notify_iam_load_policy_mapping( user_or_group: &str, user_type: u64, is_group: bool, ) -> Vec { match runtime_sources::notification_sys() { Some(notification_sys) => notification_sys .load_policy_mapping(user_or_group, user_type, is_group) .await .into_iter() .map(Into::into) .collect(), None => Vec::new(), } } static IAM_SYS: OnceLock>> = OnceLock::new(); static OIDC_SYS: OnceLock> = OnceLock::new(); #[instrument(skip(ecstore))] pub async fn init_iam_sys(ecstore: Arc) -> Result<()> { if IAM_SYS.get().is_some() { info!( event = EVENT_IAM_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_RUNTIME, state = "already_initialized", "IAM runtime already initialized" ); return Ok(()); } info!( event = EVENT_IAM_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_RUNTIME, state = "starting", "IAM runtime starting" ); // 1. Create the persistent storage adapter let storage_adapter = ObjectStore::new(ecstore); // 2. Create the cache manager. // The `new` method now performs a blocking initial load from disk. let cache_manager = IamCache::new(storage_adapter).await?; // 3. Construct the system interface let iam_instance = Arc::new(IamSys::new(cache_manager)); // 4. Securely set the global singleton if IAM_SYS.set(iam_instance).is_err() { error!( event = EVENT_IAM_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_RUNTIME, state = "singleton_set_failed", "IAM runtime singleton set failed" ); return Err(Error::IamSysAlreadyInitialized); } info!( event = EVENT_IAM_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_RUNTIME, state = "ready", "IAM runtime ready" ); Ok(()) } #[inline] pub fn get() -> Result>> { let sys = IAM_SYS.get().map(Arc::clone).ok_or(Error::IamSysNotInitialized)?; // Double-check the internal readiness state. The OnceLock is only set // after initialization and data loading complete, so this is a defensive // guard to ensure callers never operate on a partially initialized system. if !sys.is_ready() { return Err(Error::IamSysNotInitialized); } Ok(sys) } pub fn get_global_iam_sys() -> Option>> { IAM_SYS.get().cloned() } /// Initialize the global OIDC system. Non-fatal if no OIDC providers are configured. pub async fn init_oidc_sys() -> Result<()> { if OIDC_SYS.get().is_some() { debug!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, state = "already_initialized", "OIDC runtime already initialized" ); return Ok(()); } debug!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, state = "starting", "OIDC runtime starting" ); let oidc_sys = match OidcSys::new().await { Ok(sys) => { if sys.has_providers() { debug!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, provider_count = sys.list_providers().len(), state = "ready", "OIDC runtime ready" ); } else { debug!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, state = "empty", "OIDC runtime has no providers" ); } sys } Err(e) => { warn!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, state = "init_failed_non_fatal", error = %e, "OIDC runtime initialization failed" ); OidcSys::empty().map_err(Error::StringError)? } }; if OIDC_SYS.set(Arc::new(oidc_sys)).is_err() { warn!( event = EVENT_OIDC_STATE, component = LOG_COMPONENT_IAM, subsystem = LOG_SUBSYSTEM_OIDC, state = "singleton_set_race", "OIDC runtime singleton set raced" ); } Ok(()) } /// Get the global OIDC system. pub fn get_oidc() -> Option> { OIDC_SYS.get().cloned() }