refactor: route notify dispatch through app context (#3789)

* refactor: route notify dispatch through app context

* refactor: route admin IAM globals through app context (#3791)

* refactor: centralize IAM root credential access (#3792)
This commit is contained in:
Zhengchao An
2026-06-23 20:05:28 +08:00
committed by GitHub
parent da565c11bc
commit 70a2441407
19 changed files with 272 additions and 72 deletions
+5
View File
@@ -47,6 +47,7 @@ pub mod keyring;
pub mod manager;
pub mod oidc;
pub mod oidc_state;
mod root_credentials;
pub mod store;
pub mod sys;
pub mod utils;
@@ -60,6 +61,10 @@ pub(crate) type IamStore = EcstoreStore;
pub(crate) type IamConfigObjectInfo = <IamStore as rustfs_storage_api::ObjectOperations>::ObjectInfo;
pub(crate) type IamConfigObjectOptions = <IamStore as rustfs_storage_api::ObjectOperations>::ObjectOptions;
pub fn is_root_access_key(access_key: &str) -> bool {
root_credentials::is_root_access_key(access_key)
}
pub(crate) async fn read_iam_config_no_lock(api: Arc<IamStore>, file: &str) -> IamStorageResult<Vec<u8>> {
ecstore_read_config_no_lock(api, file).await
}
+2 -6
View File
@@ -25,7 +25,7 @@ use crate::{
},
};
use futures::future::join_all;
use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred};
use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE};
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
use rustfs_policy::{
arn::ARN,
@@ -2137,11 +2137,7 @@ fn set_default_canned_policies(policies: &mut HashMap<String, PolicyDoc>) {
}
pub fn get_token_signing_key() -> Option<String> {
if let Some(s) = get_global_action_cred() {
Some(s.secret_key)
} else {
None
}
crate::root_credentials::token_signing_key()
}
pub fn extract_jwt_claims(u: &UserIdentity) -> Result<HashMap<String, Value>> {
+31
View File
@@ -0,0 +1,31 @@
// 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 rustfs_credentials::{Credentials, get_global_action_cred};
pub(crate) fn credentials() -> Option<Credentials> {
get_global_action_cred()
}
pub(crate) fn credentials_or_default() -> Credentials {
credentials().unwrap_or_default()
}
pub(crate) fn token_signing_key() -> Option<String> {
credentials().map(|cred| cred.secret_key)
}
pub(crate) fn is_root_access_key(access_key: &str) -> bool {
credentials().is_some_and(|cred| cred.access_key == access_key)
}
+5 -5
View File
@@ -23,9 +23,9 @@ use crate::{
error::{is_err_no_such_policy, is_err_no_such_user},
keyring,
manager::{extract_jwt_claims, extract_jwt_claims_allow_missing_exp, get_default_policyes},
root_credentials,
};
use futures::future::join_all;
use rustfs_credentials::get_global_action_cred;
use rustfs_io_metrics::record_system_path_failure;
use rustfs_policy::{auth::UserIdentity, policy::PolicyDoc};
use rustfs_storage_api::{HTTPPreconditions, ListOperations as _, ObjectInfoOrErr as StorageObjectInfoOrErr, ObjectOperations};
@@ -165,7 +165,7 @@ impl ObjectStore {
}
const STREAM_IO_HEADER_LEN: usize = 41;
let cred = get_global_action_cred().unwrap_or_default();
let cred = root_credentials::credentials_or_default();
let mut last_err = None;
let mut try_decrypt_with_key = |key: &[u8], source: DecryptSource| -> Option<DecryptOutcome> {
@@ -1273,16 +1273,16 @@ impl Store for ObjectStore {
mod tests {
use super::{DecryptSource, ObjectStore};
use crate::keyring;
use rustfs_credentials::{Credentials, get_global_action_cred, init_global_action_credentials};
use rustfs_credentials::{Credentials, init_global_action_credentials};
use serial_test::serial;
use temp_env::with_vars;
fn test_cred() -> Credentials {
if let Some(cred) = get_global_action_cred() {
if let Some(cred) = crate::root_credentials::credentials() {
return cred;
}
let _ = init_global_action_credentials(Some("COMPATTESTAK".to_string()), Some("COMPATTESTSK1234567890".to_string()));
get_global_action_cred().unwrap_or_default()
crate::root_credentials::credentials_or_default()
}
#[test]
+8 -9
View File
@@ -28,7 +28,7 @@ use crate::{
notify_iam_delete_policy, notify_iam_delete_service_account, notify_iam_delete_user, notify_iam_load_group,
notify_iam_load_policy, notify_iam_load_policy_mapping, notify_iam_load_service_account, notify_iam_load_user,
};
use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred};
use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE};
use rustfs_madmin::AddOrUpdateUserReq;
use rustfs_madmin::GroupDesc;
use rustfs_policy::arn::ARN;
@@ -748,7 +748,7 @@ impl<T: Store> IamSys<T> {
}
pub async fn check_key(&self, access_key: &str) -> Result<(Option<UserIdentity>, bool)> {
if let Some(sys_cred) = get_global_action_cred()
if let Some(sys_cred) = crate::root_credentials::credentials()
&& sys_cred.access_key == access_key
{
return Ok((Some(UserIdentity::new(sys_cred)), true));
@@ -1010,7 +1010,7 @@ impl<T: Store> IamSys<T> {
}
pub(crate) async fn prepare_sts_auth(&self, args: &Args<'_>, parent_user: &str) -> PreparedIamAuth {
let is_owner = matches!(get_global_action_cred(), Some(cred) if cred.access_key == parent_user);
let is_owner = crate::is_root_access_key(parent_user);
let role_arn = args.get_role_arn();
let (effective_groups, groups_source, policies) = if is_owner {
@@ -1162,7 +1162,7 @@ impl<T: Store> IamSys<T> {
&& matches!(mode, PreparedServicePolicyMode::SessionBound)
&& matches!(session_policy, PreparedSessionPolicy::Policy(_));
let is_owner = matches!(get_global_action_cred(), Some(cred) if cred.access_key == parent_user);
let is_owner = crate::is_root_access_key(parent_user);
let role_arn = args.get_role_arn();
let svc_policies = if is_owner || bypass_parent_policy {
@@ -1352,7 +1352,7 @@ mod tests {
use crate::error::Error;
use crate::manager::get_default_policyes;
use crate::store::{GroupInfo, MappedPolicy, Store, UserType};
use rustfs_credentials::{Credentials, get_global_action_cred, init_global_action_credentials};
use rustfs_credentials::{Credentials, init_global_action_credentials};
use rustfs_policy::auth::{UserIdentity, get_new_credentials_with_metadata};
use rustfs_policy::policy::Args;
use rustfs_policy::policy::action::{Action, AdminAction, S3Action};
@@ -1660,7 +1660,7 @@ mod tests {
}
fn ensure_test_global_credentials() {
if get_global_action_cred().is_none() {
if crate::root_credentials::credentials().is_none() {
let _ = init_global_action_credentials(Some("TESTROOTACCESSKEY".to_string()), Some("TESTROOTSECRET123".to_string()));
}
}
@@ -1940,9 +1940,8 @@ mod tests {
let iam_sys = IamSys::new(cache_manager);
let parent_user = "sts-fallback-test-parent";
let token_secret = get_global_action_cred()
.expect("global action credentials should be initialized")
.secret_key;
let token_secret = crate::root_credentials::token_signing_key()
.unwrap_or_else(|| unreachable!("global action credentials should be initialized"));
let mut claims = HashMap::new();
claims.insert("parent".to_string(), Value::String(parent_user.to_string()));
claims.insert(
+1 -6
View File
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use rustfs_credentials;
use rustfs_policy::policy::action::S3Action as PolicyS3Action;
use serde_json;
use std::collections::HashMap;
@@ -299,11 +298,7 @@ pub async fn is_authorized(
let policy_action: rustfs_policy::policy::action::Action = action.clone().into();
// Check if user is the owner (admin)
let is_owner = if let Some(global_cred) = rustfs_credentials::get_global_action_cred() {
session_context.principal.access_key() == global_cred.access_key
} else {
false
};
let is_owner = rustfs_iam::is_root_access_key(session_context.principal.access_key());
let args = rustfs_policy::policy::Args {
account: session_context.principal.access_key(),
+112 -7
View File
@@ -5,16 +5,18 @@ 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-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.
- Branch: `overtrue/arch-iam-credential-boundary`
- 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/API-179/API-180/API-181`.
- Based on: API-180 prepared in PR #3791; this branch batches IAM root
credential boundary cleanup 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,
internode RPC metrics, and IAM authorization/handler reads through
AppContext-first resolvers.
internode RPC metrics, IAM authorization/handler reads, notification
rules/event dispatch, admin OIDC/token-signing reads, and IAM root
credential consumers through AppContext-first or IAM-owned resolver
boundaries.
- 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
@@ -23,7 +25,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-178 owner facade cleanup.
- Docs changes: record the API-136 through API-181 owner facade cleanup.
## Phase 0 Tasks
@@ -4571,6 +4573,52 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
migration guard, layer guard, formatting, diff hygiene, residual IAM getter
scan, Rust risk scan, branch freshness check, and three-expert review.
- [x] `API-179` Route notification dispatch through AppContext.
- Do: route startup notification rule registration, storage event
notifications, and ECStore event dispatch hooks through the AppContext
notify interface.
- Acceptance: production RustFS notification dispatch paths no longer call
the notify global directly, while the default adapter preserves the legacy
notifier fallback.
- Must preserve: bucket notification rule registration, operation helper
success-only event emission, replication-request suppression, ECStore event
conversion, and background spawn behavior.
- Verification: RustFS compile coverage, targeted context resolver tests,
migration guard, layer guard, formatting, diff hygiene, residual notify
dispatch scan, Rust risk scan, branch freshness check, and three-expert
review.
- [x] `API-180` Route admin OIDC and token-signing reads through AppContext.
- Do: expose AppContext-first resolvers for the OIDC system and token
signing key, then route console config, OIDC handlers, STS credential
generation, table credential vending, and site-replication STS validation
through those resolvers.
- Acceptance: targeted RustFS admin OIDC/token-signing consumers no longer
read IAM globals directly, while default adapters preserve the existing
global OIDC and action-credential fallback.
- Must preserve: OIDC provider listing, authorize/callback/logout behavior,
STS web-identity verification, STS/table credential claims, session-policy
handling, and site-replication STS token validation.
- Verification: RustFS compile coverage, targeted context resolver tests,
migration guard, layer guard, formatting, diff hygiene, residual
admin/global IAM scan, Rust risk scan, branch freshness check, and
three-expert review.
- [x] `API-181` Centralize IAM root credential reads behind IAM boundary.
- Do: add an IAM-owned root credential helper, route IAM store/sys/token
signing consumers through it, and make protocol gateway owner checks call an
IAM predicate instead of reading credentials directly.
- Acceptance: production IAM and protocol gateway paths no longer call the
action credential global directly outside the IAM boundary module, while
root user detection, legacy IAM decrypt fallback, and token-signing behavior
remain unchanged.
- Must preserve: root credential lookup, owner-policy bypass decisions,
legacy secret-key decrypt fallback, STS token signing key selection,
service-account/STSes authorization, and protocol gateway policy args.
- Verification: IAM/protocol compile coverage, IAM focused tests, formatting,
migration guard, layer guard, diff hygiene, residual direct credential 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.
@@ -4661,11 +4709,68 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| 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. |
| Quality/architecture | pass | API-179 keeps notification rule registration and event dispatch behind the AppContext notify resolver. |
| Migration preservation | pass | Startup rules, success-only storage events, replication suppression, ECStore hook conversion, and default notifier fallback are preserved. |
| Testing/verification | pass | RustFS focused compile, targeted context resolver test, formatting, migration/layer guards, diff hygiene, residual notify dispatch scan, Rust risk scan, and pre-commit passed for API-179. |
| Quality/architecture | pass | API-180 keeps admin OIDC and token-signing reads behind IAM/AppContext resolver methods without widening handler behavior. |
| Migration preservation | pass | OIDC provider discovery, STS credential generation, table credential vending, and site-replication STS validation keep existing error mapping and fallback semantics. |
| Testing/verification | pass | RustFS focused compile, targeted context resolver test, formatting/migration/layer guards, diff hygiene, residual admin IAM scan, and Rust risk scan passed for API-180. |
| Quality/architecture | pass | API-181 keeps IAM root credential reads centralized in an IAM-owned helper and exposes only a root access-key predicate to protocol code. |
| Migration preservation | pass | Root owner checks, legacy IAM decrypt fallback, token signing, and gateway policy args preserve existing credential semantics. |
| Testing/verification | pass | IAM/protocol compile, IAM unit tests, formatting, migration/layer guards, diff hygiene, residual credential scan, and Rust risk scan passed for API-181. |
## Verification Notes
Passed before push:
- Issue #660 API-181 current slice:
- `cargo check -p rustfs-iam -p rustfs-protocols --tests`: passed.
- `cargo test -p rustfs-iam --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/protocol credential scan: passed; production direct
`get_global_action_cred` calls are isolated to the IAM root credential
boundary.
- Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
risks added.
- `make pre-commit`: passed.
- Issue #660 API-180 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.
- Admin IAM/OIDC scan: passed; targeted admin OIDC and token-signing reads
now go through AppContext IAM resolvers.
- Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
risks added.
- `make pre-commit`: passed.
- Issue #660 API-179 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.
- Notify dispatch scan: passed; production startup, storage helper, and
ECStore event hook dispatch no longer call the notify global directly.
- Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
risks added.
- `make pre-commit`: passed.
- 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`:
+2 -1
View File
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::admin::handlers::health::{HealthProbe, build_health_response_parts, collect_dependency_readiness};
use crate::app::context::resolve_oidc_handle;
use crate::license::has_valid_license;
use crate::server::has_path_prefix;
use crate::server::{
@@ -134,7 +135,7 @@ impl Config {
let http_prefix = rustfs_config::RUSTFS_HTTP_PREFIX;
// Collect OIDC provider info if available
let oidc = rustfs_iam::get_oidc()
let oidc = resolve_oidc_handle()
.map(|sys| {
sys.list_visible_providers()
.into_iter()
+6 -6
View File
@@ -16,7 +16,7 @@ use super::super::{read_admin_config_without_migrate, save_admin_server_config};
use super::sts::create_oidc_sts_credentials;
use crate::admin::auth::validate_admin_request;
use crate::admin::router::{AdminOperation, Operation, S3Router};
use crate::app::context::{resolve_object_store_handle, resolve_server_config};
use crate::app::context::{resolve_object_store_handle, resolve_oidc_handle, resolve_server_config};
use crate::auth::{check_key_valid, get_session_token};
use crate::server::{ADMIN_PREFIX, MINIO_ADMIN_PREFIX, RemoteAddr};
use http::StatusCode;
@@ -268,7 +268,7 @@ pub struct ListOidcProvidersHandler {}
#[async_trait::async_trait]
impl Operation for ListOidcProvidersHandler {
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
let oidc_sys = rustfs_iam::get_oidc().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let providers = oidc_sys.list_visible_providers();
let json_body = serde_json::to_vec(&providers)
@@ -439,7 +439,7 @@ impl Operation for OidcAuthorizeHandler {
return Err(s3_error!(InvalidRequest, "invalid provider_id"));
}
let oidc_sys = rustfs_iam::get_oidc().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
// Derive the callback redirect URI from the request
let redirect_uri = derive_callback_uri(&req, provider_id)?;
@@ -512,7 +512,7 @@ impl Operation for OidcCallbackHandler {
));
}
let oidc_sys = rustfs_iam::get_oidc().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let redirect_uri = derive_callback_uri(&req, provider_id)?;
@@ -599,7 +599,7 @@ impl Operation for OidcLogoutHandler {
return redirect_response(&fallback_location);
};
let location = match rustfs_iam::get_oidc() {
let location = match resolve_oidc_handle() {
Some(oidc_sys) => match oidc_sys.build_logout_url(&logout_token, &fallback_location).await {
Ok(Some(url)) => url,
Ok(None) => fallback_location.clone(),
@@ -628,7 +628,7 @@ impl Operation for OidcLogoutHandler {
/// an explicit redirect_uri is recommended to prevent header manipulation.
fn derive_callback_uri(req: &S3Request<Body>, provider_id: &str) -> S3Result<String> {
// Use explicitly configured redirect_uri if available
if let Some(oidc_sys) = rustfs_iam::get_oidc()
if let Some(oidc_sys) = resolve_oidc_handle()
&& let Some(config) = oidc_sys.get_provider_config(provider_id)
{
if let Some(ref uri) = config.redirect_uri {
@@ -34,7 +34,7 @@ use crate::admin::utils::{encode_compatible_admin_payload, read_compatible_admin
use crate::app::context::{
resolve_deployment_id, resolve_endpoints_handle, resolve_iam_handle, resolve_object_store_handle, resolve_oidc_handle,
resolve_outbound_tls_generation, resolve_outbound_tls_state, resolve_region, resolve_replication_pool_handle,
resolve_replication_stats_handle, resolve_runtime_port, resolve_server_config,
resolve_replication_stats_handle, resolve_runtime_port, resolve_server_config, resolve_token_signing_key,
};
use crate::auth::{check_key_valid, get_session_token};
use crate::config::get_config_snapshot;
@@ -3556,7 +3556,7 @@ async fn apply_iam_item(item: SRIAMItem) -> S3Result<()> {
let Some(sts_credential) = item.sts_credential else {
return Err(s3_error!(InvalidRequest, "stsCredential is required"));
};
let Some(secret) = rustfs_iam::manager::get_token_signing_key() else {
let Some(secret) = resolve_token_signing_key() else {
return Err(s3_error!(InvalidRequest, "token signing key not initialized"));
};
let claims = get_claims_from_token_with_secret(&sts_credential.session_token, &secret)
+6 -6
View File
@@ -19,7 +19,7 @@ use crate::{
handlers::site_replication::site_replication_iam_change_hook,
router::{AdminOperation, Operation, S3Router},
},
app::context::resolve_action_credentials,
app::context::{resolve_action_credentials, resolve_oidc_handle, resolve_token_signing_key},
auth::{check_key_valid, extract_string_list_claim, get_session_token},
server::ADMIN_PREFIX,
server::RemoteAddr,
@@ -29,7 +29,7 @@ use http::header::HeaderValue;
use hyper::Method;
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
use rustfs_iam::{manager::get_token_signing_key, oidc::OidcClaims, sys::SESSION_POLICY_NAME};
use rustfs_iam::{oidc::OidcClaims, sys::SESSION_POLICY_NAME};
use rustfs_madmin::{SITE_REPL_API_VERSION, SRIAMItem, SRSTSCredential};
use rustfs_policy::{
auth::get_new_credentials_with_metadata,
@@ -59,7 +59,7 @@ fn has_identity_authorization_context(policies: &[String], groups: &[String]) ->
}
fn configured_roles_claim_key(provider_id: &str) -> Option<String> {
rustfs_iam::get_oidc()
resolve_oidc_handle()
.as_ref()
.and_then(|oidc_sys| oidc_sys.get_provider_config(provider_id))
.map(|cfg| cfg.roles_claim.trim().to_string())
@@ -239,7 +239,7 @@ async fn handle_assume_role(
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
}
let Some(secret) = get_token_signing_key() else {
let Some(secret) = resolve_token_signing_key() else {
return Err(s3_error!(InvalidArgument, "global active sk not init"));
};
@@ -316,7 +316,7 @@ async fn handle_assume_role_with_web_identity(body: AssumeRoleRequest) -> S3Resu
}
// Verify the JWT and extract claims
let oidc_sys = rustfs_iam::get_oidc().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let oidc_sys = resolve_oidc_handle().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?;
let (claims, provider_id) = oidc_sys
.verify_web_identity_token(&body.web_identity_token)
@@ -429,7 +429,7 @@ pub async fn create_oidc_sts_credentials(
}
// Generate STS temp credentials
let secret = get_token_signing_key().ok_or_else(|| s3_error!(InternalError, "token signing key not initialized"))?;
let secret = resolve_token_signing_key().ok_or_else(|| s3_error!(InternalError, "token signing key not initialized"))?;
let mut new_cred = get_new_credentials_with_metadata(&token_claims, &secret)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("credential generation failed: {e}")))?;
+3 -3
View File
@@ -17,7 +17,7 @@ use crate::admin::{
auth::{AdminResourceScope, validate_admin_request, validate_admin_request_with_bucket_object},
router::{AdminOperation, Operation, S3Router},
};
use crate::app::context::resolve_object_store_handle;
use crate::app::context::{resolve_object_store_handle, resolve_token_signing_key};
use crate::auth::{check_key_valid, get_session_token};
use crate::server::{RemoteAddr, TABLE_CATALOG_COMPAT_PREFIX, TABLE_CATALOG_PREFIX};
use crate::table_catalog::{DEFAULT_WAREHOUSE_ID, TableCatalogStore};
@@ -26,7 +26,7 @@ use hyper::Method;
use matchit::Params;
use metrics::{counter, histogram};
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
use rustfs_iam::{manager::get_token_signing_key, sys::SESSION_POLICY_NAME};
use rustfs_iam::sys::SESSION_POLICY_NAME;
use rustfs_policy::{
auth::get_new_credentials_with_metadata,
policy::{
@@ -696,7 +696,7 @@ impl TableCredentialIssuer for IamTableCredentialIssuer {
serde_json::Value::String(request.scope_prefix.clone()),
);
let secret = get_token_signing_key().ok_or_else(|| s3_error!(InternalError, "token signing key not initialized"))?;
let secret = resolve_token_signing_key().ok_or_else(|| s3_error!(InternalError, "token signing key not initialized"))?;
let mut credential = get_new_credentials_with_metadata(&claims, &secret)
.map_err(|err| s3_error!(InternalError, "failed to generate table credentials: {}", err))?;
bind_table_credential_parent(&mut credential, principal);
+63 -11
View File
@@ -37,8 +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::oidc::OidcSys;
use rustfs_iam::{error::Error as IamError, store::object::ObjectStore, sys::IamSys};
use rustfs_iam::{error::Error as IamError, oidc::OidcSys, 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;
@@ -85,14 +84,19 @@ pub fn resolve_iam_handle() -> Option<Arc<IamSys<ObjectStore>>> {
resolve_iam_handle_with(get_global_app_context(), rustfs_iam::get_global_iam_sys)
}
/// Resolve OIDC handle using AppContext-first precedence.
/// 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 OIDC system handle using AppContext-first precedence.
pub fn resolve_oidc_handle() -> Option<Arc<OidcSys>> {
resolve_oidc_handle_with(get_global_app_context(), rustfs_iam::get_oidc)
}
/// 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 token signing key using AppContext-first precedence.
pub fn resolve_token_signing_key() -> Option<String> {
resolve_token_signing_key_with(get_global_app_context(), rustfs_iam::manager::get_token_signing_key)
}
/// Resolve bucket metadata handle using AppContext-first precedence.
@@ -111,6 +115,12 @@ pub fn resolve_object_store_handle_for_context(context: Option<&AppContext>) ->
context.map(|context| context.object_store()).or_else(new_object_layer_fn)
}
/// Resolve notify interface using AppContext-first precedence.
pub fn resolve_notify_interface() -> Arc<dyn NotifyInterface> {
let context = get_global_app_context();
resolve_notify_interface_for_context(context.as_deref())
}
/// Resolve notify interface using an explicit AppContext, falling back to the legacy global notifier.
pub fn resolve_notify_interface_for_context(context: Option<&AppContext>) -> Arc<dyn NotifyInterface> {
context
@@ -323,6 +333,12 @@ fn resolve_ready_iam_handle_with(
fallback()
}
fn resolve_token_signing_key_with(context: Option<Arc<AppContext>>, fallback: impl FnOnce() -> Option<String>) -> Option<String> {
context
.and_then(|context| context.iam().token_signing_key())
.or_else(fallback)
}
fn resolve_bucket_metadata_handle_with(
context: Option<Arc<AppContext>>,
fallback: impl FnOnce() -> Option<Arc<RwLock<BucketMetadataSys>>>,
@@ -546,7 +562,7 @@ mod tests {
};
use crate::config::{RustFSBufferConfig, WorkloadProfile};
use async_trait::async_trait;
use rustfs_iam::{store::object::ObjectStore, sys::IamSys};
use rustfs_iam::{oidc::OidcSys, store::object::ObjectStore, sys::IamSys};
use rustfs_io_metrics::{PerformanceMetrics, internode_metrics::InternodeMetrics};
use rustfs_lock::{LocalClient, LockClient};
use rustfs_s3select_api::{
@@ -562,6 +578,8 @@ mod tests {
struct TestIamInterface {
ready: bool,
oidc: Option<Arc<OidcSys>>,
token_signing_key: Option<String>,
}
impl IamInterface for TestIamInterface {
@@ -572,13 +590,23 @@ mod tests {
fn is_ready(&self) -> bool {
self.ready
}
fn oidc(&self) -> Option<Arc<OidcSys>> {
self.oidc.clone()
}
fn token_signing_key(&self) -> Option<String> {
self.token_signing_key.clone()
}
}
struct TestOidcInterface;
struct TestOidcInterface {
oidc: Option<Arc<OidcSys>>,
}
impl OidcInterface for TestOidcInterface {
fn handle(&self) -> Option<Arc<rustfs_iam::oidc::OidcSys>> {
None
self.oidc.clone()
}
}
@@ -985,12 +1013,30 @@ mod tests {
};
let context_region: s3s::region::Region = "context-region".parse().expect("test region");
let fallback_region: s3s::region::Region = "fallback-region".parse().expect("test region");
let context_oidc_sys = match OidcSys::empty() {
Ok(sys) => sys,
Err(err) => unreachable!("test OIDC sys should initialize: {err}"),
};
let fallback_oidc_sys = match OidcSys::empty() {
Ok(sys) => sys,
Err(err) => unreachable!("test OIDC fallback sys should initialize: {err}"),
};
let context_oidc = Arc::new(context_oidc_sys);
let fallback_oidc = Arc::new(fallback_oidc_sys);
let context_token_signing_key = "context-token-signing-key".to_string();
let fallback_token_signing_key = "fallback-token-signing-key".to_string();
let context = Arc::new(AppContext::with_test_interfaces(
object_store.clone(),
AppContextTestInterfaces {
iam: Arc::new(TestIamInterface { ready: true }),
oidc: Arc::new(TestOidcInterface),
iam: Arc::new(TestIamInterface {
ready: true,
oidc: None,
token_signing_key: Some(context_token_signing_key.clone()),
}),
oidc: Arc::new(TestOidcInterface {
oidc: Some(context_oidc.clone()),
}),
kms: Arc::new(TestKmsInterface {
kms: context_kms.clone(),
}),
@@ -1084,6 +1130,12 @@ mod tests {
context_outbound_tls_state.generation
);
assert!(resolve_iam_ready_with(Some(context.clone()), || false));
let resolved_oidc = resolve_oidc_handle_with(Some(context.clone()), || Some(fallback_oidc.clone()));
assert!(resolved_oidc.as_ref().is_some_and(|oidc| Arc::ptr_eq(oidc, &context_oidc)));
assert_eq!(
resolve_token_signing_key_with(Some(context.clone()), || Some(fallback_token_signing_key.clone())).as_deref(),
Some(context_token_signing_key.as_str())
);
assert!(Arc::ptr_eq(
&resolve_bucket_metadata_handle_with(Some(context.clone()), || None).expect("context bucket metadata"),
&bucket_metadata
+8
View File
@@ -74,6 +74,14 @@ impl IamInterface for IamHandle {
fn is_ready(&self) -> bool {
rustfs_iam::get().is_ok()
}
fn oidc(&self) -> Option<Arc<OidcSys>> {
rustfs_iam::get_oidc()
}
fn token_signing_key(&self) -> Option<String> {
rustfs_iam::manager::get_token_signing_key()
}
}
/// Default OIDC interface adapter.
+7 -2
View File
@@ -23,8 +23,7 @@ use crate::config::RustFSBufferConfig;
use async_trait::async_trait;
use rustfs_config::server_config::Config;
use rustfs_credentials::Credentials;
use rustfs_iam::oidc::OidcSys;
use rustfs_iam::{store::object::ObjectStore, sys::IamSys};
use rustfs_iam::{oidc::OidcSys, store::object::ObjectStore, sys::IamSys};
use rustfs_io_metrics::{PerformanceMetrics, internode_metrics::InternodeMetrics};
use rustfs_kms::KmsServiceManager;
use rustfs_lock::LockClient;
@@ -41,6 +40,12 @@ pub trait IamInterface: Send + Sync {
#[allow(dead_code)]
fn handle(&self) -> Arc<IamSys<ObjectStore>>;
fn is_ready(&self) -> bool;
fn oidc(&self) -> Option<Arc<OidcSys>> {
None
}
fn token_signing_key(&self) -> Option<String> {
None
}
}
/// OIDC interface for admin and runtime consumers.
+3 -3
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::app::context::resolve_region;
use crate::app::context::{resolve_notify_interface, resolve_region};
use crate::server::ShutdownHandle;
use crate::storage::{
get_bucket_notification_config, process_lambda_configurations, process_queue_configurations, process_topic_configurations,
@@ -22,7 +22,6 @@ use rustfs_config::{
DEFAULT_BUFFER_MAX_SIZE, DEFAULT_BUFFER_MIN_SIZE, DEFAULT_BUFFER_PROFILE, DEFAULT_BUFFER_UNKNOWN_SIZE, DEFAULT_UPDATE_CHECK,
ENV_RUSTFS_BUFFER_DEFAULT_SIZE, ENV_RUSTFS_BUFFER_MAX_SIZE, ENV_RUSTFS_BUFFER_MIN_SIZE, ENV_UPDATE_CHECK, RUSTFS_REGION,
};
use rustfs_notify::notifier_global;
use rustfs_targets::arn::{ARN, TargetIDError};
use rustfs_utils::get_env_usize;
use s3s::s3_error;
@@ -245,7 +244,8 @@ pub async fn add_bucket_notification_configuration(buckets: Vec<String>) {
);
}
if let Err(e) = notifier_global::add_event_specific_rules(bucket, region, &event_rules)
if let Err(e) = resolve_notify_interface()
.add_event_specific_rules(bucket, region, &event_rules)
.await
.map_err(|e| s3_error!(InternalError, "Failed to add rules: {e}"))
{
+2 -2
View File
@@ -13,7 +13,7 @@
// limitations under the License.
use super::{module_switch::resolve_notify_module_state, refresh_persisted_module_switches_from_store};
use crate::app::context::resolve_server_config;
use crate::app::context::{resolve_notify_interface, resolve_server_config};
use crate::storage::{EventArgs as EcstoreEventArgs, StorageObjectInfo, register_event_dispatch_hook};
use chrono::{DateTime, Utc};
use rustfs_notify::{EventArgs as NotifyEventArgs, NotifyObjectInfo};
@@ -118,7 +118,7 @@ fn install_ecstore_event_dispatch_hook() {
return;
};
spawn(async move {
rustfs_notify::notifier_global::notify(notify_args).await;
resolve_notify_interface().notify(notify_args).await;
});
});
+3 -3
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::app::context::resolve_action_credentials;
use crate::app::context::{resolve_action_credentials, resolve_notify_interface};
use crate::server::{convert_ecstore_object_info, is_audit_module_enabled, is_notify_module_enabled};
use crate::storage::access::{ReqInfo, request_context_from_req};
use crate::storage::request_context::{RequestContext, extract_request_id_from_headers};
@@ -24,7 +24,7 @@ use rustfs_audit::{
global::AuditLogger,
};
use rustfs_io_metrics::record_s3_op;
use rustfs_notify::{EventArgsBuilder, notifier_global};
use rustfs_notify::EventArgsBuilder;
use rustfs_s3_ops::{S3Operation, operation_matches_event_name};
use rustfs_s3_types::EventName;
use rustfs_targets::{
@@ -383,7 +383,7 @@ impl Drop for OperationHelper {
if !event_args.is_replication_request() {
let ctx = state.request_context.clone();
spawn_background_with_context(ctx, async move {
notifier_global::notify(event_args).await;
resolve_notify_interface().notify(event_args).await;
});
}
}
+3
View File
@@ -41,11 +41,13 @@ dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::ecfs
dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params
dep|rustfs/src/auth.rs|infra->app|crate::app::context::resolve_action_credentials
dep|rustfs/src/config/info.rs|infra->app|crate::app::context::resolve_buffer_config
dep|rustfs/src/init.rs|infra->app|crate::app::context::resolve_notify_interface
dep|rustfs/src/init.rs|infra->app|crate::app::context::resolve_region
dep|rustfs/src/init.rs|infra->interface|crate::admin
dep|rustfs/src/protocols/client.rs|infra->app|crate::app::context::resolve_action_credentials
dep|rustfs/src/protocols/client.rs|infra->interface|crate::storage::ecfs::FS
dep|rustfs/src/server/audit.rs|infra->app|crate::app::context::resolve_server_config
dep|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_notify_interface
dep|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_server_config
dep|rustfs/src/server/http.rs|infra->interface|crate::admin
dep|rustfs/src/server/layer.rs|infra->app|crate::app::context::resolve_kms_runtime_service_manager
@@ -69,6 +71,7 @@ dep|rustfs/src/storage/access.rs|infra->app|crate::app::context::resolve_region
dep|rustfs/src/storage/concurrency/manager.rs|infra->app|crate::app::context::resolve_performance_metrics
dep|rustfs/src/storage/sse.rs|infra->app|crate::app::context::resolve_encryption_service
dep|rustfs/src/storage/helper.rs|infra->app|crate::app::context::resolve_action_credentials
dep|rustfs/src/storage/helper.rs|infra->app|crate::app::context::resolve_notify_interface
dep|rustfs/src/storage/rpc/disk.rs|infra->app|crate::app::context::resolve_internode_metrics
dep|rustfs/src/storage/rpc/health.rs|infra->app|crate::app::context::resolve_local_node_name
dep|rustfs/src/storage/rpc/http_service.rs|infra->app|crate::app::context::resolve_internode_metrics