diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 0c36b37b9..75368b0ce 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,13 +5,13 @@ 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-rpc-node-context-resolvers` -- 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`. -- Based on: `main` after API-158. +- Branch: `overtrue/arch-admin-runtime-context-resolvers` +- 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`. +- Based on: API-159 branch while PR #3772 is pending. - PR type for this branch: `consumer-migration` - Runtime behavior changes: none. -- Rust code changes: route RPC node lock-client and local-node-name consumers - through AppContext resolvers with legacy global fallback. +- Rust code changes: route admin/server action credentials, region, and server + config consumers through AppContext resolvers with legacy global fallback. - 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 @@ -19,7 +19,7 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ECStore compatibility bypasses, plus runtime crate, owner crate, test/fuzz, and storage owner thin bridge regressions, plus app context and notify event-bridge thin module regressions. -- Docs changes: record the 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 owner facade cleanup. +- Docs changes: record the 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 owner facade cleanup. ## Phase 0 Tasks @@ -4298,6 +4298,20 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block migration guard, formatting, diff hygiene, Rust risk scan, branch freshness check, pre-commit, and three-expert review. +- [x] `API-160` Route admin runtime reads through AppContext resolvers. + - Do: add action-credential and region AppContext interfaces, resolver + helpers, default legacy adapters, and use them with the existing server + config resolver across admin/server read paths. + - Acceptance: admin handlers and router code no longer directly read action + credentials, region, or server config globals when an AppContext resolver + owns that boundary. + - Must preserve: admin auth decisions, object-ZIP token signing, object lambda + signing region fallback, OIDC restart detection, site replication metadata, + and legacy global fallback when AppContext is absent. + - Verification: RustFS compile coverage, targeted context resolver tests, + migration guard, formatting, diff hygiene, Rust risk scan, branch freshness + check, pre-commit, and three-expert review. + ## Next PRs 1. `consumer-migration`: continue reducing direct global reads behind AppContext resolver boundaries. @@ -4330,11 +4344,30 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Quality/architecture | pass | API-159 keeps RPC node lock client and node identity reads behind AppContext resolver boundaries. | | Migration preservation | pass | RPC lock initialization errors and health metric node-name inputs keep legacy fallback behavior. | | Testing/verification | pass | RustFS focused compile, targeted context tests, formatting, migration guard, diff hygiene, Rust risk scan, and pre-commit passed for API-159. | +| Quality/architecture | pass | API-160 keeps admin runtime action credentials, region, and server config reads behind AppContext resolver boundaries. | +| Migration preservation | pass | Admin authorization, object-ZIP token encryption, object-lambda signing, OIDC restart detection, and site replication metadata keep legacy fallback behavior. | +| Testing/verification | pass | RustFS focused compile, targeted context tests, formatting, migration guard, diff hygiene, Rust risk scan, and pre-commit passed for API-160. | ## Verification Notes Passed before push: +- Issue #660 API-160 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. + - `make pre-commit`: passed. + - AppContext admin runtime resolver scan: passed; direct admin action + credential, server config, and region global reads are isolated to + AppContext default adapters or tests. + - Rust risk scan: no new production panic/todo/unsafe/cast risks added; new + unwrap/expect hits are resolver fallback plumbing or test assertions. + - Issue #660 API-159 current slice: - `cargo check --tests -p rustfs`: passed. - `cargo test -p rustfs resolver_helpers_are_context_first_and_fallback_when_context_is_absent --lib`: diff --git a/rustfs/src/admin/handlers/account_info.rs b/rustfs/src/admin/handlers/account_info.rs index d1284e2e6..202b176db 100644 --- a/rustfs/src/admin/handlers/account_info.rs +++ b/rustfs/src/admin/handlers/account_info.rs @@ -15,13 +15,12 @@ use super::super::versioning_sys::BucketVersioningSys; use crate::admin::auth::authenticate_request; use crate::admin::router::{AdminOperation, Operation, S3Router}; -use crate::app::context::resolve_object_store_handle; +use crate::app::context::{resolve_action_credentials, resolve_object_store_handle}; use crate::auth::get_condition_values; use crate::server::{ADMIN_PREFIX, RemoteAddr}; use http::{HeaderMap, HeaderValue}; use hyper::{Method, StatusCode}; use matchit::Params; -use rustfs_credentials::get_global_action_cred; use rustfs_policy::policy::BucketPolicy; use rustfs_policy::policy::default::DEFAULT_POLICIES; use rustfs_policy::policy::{Args, action::Action, action::S3Action}; @@ -145,10 +144,10 @@ impl Operation for AccountInfoHandler { cred.access_key.clone() }; - let Some(admin_cred) = get_global_action_cred() else { + let Some(admin_cred) = resolve_action_credentials() else { return Err(S3Error::with_message( S3ErrorCode::InternalError, - "get_global_action_cred failed".to_string(), + "action credentials are not initialized".to_string(), )); }; diff --git a/rustfs/src/admin/handlers/config_admin.rs b/rustfs/src/admin/handlers/config_admin.rs index b944bded8..74ef3d83e 100644 --- a/rustfs/src/admin/handlers/config_admin.rs +++ b/rustfs/src/admin/handlers/config_admin.rs @@ -24,7 +24,7 @@ use crate::admin::service::config::{ validate_server_config, }; use crate::admin::utils::{encode_compatible_admin_payload, is_compat_admin_request, read_compatible_admin_body}; -use crate::app::context::resolve_object_store_handle; +use crate::app::context::{resolve_object_store_handle, resolve_server_config}; use crate::auth::{check_key_valid, get_session_token}; use crate::error::ApiError; use crate::server::{ADMIN_PREFIX, RemoteAddr}; @@ -54,9 +54,7 @@ use rustfs_config::oidc::{ OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_SCOPES, OIDC_USERNAME_CLAIM, }; -use rustfs_config::server_config::{ - Config as ServerConfig, DEFAULT_KVS, KV, KVS, get_global_server_config, set_global_server_config, -}; +use rustfs_config::server_config::{Config as ServerConfig, DEFAULT_KVS, KV, KVS, set_global_server_config}; use rustfs_config::{ COMMENT_KEY, DEFAULT_DELIMITER, ENABLE_KEY, ENV_PREFIX, ENV_SCANNER_ALERT_EXCESS_FOLDERS, ENV_SCANNER_ALERT_EXCESS_VERSION_SIZE, ENV_SCANNER_ALERT_EXCESS_VERSIONS, ENV_SCANNER_BITROT_CYCLE_SECS, @@ -727,7 +725,7 @@ async fn load_active_server_config() -> S3Result { return Ok(config); } - get_global_server_config().ok_or_else(|| s3_error!(InternalError, "server config is not initialized")) + resolve_server_config().ok_or_else(|| s3_error!(InternalError, "server config is not initialized")) } async fn save_server_config_to_store(config: &ServerConfig) -> S3Result<()> { diff --git a/rustfs/src/admin/handlers/group.rs b/rustfs/src/admin/handlers/group.rs index f1f2d00ff..7adfdfd74 100644 --- a/rustfs/src/admin/handlers/group.rs +++ b/rustfs/src/admin/handlers/group.rs @@ -20,6 +20,7 @@ use crate::{ router::{AdminOperation, Operation, S3Router}, utils::has_space_be, }, + app::context::resolve_action_credentials, auth::{check_key_valid, constant_time_eq, get_session_token}, server::{ADMIN_PREFIX, RemoteAddr}, }; @@ -28,7 +29,6 @@ use hyper::Method; use matchit::Params; use percent_encoding::percent_decode_str; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_credentials::get_global_action_cred; use rustfs_iam::error::{is_err_no_such_group, is_err_no_such_user}; use rustfs_madmin::{GroupAddRemove, GroupStatus, SITE_REPL_API_VERSION, SRGroupInfo, SRIAMItem}; use rustfs_policy::policy::action::{Action, AdminAction}; @@ -551,7 +551,7 @@ impl Operation for UpdateGroupMembers { )); } - get_global_action_cred() + resolve_action_credentials() .map(|cred| { if constant_time_eq(&cred.access_key, member) { return Err(S3Error::with_message( diff --git a/rustfs/src/admin/handlers/is_admin.rs b/rustfs/src/admin/handlers/is_admin.rs index 9101dbcca..7ace52ab2 100644 --- a/rustfs/src/admin/handlers/is_admin.rs +++ b/rustfs/src/admin/handlers/is_admin.rs @@ -13,11 +13,11 @@ // limitations under the License. use crate::admin::router::Operation; +use crate::app::context::resolve_action_credentials; use crate::auth::{check_key_valid, constant_time_eq, get_condition_values, get_session_token}; use http::{HeaderMap, HeaderValue}; use hyper::StatusCode; use matchit::Params; -use rustfs_credentials::get_global_action_cred; use rustfs_policy::policy::Args; use rustfs_policy::policy::action::{Action, AdminAction}; use s3s::header::CONTENT_TYPE; @@ -47,7 +47,7 @@ impl Operation for IsAdminHandler { let access_key_to_check = input_cred.access_key.clone(); // Check if the user is admin: root user check, then evaluate through the policy engine - let is_admin = if let Some(sys_cred) = get_global_action_cred() { + let is_admin = if let Some(sys_cred) = resolve_action_credentials() { constant_time_eq(&access_key_to_check, &sys_cred.access_key) || constant_time_eq(&cred.parent_user, &sys_cred.access_key) } else { diff --git a/rustfs/src/admin/handlers/object_zip_download.rs b/rustfs/src/admin/handlers/object_zip_download.rs index 90476d90b..f0fc24a16 100644 --- a/rustfs/src/admin/handlers/object_zip_download.rs +++ b/rustfs/src/admin/handlers/object_zip_download.rs @@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::super::get_global_region; use crate::admin::router::{ADMIN_OBJECT_ZIP_DOWNLOADS_PATH, AdminOperation, Operation, S3Router}; -use crate::app::context::resolve_object_store_handle; +use crate::app::context::{resolve_action_credentials, resolve_object_store_handle, resolve_region}; use crate::auth::{check_key_valid, get_session_token}; use crate::error::ApiError; use crate::license::license_check; @@ -31,7 +30,6 @@ use hyper::{Method, Uri}; use matchit::Params; use rand::RngExt; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_credentials::get_global_action_cred; use rustfs_policy::policy::action::{Action, S3Action}; use rustfs_storage_api::{BucketOperations, ListOperations as _, ObjectIO as _, ObjectOperations as _, bucket::BucketOptions}; use rustfs_trusted_proxies::{ClientInfo, ValidationMode}; @@ -206,7 +204,7 @@ impl From for ReqInfo { bucket: snapshot.bucket, object: snapshot.object, version_id: snapshot.version_id, - region: get_global_region(), + region: resolve_region(), request_context: None, } } @@ -284,7 +282,7 @@ impl From for ClientInfo { fn download_token_encryption_key() -> S3Result<[u8; 32]> { let credentials = - get_global_action_cred().ok_or_else(|| s3_error!(InternalError, "global action credentials are not initialized"))?; + resolve_action_credentials().ok_or_else(|| s3_error!(InternalError, "global action credentials are not initialized"))?; if credentials.secret_key.is_empty() { return Err(s3_error!(InternalError, "global action credentials are not initialized")); } @@ -401,7 +399,7 @@ async fn authenticate_object_zip_download_request(req: &mut S3Request) -> req.extensions.insert(ReqInfo { cred: Some(cred), is_owner: owner, - region: get_global_region(), + region: resolve_region(), request_context: req.extensions.get().cloned(), ..Default::default() }); @@ -698,7 +696,7 @@ async fn authorize_zip_items_for_download(record: &ObjectZipDownloadToken, items extensions }, credentials: None, - region: get_global_region(), + region: resolve_region(), service: None, trailing_headers: None, }; @@ -1093,7 +1091,7 @@ mod tests { "object ZIP download token should carry an authenticated payload" ); assert!( - src.contains("get_global_action_cred"), + src.contains("resolve_action_credentials"), "object ZIP download token should be verifiable by any node sharing global credentials" ); assert!( @@ -1378,7 +1376,7 @@ mod tests { } fn ensure_test_signing_credentials() { - if get_global_action_cred().is_none() { + if resolve_action_credentials().is_none() { let _ = init_global_action_credentials( Some("TESTROOTACCESSKEY".to_string()), Some("TESTROOTSECRET1234567890".to_string()), @@ -1401,7 +1399,7 @@ mod tests { bucket: Some("photos".to_string()), object: None, version_id: None, - region: get_global_region(), + region: resolve_region(), request_context: None, } } diff --git a/rustfs/src/admin/handlers/oidc.rs b/rustfs/src/admin/handlers/oidc.rs index 5619d3fb4..5c35e0abd 100644 --- a/rustfs/src/admin/handlers/oidc.rs +++ b/rustfs/src/admin/handlers/oidc.rs @@ -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; +use crate::app::context::{resolve_object_store_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; @@ -29,7 +29,6 @@ use rustfs_config::oidc::{ OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM, }; use rustfs_config::server_config::Config as ServerConfig; -use rustfs_config::server_config::get_global_server_config; use rustfs_config::{DEFAULT_DELIMITER, ENABLE_KEY, EnableState, MAX_ADMIN_REQUEST_BODY_SIZE}; use rustfs_policy::policy::action::{Action, AdminAction}; use rustfs_utils::egress::validate_outbound_url; @@ -827,7 +826,7 @@ fn provider_instance_key(provider_id: &str) -> String { } fn oidc_restart_required(config: &ServerConfig) -> bool { - let active_config = get_global_server_config(); + let active_config = resolve_server_config(); oidc_restart_required_from_active_config(config, active_config.as_ref()) } diff --git a/rustfs/src/admin/handlers/policies.rs b/rustfs/src/admin/handlers/policies.rs index f04c7387e..a3e3d49b8 100644 --- a/rustfs/src/admin/handlers/policies.rs +++ b/rustfs/src/admin/handlers/policies.rs @@ -20,6 +20,7 @@ use crate::{ router::{AdminOperation, Operation, S3Router}, utils::{encode_compatible_admin_payload, has_space_be, read_compatible_admin_body}, }, + app::context::resolve_action_credentials, auth::{check_key_valid, get_session_token}, server::{ADMIN_PREFIX, RemoteAddr}, }; @@ -27,7 +28,6 @@ use http::{HeaderMap, StatusCode}; use hyper::Method; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_credentials::get_global_action_cred; use rustfs_iam::error::is_err_no_such_user; use rustfs_iam::store::MappedPolicy; use rustfs_madmin::{ @@ -520,7 +520,7 @@ impl Operation for SetPolicyForUserOrGroup { } }; - let Some(sys_cred) = get_global_action_cred() else { + let Some(sys_cred) = resolve_action_credentials() else { return Err(s3_error!(InternalError, "failed to load global credentials")); }; @@ -982,7 +982,7 @@ async fn handle_builtin_policy_association(req: S3Request, is_attach: bool } } - let Some(sys_cred) = get_global_action_cred() else { + let Some(sys_cred) = resolve_action_credentials() else { return Err(s3_error!(InternalError, "get global action cred failed")); }; diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index 568a6c217..427cfee00 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -16,6 +16,7 @@ use super::iam_error::iam_error_to_s3_error; use crate::admin::access_key_identity; use crate::admin::handlers::site_replication::site_replication_iam_change_hook; use crate::admin::utils::{encode_compatible_admin_payload, has_space_be, is_compat_admin_request, read_compatible_admin_body}; +use crate::app::context::resolve_action_credentials; use crate::auth::{constant_time_eq, get_condition_values, get_session_token}; use crate::server::{ADMIN_PREFIX, RemoteAddr}; use crate::{ @@ -26,7 +27,7 @@ use http::HeaderMap; use hyper::{Method, StatusCode}; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_credentials::{Credentials as StoredCredentials, get_global_action_cred}; +use rustfs_credentials::Credentials as StoredCredentials; use rustfs_iam::error::{is_err_no_such_service_account, is_err_no_such_temp_account}; use rustfs_iam::store::Store as IamStore; use rustfs_iam::sys::{NewServiceAccountOpts, UpdateServiceAccountOpts}; @@ -267,7 +268,7 @@ impl Operation for AddServiceAccount { None }; - let Some(sys_cred) = get_global_action_cred() else { + let Some(sys_cred) = resolve_action_credentials() else { return Err(s3_error!(InvalidRequest, "get sys cred failed")); }; @@ -1120,7 +1121,7 @@ impl Operation for ListAccessKeysBulk { .into_keys() .collect::>(); - if let Some(sys_cred) = get_global_action_cred() { + if let Some(sys_cred) = resolve_action_credentials() { users.push(sys_cred.access_key); } users diff --git a/rustfs/src/admin/handlers/site_replication.rs b/rustfs/src/admin/handlers/site_replication.rs index 753027644..2e93c303e 100644 --- a/rustfs/src/admin/handlers/site_replication.rs +++ b/rustfs/src/admin/handlers/site_replication.rs @@ -25,7 +25,7 @@ use super::super::replication::{ResyncOpts, get_global_replication_pool}; use super::super::target::{ARN, BucketTarget, BucketTargetType, BucketTargets, Credentials}; use super::super::{AdminReplicationConfigExt as _, AdminVersioningConfigExt as _}; use super::super::{delete_admin_config, read_admin_config, save_admin_config}; -use super::super::{get_global_deployment_id, get_global_endpoints_opt, get_global_region, global_rustfs_port}; +use super::super::{get_global_deployment_id, get_global_endpoints_opt, global_rustfs_port}; use crate::admin::auth::validate_admin_request; use crate::admin::router::{AdminOperation, Operation, S3Router}; use crate::admin::site_replication_identity::{ @@ -33,7 +33,7 @@ use crate::admin::site_replication_identity::{ site_identity_key, }; use crate::admin::utils::{encode_compatible_admin_payload, read_compatible_admin_body}; -use crate::app::context::resolve_object_store_handle; +use crate::app::context::{resolve_object_store_handle, resolve_region, resolve_server_config}; use crate::auth::{check_key_valid, get_session_token}; use crate::config::get_config_snapshot; use crate::error::ApiError; @@ -45,7 +45,6 @@ use http::header::{CONTENT_TYPE, HOST}; use http::{HeaderMap, HeaderValue, Uri}; use hyper::{Method, StatusCode}; use matchit::Params; -use rustfs_config::server_config::get_global_server_config; use rustfs_config::{ DEFAULT_CONSOLE_ADDRESS, DEFAULT_DELIMITER, DEFAULT_RUSTFS_TLS_PATH, ENV_RUSTFS_CONSOLE_ADDRESS, ENV_RUSTFS_TLS_PATH, MAX_ADMIN_REQUEST_BODY_SIZE, @@ -783,7 +782,7 @@ fn ldap_settings_from_kvs(kvs: &rustfs_config::server_config::KVS) -> (LDAPSetti } fn load_ldap_idp_settings() -> (LDAPSettings, LDAPConfigSettings) { - let Some(config) = get_global_server_config() else { + let Some(config) = resolve_server_config() else { return (LDAPSettings::default(), LDAPConfigSettings::default()); }; @@ -1231,7 +1230,7 @@ async fn send_peer_admin_request( access_key, secret_key, "", - get_global_region() + resolve_region() .map(|region| region.to_string()) .as_deref() .unwrap_or("us-east-1"), @@ -1344,7 +1343,7 @@ async fn send_peer_admin_get_request(endpoint: &str, path: &str, access_key: &st access_key, secret_key, "", - get_global_region() + resolve_region() .map(|region| region.to_string()) .as_deref() .unwrap_or("us-east-1"), @@ -1590,7 +1589,7 @@ async fn build_sr_info(state: &SiteReplicationState, local_peer: &PeerInfo) -> S let mut entry = SRBucketInfo { bucket: bucket.name.clone(), created_at: bucket.created, - location: get_global_region().map(|region| region.to_string()).unwrap_or_default(), + location: resolve_region().map(|region| region.to_string()).unwrap_or_default(), api_version: Some(SITE_REPL_API_VERSION.to_string()), ..Default::default() }; @@ -2734,7 +2733,7 @@ fn site_replication_bucket_target_for_peer( let port = parsed.port_or_known_default().ok_or_else(|| { S3Error::with_message(S3ErrorCode::InvalidRequest, format!("peer endpoint missing port: {}", peer.endpoint)) })?; - let region = get_global_region() + let region = resolve_region() .map(|region| region.to_string()) .filter(|region| !region.is_empty()) .unwrap_or_else(|| "us-east-1".to_string()); @@ -4231,7 +4230,7 @@ impl Operation for SRPeerGetIDPSettingsHandler { if let Some(oidc) = get_oidc() { let providers = oidc.list_providers(); settings.open_id.enabled = !providers.is_empty(); - settings.open_id.region = get_global_region().map(|region| region.to_string()).unwrap_or_default(); + settings.open_id.region = resolve_region().map(|region| region.to_string()).unwrap_or_default(); for provider in providers { let Some(config) = oidc.get_provider_config(&provider.provider_id) else { diff --git a/rustfs/src/admin/handlers/sts.rs b/rustfs/src/admin/handlers/sts.rs index 322504025..f014f2135 100644 --- a/rustfs/src/admin/handlers/sts.rs +++ b/rustfs/src/admin/handlers/sts.rs @@ -19,6 +19,7 @@ use crate::{ handlers::site_replication::site_replication_iam_change_hook, router::{AdminOperation, Operation, S3Router}, }, + app::context::resolve_action_credentials, auth::{check_key_valid, extract_string_list_claim, get_session_token}, server::ADMIN_PREFIX, server::RemoteAddr, @@ -28,7 +29,6 @@ use http::header::HeaderValue; use hyper::Method; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_credentials::get_global_action_cred; use rustfs_iam::{manager::get_token_signing_key, oidc::OidcClaims, sys::SESSION_POLICY_NAME}; use rustfs_madmin::{SITE_REPL_API_VERSION, SRIAMItem, SRSTSCredential}; use rustfs_policy::{ @@ -262,7 +262,7 @@ async fn handle_assume_role( .await .map_err(|_| s3_error!(InternalError, "set_temp_user failed"))?; - let root_access_key = get_global_action_cred().map(|cred| cred.access_key); + let root_access_key = resolve_action_credentials().map(|cred| cred.access_key); if root_access_key.as_deref() != Some(new_cred.parent_user.as_str()) && let Err(err) = site_replication_iam_change_hook(SRIAMItem { r#type: "sts-credential".to_string(), diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 55b802fd1..f1952f169 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -21,13 +21,14 @@ use crate::{ router::{AdminOperation, Operation, S3Router}, utils::{encode_compatible_admin_payload, has_space_be, read_compatible_admin_body}, }, + app::context::resolve_action_credentials, auth::{check_key_valid, constant_time_eq, get_session_token}, server::RemoteAddr, }; use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_config::{MAX_ADMIN_REQUEST_BODY_SIZE, MAX_IAM_IMPORT_SIZE}; -use rustfs_credentials::{Credentials, get_global_action_cred}; +use rustfs_credentials::Credentials; use rustfs_iam::{ store::{GroupInfo, MappedPolicy, UserType}, sys::{NewServiceAccountOpts, UpdateServiceAccountOpts}, @@ -220,7 +221,7 @@ impl Operation for AddUser { return Err(s3_error!(InvalidArgument, "secret key is required")); } - if let Some(sys_cred) = get_global_action_cred() + if let Some(sys_cred) = resolve_action_credentials() && constant_time_eq(&sys_cred.access_key, ak) { return Err(s3_error!(InvalidArgument, "cannot create a user with the system access key")); @@ -473,7 +474,7 @@ impl Operation for RemoveUser { return Err(s3_error!(InvalidArgument, "access key is empty")); } - let sys_cred = get_global_action_cred() + let sys_cred = resolve_action_credentials() .ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "failed to load global credentials"))?; if ak == sys_cred.access_key || ak == cred.access_key || cred.parent_user == ak { @@ -932,7 +933,7 @@ impl Operation for ImportIam { } } - let Some(sys_cred) = get_global_action_cred() else { + let Some(sys_cred) = resolve_action_credentials() else { return Err(s3_error!(InvalidRequest, "get sys cred failed")); }; diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs index 7c8cc9a0d..4f6ef598c 100644 --- a/rustfs/src/admin/mod.rs +++ b/rustfs/src/admin/mod.rs @@ -137,8 +137,7 @@ mod ecstore_error { mod ecstore_global { pub(crate) use crate::storage::ecstore_global::{ - GLOBAL_BOOT_TIME, get_global_bucket_monitor, get_global_deployment_id, get_global_endpoints_opt, get_global_region, - global_rustfs_port, + GLOBAL_BOOT_TIME, get_global_bucket_monitor, get_global_deployment_id, get_global_endpoints_opt, global_rustfs_port, }; } @@ -525,10 +524,6 @@ pub(crate) fn get_global_endpoints_opt() -> Option { ecstore_global::get_global_endpoints_opt() } -pub(crate) fn get_global_region() -> Option { - ecstore_global::get_global_region() -} - pub(crate) fn global_rustfs_port() -> u16 { ecstore_global::global_rustfs_port() } diff --git a/rustfs/src/admin/router.rs b/rustfs/src/admin/router.rs index 02852deaf..8a5f73778 100644 --- a/rustfs/src/admin/router.rs +++ b/rustfs/src/admin/router.rs @@ -26,10 +26,10 @@ use super::replication::{ use super::target::{BucketTarget, BucketTargetType, BucketTargets}; use super::versioning_sys::BucketVersioningSys; use super::{AdminReplicationConfigExt as _, AdminVersioningConfigExt as _}; -use super::{get_global_bucket_monitor, get_global_deployment_id, get_global_region}; +use super::{get_global_bucket_monitor, get_global_deployment_id}; use crate::admin::console::{is_console_path, make_console_server}; use crate::admin::handlers::oidc::is_oidc_path; -use crate::app::context::resolve_object_store_handle; +use crate::app::context::{resolve_object_store_handle, resolve_region, resolve_server_config}; use crate::app::object_usecase::DefaultObjectUsecase; use crate::auth::{check_key_valid, get_session_token}; use crate::error::ApiError; @@ -54,7 +54,6 @@ use matchit::Router; use reqwest::Url; use rustfs_config::notify::NOTIFY_WEBHOOK_SUB_SYS; use rustfs_config::server_config::Config; -use rustfs_config::server_config::get_global_server_config; use rustfs_config::{ ENABLE_KEY, WEBHOOK_AUTH_TOKEN, WEBHOOK_CLIENT_CA, WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_SKIP_TLS_VERIFY, @@ -648,7 +647,7 @@ async fn load_current_server_config() -> S3Result { } } - let config = get_global_server_config().ok_or_else(|| s3_error!(InternalError, "server config is not initialized"))?; + let config = resolve_server_config().ok_or_else(|| s3_error!(InternalError, "server config is not initialized"))?; Ok(config) } @@ -751,7 +750,7 @@ fn build_object_lambda_source_url(req: &S3Request) -> S3Result { let region = req .region .clone() - .or_else(get_global_region) + .or_else(resolve_region) .map(|value| value.as_str().to_string()) .unwrap_or_else(|| "us-east-1".to_string()); let session_token = get_session_token(&req.uri, &req.headers).unwrap_or_default().to_string(); @@ -1515,7 +1514,7 @@ async fn authorize_replication_extension_request(req: &mut S3Request, ext_ bucket: Some(ext_req.bucket.clone()), object: None, version_id: None, - region: get_global_region(), + region: resolve_region(), ..Default::default() }); @@ -2244,7 +2243,7 @@ async fn authorize_misc_extension_request(req: &mut S3Request, route: &Mis bucket, object, version_id: None, - region: get_global_region(), + region: resolve_region(), ..Default::default() }); @@ -3663,7 +3662,7 @@ mod tests { access_key: "rustfsadmin".to_string(), secret_key: s3s::auth::SecretKey::from("rustfssecret"), }), - region: get_global_region(), + region: resolve_region(), service: None, trailing_headers: None, }; diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index 7e2dec10d..0501f3c3b 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -32,6 +32,7 @@ use super::metadata_sys::BucketMetadataSys; use super::new_object_layer_fn; use crate::config::RustFSBufferConfig; use rustfs_config::server_config::Config; +use rustfs_credentials::Credentials; use rustfs_iam::{store::object::ObjectStore, sys::IamSys}; use rustfs_kms::KmsServiceManager; use rustfs_lock::LockClient; @@ -93,6 +94,16 @@ pub async fn resolve_local_node_name() -> String { resolve_local_node_name_with(get_global_app_context(), rustfs_common::get_global_local_node_name).await } +/// Resolve action credentials using AppContext-first precedence. +pub fn resolve_action_credentials() -> Option { + resolve_action_credentials_with(get_global_app_context(), || default_action_credential_interface().get()) +} + +/// Resolve region using AppContext-first precedence. +pub fn resolve_region() -> Option { + resolve_region_with(get_global_app_context(), || default_region_interface().get()) +} + /// Resolve tier config handle using AppContext-first precedence. pub fn resolve_tier_config_handle() -> Arc> { resolve_tier_config_handle_with(get_global_app_context(), || default_tier_config_interface().handle()) @@ -171,6 +182,22 @@ where fallback().await } +fn resolve_action_credentials_with( + context: Option>, + fallback: impl FnOnce() -> Option, +) -> Option { + context + .map(|context| context.action_credentials().get()) + .unwrap_or_else(fallback) +} + +fn resolve_region_with( + context: Option>, + fallback: impl FnOnce() -> Option, +) -> Option { + context.map(|context| context.region().get()).unwrap_or_else(fallback) +} + fn resolve_tier_config_handle_with( context: Option>, fallback: impl FnOnce() -> Arc>, @@ -197,10 +224,11 @@ mod tests { use super::super::{Endpoints, PoolEndpoints}; use super::*; use crate::app::context::global::AppContextTestInterfaces; - use crate::app::context::handles::{default_notify_interface, default_region_interface}; + use crate::app::context::handles::default_notify_interface; use crate::app::context::interfaces::{ - BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, KmsInterface, KmsRuntimeInterface, - LocalNodeNameInterface, LockClientInterface, ServerConfigInterface, TierConfigInterface, + ActionCredentialInterface, BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, + KmsInterface, KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, RegionInterface, ServerConfigInterface, + TierConfigInterface, }; use crate::config::{RustFSBufferConfig, WorkloadProfile}; use async_trait::async_trait; @@ -285,6 +313,26 @@ mod tests { } } + struct TestActionCredentialInterface { + credentials: Option, + } + + impl ActionCredentialInterface for TestActionCredentialInterface { + fn get(&self) -> Option { + self.credentials.clone() + } + } + + struct TestRegionInterface { + region: Option, + } + + impl RegionInterface for TestRegionInterface { + fn get(&self) -> Option { + self.region.clone() + } + } + struct TestTierConfigInterface { tier_config: Arc>, } @@ -378,6 +426,16 @@ mod tests { let fallback_lock_client: Arc = Arc::new(LocalClient::new()); let context_node_name = "context-node".to_string(); let fallback_node_name = "fallback-node".to_string(); + let context_credentials = Credentials { + access_key: "context-access-key".to_string(), + ..Default::default() + }; + let fallback_credentials = Credentials { + access_key: "fallback-access-key".to_string(), + ..Default::default() + }; + 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 = Arc::new(AppContext::with_test_interfaces( object_store.clone(), @@ -402,7 +460,12 @@ mod tests { local_node_name: Arc::new(TestLocalNodeNameInterface { name: context_node_name.clone(), }), - region: default_region_interface(), + action_credentials: Arc::new(TestActionCredentialInterface { + credentials: Some(context_credentials.clone()), + }), + region: Arc::new(TestRegionInterface { + region: Some(context_region.clone()), + }), tier_config: Arc::new(TestTierConfigInterface { tier_config: tier_config.clone(), }), @@ -442,6 +505,16 @@ mod tests { resolve_local_node_name_with(Some(context.clone()), || async { fallback_node_name.clone() }).await, context_node_name ); + assert_eq!( + resolve_action_credentials_with(Some(context.clone()), || Some(fallback_credentials.clone())) + .expect("context action credentials") + .access_key, + context_credentials.access_key + ); + assert_eq!( + resolve_region_with(Some(context.clone()), || Some(fallback_region.clone())).expect("context region"), + context_region + ); assert!(Arc::ptr_eq( &resolve_tier_config_handle_with(Some(context.clone()), TierConfigMgr::new), &tier_config @@ -484,6 +557,16 @@ mod tests { resolve_local_node_name_with(None, || async { fallback_node_name.clone() }).await, fallback_node_name ); + assert_eq!( + resolve_action_credentials_with(None, || Some(fallback_credentials.clone())) + .expect("fallback action credentials") + .access_key, + fallback_credentials.access_key + ); + assert_eq!( + resolve_region_with(None, || Some(fallback_region.clone())).expect("fallback region"), + fallback_region + ); assert!(Arc::ptr_eq(&resolve_tier_config_handle_with(None, || tier_config.clone()), &tier_config)); assert_eq!( resolve_server_config_with(None, || Some(server_config.clone())).expect("fallback server config"), diff --git a/rustfs/src/app/context/global.rs b/rustfs/src/app/context/global.rs index 3dff8a3b2..84fadc5d8 100644 --- a/rustfs/src/app/context/global.rs +++ b/rustfs/src/app/context/global.rs @@ -14,13 +14,15 @@ use super::super::{ECStore, set_object_store_resolver}; use super::handles::{ - IamHandle, KmsHandle, default_bucket_metadata_interface, default_buffer_config_interface, default_endpoints_interface, - default_kms_runtime_interface, default_local_node_name_interface, default_lock_client_interface, default_notify_interface, - default_region_interface, default_server_config_interface, default_tier_config_interface, + IamHandle, KmsHandle, default_action_credential_interface, default_bucket_metadata_interface, + default_buffer_config_interface, default_endpoints_interface, default_kms_runtime_interface, + default_local_node_name_interface, default_lock_client_interface, default_notify_interface, default_region_interface, + default_server_config_interface, default_tier_config_interface, }; use super::interfaces::{ - BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, KmsInterface, KmsRuntimeInterface, - LocalNodeNameInterface, LockClientInterface, NotifyInterface, RegionInterface, ServerConfigInterface, TierConfigInterface, + ActionCredentialInterface, BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, KmsInterface, + KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, NotifyInterface, RegionInterface, ServerConfigInterface, + TierConfigInterface, }; use rustfs_iam::{store::object::ObjectStore, sys::IamSys}; use rustfs_kms::KmsServiceManager; @@ -39,6 +41,7 @@ pub struct AppContext { endpoints: Arc, lock_client: Arc, local_node_name: Arc, + action_credentials: Arc, region: Arc, tier_config: Arc, server_config: Arc, @@ -57,6 +60,7 @@ impl AppContext { endpoints: default_endpoints_interface(), lock_client: default_lock_client_interface(), local_node_name: default_local_node_name_interface(), + action_credentials: default_action_credential_interface(), region: default_region_interface(), tier_config: default_tier_config_interface(), server_config: default_server_config_interface(), @@ -109,6 +113,10 @@ impl AppContext { self.local_node_name.clone() } + pub fn action_credentials(&self) -> Arc { + self.action_credentials.clone() + } + pub fn region(&self) -> Arc { self.region.clone() } @@ -136,6 +144,7 @@ pub(super) struct AppContextTestInterfaces { pub(super) endpoints: Arc, pub(super) lock_client: Arc, pub(super) local_node_name: Arc, + pub(super) action_credentials: Arc, pub(super) region: Arc, pub(super) tier_config: Arc, pub(super) server_config: Arc, @@ -155,6 +164,7 @@ impl AppContext { endpoints: interfaces.endpoints, lock_client: interfaces.lock_client, local_node_name: interfaces.local_node_name, + action_credentials: interfaces.action_credentials, region: interfaces.region, tier_config: interfaces.tier_config, server_config: interfaces.server_config, diff --git a/rustfs/src/app/context/handles.rs b/rustfs/src/app/context/handles.rs index 63bc03d95..3e5ec204d 100644 --- a/rustfs/src/app/context/handles.rs +++ b/rustfs/src/app/context/handles.rs @@ -17,14 +17,16 @@ use super::super::TierConfigMgr; use super::super::metadata_sys::{BucketMetadataSys, get_global_bucket_metadata_sys}; use super::super::{get_global_endpoints_opt, get_global_lock_client, get_global_region, get_global_tier_config_mgr}; use super::interfaces::{ - BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, KmsInterface, KmsRuntimeInterface, - LocalNodeNameInterface, LockClientInterface, NotifyInterface, RegionInterface, ServerConfigInterface, TierConfigInterface, + ActionCredentialInterface, BucketMetadataInterface, BufferConfigInterface, EndpointsInterface, IamInterface, KmsInterface, + KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, NotifyInterface, RegionInterface, ServerConfigInterface, + TierConfigInterface, }; use crate::config::{RustFSBufferConfig, get_global_buffer_config}; use async_trait::async_trait; use rustfs_common::get_global_local_node_name; use rustfs_config::server_config::Config; use rustfs_config::server_config::get_global_server_config; +use rustfs_credentials::{Credentials, get_global_action_cred}; use rustfs_iam::{store::object::ObjectStore, sys::IamSys}; use rustfs_kms::{KmsServiceManager, get_global_kms_service_manager}; use rustfs_lock::LockClient; @@ -148,6 +150,16 @@ impl LocalNodeNameInterface for LocalNodeNameHandle { } } +/// Default action credentials interface adapter. +#[derive(Default)] +pub struct ActionCredentialHandle; + +impl ActionCredentialInterface for ActionCredentialHandle { + fn get(&self) -> Option { + get_global_action_cred() + } +} + /// Default region interface adapter. #[derive(Default)] pub struct RegionHandle; @@ -212,6 +224,10 @@ pub fn default_local_node_name_interface() -> Arc { Arc::new(LocalNodeNameHandle) } +pub fn default_action_credential_interface() -> Arc { + Arc::new(ActionCredentialHandle) +} + pub fn default_region_interface() -> Arc { Arc::new(RegionHandle) } diff --git a/rustfs/src/app/context/interfaces.rs b/rustfs/src/app/context/interfaces.rs index 7a9ef1d26..89bbfca98 100644 --- a/rustfs/src/app/context/interfaces.rs +++ b/rustfs/src/app/context/interfaces.rs @@ -18,6 +18,7 @@ use super::super::metadata_sys::BucketMetadataSys; use crate::config::RustFSBufferConfig; use async_trait::async_trait; use rustfs_config::server_config::Config; +use rustfs_credentials::Credentials; use rustfs_iam::{store::object::ObjectStore, sys::IamSys}; use rustfs_kms::KmsServiceManager; use rustfs_lock::LockClient; @@ -80,6 +81,11 @@ pub trait LocalNodeNameInterface: Send + Sync { async fn get(&self) -> String; } +/// Action credentials interface for admin handler integration. +pub trait ActionCredentialInterface: Send + Sync { + fn get(&self) -> Option; +} + /// Region interface for application-layer use-cases. pub trait RegionInterface: Send + Sync { fn get(&self) -> Option;