fix(admin): align accountinfo policy with IAM prepare_auth for OIDC console (#2568)

Co-authored-by: GatewayJ <8352692332qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
GatewayJ
2026-04-17 13:38:18 +08:00
committed by GitHub
parent 478720d2ee
commit f255b8a9f1
2 changed files with 56 additions and 45 deletions

View File

@@ -134,6 +134,19 @@ impl PreparedIamAuth {
}
}
}
/// Returns the resolved identity policy prepared for the current auth mode.
///
/// This is intended for read-only views (for example `/accountinfo`) so
/// callers can reuse the same policy resolution path as authorization.
pub fn combined_policy_for_view(&self) -> Option<&Policy> {
match &self.mode {
PreparedIamMode::Regular { combined_policy } => Some(combined_policy),
PreparedIamMode::Sts { combined_policy, .. } => Some(combined_policy),
PreparedIamMode::ServiceAccount { combined_policy, .. } => Some(combined_policy),
PreparedIamMode::Opa | PreparedIamMode::Owner | PreparedIamMode::Deny => None,
}
}
}
impl<T: Store> IamSys<T> {
@@ -1286,6 +1299,31 @@ mod tests {
use std::collections::HashMap;
use time::OffsetDateTime;
#[test]
fn test_combined_policy_for_view_returns_regular_policy() {
let policy = Policy {
version: "2012-10-17".to_string(),
..Default::default()
};
let prepared = PreparedIamAuth {
needs_existing_object_tag: false,
mode: PreparedIamMode::Regular { combined_policy: policy },
};
let resolved = prepared.combined_policy_for_view();
assert_eq!(resolved.map(|p| p.version.as_str()), Some("2012-10-17"));
}
#[test]
fn test_combined_policy_for_view_returns_none_for_deny() {
let prepared = PreparedIamAuth {
needs_existing_object_tag: false,
mode: PreparedIamMode::Deny,
};
assert!(prepared.combined_policy_for_view().is_none());
}
/// Mock Store for STS tests: either group-attached policies via parent user, or no IAM policies.
#[derive(Clone)]
struct StsTestMockStore {

View File

@@ -23,7 +23,6 @@ use rustfs_credentials::get_global_action_cred;
use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys;
use rustfs_ecstore::new_object_layer_fn;
use rustfs_ecstore::store_api::{BucketOperations, BucketOptions, StorageAPI};
use rustfs_iam::store::MappedPolicy;
use rustfs_policy::policy::BucketPolicy;
use rustfs_policy::policy::default::DEFAULT_POLICIES;
use rustfs_policy::policy::{Args, action::Action, action::S3Action};
@@ -146,22 +145,6 @@ impl Operation for AccountInfoHandler {
cred.access_key.clone()
};
let claims_args = Args {
account: "",
groups: &None,
action: Action::None,
bucket: "",
conditions: &HashMap::new(),
is_owner: false,
object: "",
claims,
deny_only: false,
};
let role_arn = claims_args.get_role_arn();
// TODO: get_policies_from_claims(claims);
let Some(admin_cred) = get_global_action_cred() else {
return Err(S3Error::with_message(
S3ErrorCode::InternalError,
@@ -178,35 +161,25 @@ impl Operation for AccountInfoHandler {
break;
}
}
} else if let Some(arn) = role_arn {
let (_, policy_name) = iam_store
.get_role_policy(arn)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
let policies = MappedPolicy::new(&policy_name).to_slice();
effective_policy = iam_store.get_combined_policy(&policies).await;
} else if let Some(claim_policies) = claims.get("policy").and_then(|v| v.as_str()) {
// STS/OIDC users: resolve policy names from JWT claims against built-in policies
let mut resolved = Vec::new();
for policy_name in claim_policies.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
for (name, p) in DEFAULT_POLICIES.iter() {
if *name == policy_name {
resolved.push(p.clone());
break;
}
}
}
if !resolved.is_empty() {
effective_policy = rustfs_policy::policy::Policy::merge_policies(resolved);
}
} else {
let policies = iam_store
.policy_db_get(&account_name, &cred.groups)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get policy failed: {e}")))?;
effective_policy = iam_store.get_combined_policy(&policies).await;
// Reuse the canonical IAM preparation path so accountinfo policy view
// stays in sync with real authorization semantics (STS/group fallback included).
let empty_conditions = HashMap::new();
let auth_args = Args {
account: &cred.access_key,
groups: &cred.groups,
action: Action::None,
bucket: "",
conditions: &empty_conditions,
is_owner: owner,
object: "",
claims,
deny_only: false,
};
let prepared = iam_store.prepare_auth(&auth_args).await;
if let Some(policy) = prepared.combined_policy_for_view() {
effective_policy = policy.clone();
}
};
let policy_str = serde_json::to_string(&effective_policy)