mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-01 01:14:20 +08:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user