refactor: route IAM reads through app context (#3786)

This commit is contained in:
Zhengchao An
2026-06-23 17:57:55 +08:00
committed by GitHub
parent 61cfd4fc13
commit fd2ab38581
13 changed files with 106 additions and 46 deletions
+41 -7
View File
@@ -5,15 +5,16 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
- Branch: `overtrue/arch-runtime-context-consumer-batch`
- Baseline: completed `C-011/C-012/C-013/API-055/API-059/API-079/API-080/API-081/API-082/API-083/API-084/API-085/API-086/API-087/API-088/API-089/API-090/API-091/API-092/API-093/API-094/API-095/API-096/API-097/API-098/API-099/API-100/API-101/API-102/API-103/API-104/API-105/API-106/API-107/API-108/API-109/API-110/API-111/API-112/API-113/API-114/API-115/API-116/API-117/API-118/API-119/API-120/API-121/API-122/API-123/API-124/API-125/API-126/API-127/API-128/API-129/API-130/API-131/API-132/API-133/API-134/API-135/API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166/API-167/API-168/API-169/API-170/API-171/API-172/API-173/API-174/API-175/API-176/API-177`.
- Based on: API-170 merged in PR #3783; this branch batches API-171 through
API-177 on top of latest `main`.
- Branch: `overtrue/arch-iam-global-read-batch`
- Baseline: completed `C-011/C-012/C-013/API-055/API-059/API-079/API-080/API-081/API-082/API-083/API-084/API-085/API-086/API-087/API-088/API-089/API-090/API-091/API-092/API-093/API-094/API-095/API-096/API-097/API-098/API-099/API-100/API-101/API-102/API-103/API-104/API-105/API-106/API-107/API-108/API-109/API-110/API-111/API-112/API-113/API-114/API-115/API-116/API-117/API-118/API-119/API-120/API-121/API-122/API-123/API-124/API-125/API-126/API-127/API-128/API-129/API-130/API-131/API-132/API-133/API-134/API-135/API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166/API-167/API-168/API-169/API-170/API-171/API-172/API-173/API-174/API-175/API-176/API-177/API-178`.
- Based on: API-171 through API-177 prepared in PR #3785; this branch batches
the next IAM consumer migration on top of that branch.
- PR type for this branch: `consumer-migration`
- Runtime behavior changes: none.
- Rust code changes: route replication pool, outbound TLS generation, runtime
region, KMS encryption service, runtime support handles, S3 Select DB, and
internode RPC metrics through AppContext-first resolvers.
region, KMS encryption service, runtime support handles, S3 Select DB,
internode RPC metrics, and IAM authorization/handler reads through
AppContext-first resolvers.
- CI/script changes: lock completed owner and test/fuzz boundaries against
bare/glob imports, scattered raw ECStore facade subpaths, and startup
runtime/root-server/table/S3/app shared/app bucket/app ECStore/admin facade
@@ -22,7 +23,7 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
and storage owner thin bridge regressions, plus app context and notify
event-bridge thin module regressions; accept the reviewed AppContext resolver
reverse dependencies in the layer baseline.
- Docs changes: record the API-136 through API-177 owner facade cleanup.
- Docs changes: record the API-136 through API-178 owner facade cleanup.
## Phase 0 Tasks
@@ -4556,6 +4557,20 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
metrics scan, Rust risk scan, branch freshness check, and three-expert
review.
- [x] `API-178` Route IAM runtime reads through AppContext.
- Do: route auth, storage authorization, admin auth, admin IAM handlers, STS,
and table-catalog credential issuance through an AppContext-first ready IAM
resolver.
- Acceptance: production auth/admin/storage request paths no longer call the
IAM global getter directly, while the resolver preserves the legacy ready
check and global fallback.
- Must preserve: signature secret lookup, access-key validation, S3 policy
authorization, table data-plane authorization, admin IAM CRUD, STS temp-user
creation, service-account flows, and table credential issuance.
- Verification: RustFS compile coverage, targeted context resolver tests,
migration guard, layer guard, formatting, diff hygiene, residual IAM getter
scan, Rust risk scan, branch freshness check, and three-expert review.
## Next PRs
1. `consumer-migration`: continue reducing direct global reads behind AppContext resolver boundaries.
@@ -4643,11 +4658,30 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Quality/architecture | pass | API-177 keeps internode RPC metrics behind an AppContext-first resolver across HTTP and gRPC RPC paths. |
| Migration preservation | pass | Request counters, byte accounting, transport backend labels, and error metrics preserve existing global fallback behavior. |
| Testing/verification | pass | RustFS focused compile, targeted context resolver test, formatting, migration/layer guards, diff hygiene, residual internode metrics scan, and Rust risk scan passed for API-177. |
| Quality/architecture | pass | API-178 keeps ready IAM access behind an AppContext-first resolver without widening handler semantics. |
| Migration preservation | pass | Auth, storage authorization, admin IAM handlers, STS, and table credential flows keep existing error mapping and ready-check fallback. |
| Testing/verification | pass | RustFS focused compile, targeted context resolver test, formatting, migration/layer guards, diff hygiene, residual IAM getter scan, Rust risk scan, and pre-commit passed for API-178. |
## Verification Notes
Passed before push:
- Issue #660 API-178 current slice:
- `cargo check --tests -p rustfs`: passed.
- `cargo test -p rustfs resolver_helpers_are_context_first_and_fallback_when_context_is_absent --lib`:
passed.
- `cargo fmt --all`: passed.
- `cargo fmt --all --check`: passed.
- `git diff --check`: passed.
- `bash -n scripts/check_architecture_migration_rules.sh`: passed.
- `./scripts/check_architecture_migration_rules.sh`: passed.
- `./scripts/check_layer_dependencies.sh`: passed.
- IAM getter scan: passed; production auth/admin/storage IAM reads now go
through the AppContext ready IAM resolver.
- Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
risks added.
- `make pre-commit`: passed.
- Issue #660 API-176 current slice:
- `cargo check --tests -p rustfs`: passed.
- `cargo test -p rustfs resolver_helpers_are_context_first_and_fallback_when_context_is_absent --lib`:
+2 -2
View File
@@ -56,7 +56,7 @@ pub async fn validate_admin_request(
actions: Vec<Action>,
remote_addr: Option<std::net::SocketAddr>,
) -> S3Result<()> {
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam not init"));
};
let ctx = AuthContext {
@@ -146,7 +146,7 @@ pub async fn validate_admin_request_with_bucket_object(
remote_addr: Option<std::net::SocketAddr>,
resource: AdminResourceScope<'_>,
) -> S3Result<()> {
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam not init"));
};
let ctx = AuthContext {
+1 -1
View File
@@ -69,7 +69,7 @@ impl Operation for AccountInfoHandler {
let (cred, owner) = authenticate_request(&req.headers, &req.uri, &input_cred).await?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
+5 -5
View File
@@ -115,7 +115,7 @@ impl Operation for ListGroups {
)
.await?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -180,7 +180,7 @@ impl Operation for GetGroup {
GroupQuery::default()
}
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -256,7 +256,7 @@ impl Operation for DeleteGroup {
let group = decode_delete_group_name(&params)?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -394,7 +394,7 @@ impl Operation for SetGroupStatus {
return Err(s3_error!(InvalidArgument, "group is required"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -537,7 +537,7 @@ impl Operation for UpdateGroupMembers {
"admin group state"
);
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
+2 -1
View File
@@ -58,7 +58,8 @@ impl Operation for IsAdminHandler {
true
} else {
let empty_claims = HashMap::new();
let iam_store = rustfs_iam::get().map_err(|_| s3_error!(InternalError, "iam not init"))?;
let iam_store =
crate::app::context::resolve_ready_iam_handle().map_err(|_| s3_error!(InternalError, "iam not init"))?;
let conditions = get_condition_values(&req.headers, &cred, None, None, None);
iam_store
.is_allowed(&Args {
+11 -7
View File
@@ -144,7 +144,7 @@ impl Operation for ListCannedPolicies {
}
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -252,7 +252,7 @@ impl Operation for AddCannedPolicy {
if policy.version.is_empty() {
return Err(s3_error!(InvalidArgument, "policy version is required"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -340,7 +340,7 @@ impl Operation for InfoCannedPolicy {
return Err(s3_error!(InvalidArgument, "too many policies"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -401,7 +401,7 @@ impl Operation for RemoveCannedPolicy {
return Err(s3_error!(InvalidArgument, "policy name is required"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -492,7 +492,7 @@ impl Operation for SetPolicyForUserOrGroup {
return Err(s3_error!(InvalidArgument, "user or group is required"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -825,7 +825,9 @@ async fn handle_builtin_policy_entities(req: S3Request<Body>) -> S3Result<S3Resp
let query = parse_policy_entities_query(req.uri.query());
let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam not init"));
};
let all_group_policy_mappings = collect_group_policy_mappings(&iam_store, &[]).await?;
let users = iam_store.list_users().await.map_err(|e| {
@@ -959,7 +961,9 @@ async fn handle_builtin_policy_association(req: S3Request<Body>, is_attach: bool
.map_err(|e| s3_error!(InvalidRequest, "unmarshal policy association body failed, e: {:?}", e))?;
validate_policy_association_req(&assoc_req)?;
let Ok(iam_store) = rustfs_iam::get() else { return Err(s3_error!(InternalError, "iam not init")) };
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam not init"));
};
let (target_name, is_group, existing_policies) = if !assoc_req.user.is_empty() {
match iam_store.is_temp_user(&assoc_req.user).await {
+8 -8
View File
@@ -292,7 +292,7 @@ impl Operation for AddServiceAccount {
req_is_derived_cred = true;
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -513,7 +513,7 @@ impl Operation for UpdateServiceAccount {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -642,7 +642,7 @@ impl Operation for InfoServiceAccount {
let access_key = query.access_key;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -719,7 +719,7 @@ impl Operation for TemporaryAccountInfo {
let (cred, owner) =
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -789,7 +789,7 @@ impl Operation for InfoAccessKey {
query.access_key
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -912,7 +912,7 @@ impl Operation for ListServiceAccount {
// cred.parent_user
// };
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -1049,7 +1049,7 @@ impl Operation for ListAccessKeysBulk {
let (cred, owner) =
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -1252,7 +1252,7 @@ impl Operation for DeleteServiceAccount {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
+3 -2
View File
@@ -187,7 +187,7 @@ async fn handle_assume_role(
return Err(s3_error!(InvalidRequest, "AccessDenied"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
let conditions = crate::auth::get_condition_values(&headers, &cred, None, None, remote_addr);
@@ -438,7 +438,8 @@ pub async fn create_oidc_sts_credentials(
new_cred.groups = Some(groups.to_vec());
// Store temp user in IAM
let iam_store = rustfs_iam::get().map_err(|_| s3_error!(InternalError, "IAM not initialized"))?;
let iam_store =
crate::app::context::resolve_ready_iam_handle().map_err(|_| s3_error!(InternalError, "IAM not initialized"))?;
let updated_at = iam_store
.set_temp_user(&new_cred.access_key, &new_cred, None)
+1 -1
View File
@@ -701,7 +701,7 @@ impl TableCredentialIssuer for IamTableCredentialIssuer {
.map_err(|err| s3_error!(InternalError, "failed to generate table credentials: {}", err))?;
bind_table_credential_parent(&mut credential, principal);
let iam_store = rustfs_iam::get().map_err(|_| s3_error!(InternalError, "iam not init"))?;
let iam_store = crate::app::context::resolve_ready_iam_handle().map_err(|_| s3_error!(InternalError, "iam not init"))?;
iam_store
.set_temp_user(&credential.access_key, &credential, None)
.await
+7 -7
View File
@@ -227,7 +227,7 @@ impl Operation for AddUser {
return Err(s3_error!(InvalidArgument, "cannot create a user with the system access key"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -356,7 +356,7 @@ impl Operation for SetUserStatus {
let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -408,7 +408,7 @@ impl Operation for ListUsers {
}
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -481,7 +481,7 @@ impl Operation for RemoveUser {
return Err(s3_error!(InvalidArgument, "cannot remove the current user"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -558,7 +558,7 @@ impl Operation for GetUserInfo {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam is not initialized"));
};
@@ -654,7 +654,7 @@ impl Operation for ExportIam {
)
.await?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
@@ -888,7 +888,7 @@ impl Operation for ImportIam {
let mut zip_reader =
ZipArchive::new(Cursor::new(body)).map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InvalidRequest, "iam not init"));
};
+21 -1
View File
@@ -37,7 +37,7 @@ use super::{BucketBandwidthMonitor, DynReplicationPool, NotificationSys, Replica
use crate::config::RustFSBufferConfig;
use rustfs_config::server_config::Config;
use rustfs_credentials::Credentials;
use rustfs_iam::{store::object::ObjectStore, sys::IamSys};
use rustfs_iam::{error::Error as IamError, store::object::ObjectStore, sys::IamSys};
use rustfs_io_metrics::{PerformanceMetrics, internode_metrics::InternodeMetrics};
use rustfs_kms::{KmsServiceManager, ObjectEncryptionService, init_global_kms_service_manager};
use rustfs_lock::LockClient;
@@ -84,6 +84,11 @@ pub fn resolve_iam_handle() -> Option<Arc<IamSys<ObjectStore>>> {
resolve_iam_handle_with(get_global_app_context(), rustfs_iam::get_global_iam_sys)
}
/// Resolve a ready IAM system handle using AppContext-first precedence.
pub fn resolve_ready_iam_handle() -> rustfs_iam::error::Result<Arc<IamSys<ObjectStore>>> {
resolve_ready_iam_handle_with(get_global_app_context(), rustfs_iam::get)
}
/// Resolve bucket metadata handle using AppContext-first precedence.
pub fn resolve_bucket_metadata_handle() -> Option<Arc<RwLock<BucketMetadataSys>>> {
resolve_bucket_metadata_handle_with(get_global_app_context(), || default_bucket_metadata_interface().handle())
@@ -290,6 +295,21 @@ fn resolve_iam_handle_with(
context.map(|context| context.iam().handle()).or_else(fallback)
}
fn resolve_ready_iam_handle_with(
context: Option<Arc<AppContext>>,
fallback: impl FnOnce() -> rustfs_iam::error::Result<Arc<IamSys<ObjectStore>>>,
) -> rustfs_iam::error::Result<Arc<IamSys<ObjectStore>>> {
if let Some(context) = context {
if context.iam().is_ready() {
return Ok(context.iam().handle());
}
return Err(IamError::IamSysNotInitialized);
}
fallback()
}
fn resolve_bucket_metadata_handle_with(
context: Option<Arc<AppContext>>,
fallback: impl FnOnce() -> Option<Arc<RwLock<BucketMetadataSys>>>,
+2 -2
View File
@@ -186,7 +186,7 @@ impl S3Auth for IAMAuth {
return Ok(key);
}
if let Ok(iam_store) = rustfs_iam::get() {
if let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() {
// Use check_key instead of get_user to ensure user is loaded from disk if not in cache
// This is important for newly created users that may not be in cache yet.
// check_key will automatically attempt to load the user from disk if not found in cache.
@@ -341,7 +341,7 @@ pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<
let sys_cred = cred.clone();
if !constant_time_eq(&cred.access_key, access_key) {
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(S3Error::with_message(
S3ErrorCode::InternalError,
format!("check_key_valid {:?}", IamError::IamSysNotInitialized),
+2 -2
View File
@@ -328,7 +328,7 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3R
let version_id = req_info.version_id.clone();
if let Some(cred) = &cred {
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(S3Error::with_message(
S3ErrorCode::InternalError,
format!("authorize_request {:?}", IamError::IamSysNotInitialized),
@@ -841,7 +841,7 @@ async fn authorize_table_data_plane_if_needed(
let Some(resource) = table_data_plane_resource_for_request(bucket, object).await? else {
return Ok(());
};
let Ok(iam_store) = rustfs_iam::get() else {
let Ok(iam_store) = crate::app::context::resolve_ready_iam_handle() else {
return Err(s3_error!(InternalError, "iam not init"));
};