refactor(iam): hand the built IAM system to the AppContext explicitly (#4624)

backlog#1052 S3, fourth slice. The AppContext's IamHandle already owned an
Arc<IamSys>, but the value it held was read back from the process
singleton after init — so a future second server's context would silently
bind to the first server's IAM domain (credentials, policies, users all
belong to a specific store's .rustfs.sys).

- rustfs_iam gains build_iam_sys(store): construct an IAM system bound to
  a store without touching the singleton. init_iam_sys wraps it (build,
  publish first-wins, return the handle — previously Result<()>).
- Startup threads the built handle: the inline bootstrap path passes the
  Arc it just created into ensure_startup_after_iam, which constructs the
  AppContext from the passed value instead of re-resolving the global.
  The deferred-recovery path resolves the freshly published global
  directly (context-first resolution cannot be used there: the AppContext
  does not exist yet — creating it is the finalizer's job; caught by the
  embedded deferred-IAM e2e).
- IamHandle::is_ready() reports the held system's readiness instead of
  consulting the process singleton; the dead global re-resolver is
  removed. token_signing_key stays on the credentials global until S4.

157 iam tests + embedded e2e (basic + deferred recovery) green.
This commit is contained in:
Zhengchao An
2026-07-09 22:35:00 +08:00
committed by GitHub
parent 5fbd49a800
commit 008b872414
5 changed files with 44 additions and 30 deletions
+23 -15
View File
@@ -163,9 +163,25 @@ pub(crate) async fn notify_iam_load_policy_mapping(
static IAM_SYS: OnceLock<Arc<IamSys<ObjectStore>>> = OnceLock::new();
static OIDC_SYS: OnceLock<Arc<OidcSys>> = OnceLock::new();
/// Build an IAM system bound to the given store without touching the process
/// singleton (backlog#1052 S3): a per-server context can own the returned
/// handle while the singleton keeps serving ambient readers.
#[instrument(skip(ecstore))]
pub async fn init_iam_sys(ecstore: Arc<IamStore>) -> Result<()> {
if IAM_SYS.get().is_some() {
pub async fn build_iam_sys(ecstore: Arc<IamStore>) -> Result<Arc<IamSys<ObjectStore>>> {
// 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
Ok(Arc::new(IamSys::new(cache_manager)))
}
#[instrument(skip(ecstore))]
pub async fn init_iam_sys(ecstore: Arc<IamStore>) -> Result<Arc<IamSys<ObjectStore>>> {
if let Some(existing) = IAM_SYS.get() {
info!(
event = EVENT_IAM_STATE,
component = LOG_COMPONENT_IAM,
@@ -173,7 +189,7 @@ pub async fn init_iam_sys(ecstore: Arc<IamStore>) -> Result<()> {
state = "already_initialized",
"IAM runtime already initialized"
);
return Ok(());
return Ok(existing.clone());
}
info!(
@@ -184,18 +200,10 @@ pub async fn init_iam_sys(ecstore: Arc<IamStore>) -> Result<()> {
"IAM runtime starting"
);
// 1. Create the persistent storage adapter
let storage_adapter = ObjectStore::new(ecstore);
let iam_instance = build_iam_sys(ecstore).await?;
// 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() {
// Securely set the global singleton
if IAM_SYS.set(iam_instance.clone()).is_err() {
error!(
event = EVENT_IAM_STATE,
component = LOG_COMPONENT_IAM,
@@ -213,7 +221,7 @@ pub async fn init_iam_sys(ecstore: Arc<IamStore>) -> Result<()> {
state = "ready",
"IAM runtime ready"
);
Ok(())
Ok(iam_instance)
}
#[inline]