fix(iam): align OIDC parent IDs with MinIO (#5290)

* fix(iam): align OIDC parent IDs with MinIO

* test(iam): cover OIDC STS binding policy lookup

* test(admin): use runtime facade for AppContext setup

* test(ilm): wait for lifecycle backfill before manual failure

* test(ilm): make overlapping admission check concurrent
This commit is contained in:
GatewayJ
2026-07-27 15:16:22 +08:00
committed by GitHub
parent e076e8cc6e
commit 24cf2cdb78
7 changed files with 259 additions and 65 deletions
+1
View File
@@ -32,6 +32,7 @@ pub(crate) use crate::runtime_sources::{
#[cfg(test)]
pub(crate) use crate::runtime_sources::{
IamInterface, KmsInterface, NotificationSystemInterface, ServerConfigInterface, StorageClassInterface,
publish_test_app_context,
};
use rustfs_config::server_config::Config;
use rustfs_kms::KmsServiceManager;
+76 -6
View File
@@ -266,7 +266,7 @@ impl FederatedSessionBinding for DefaultFederatedSessionBinding {
FederatedSessionBindingError::InvalidRequest("verified OIDC identity is missing issuer or subject".to_string())
})?;
let selected_policy_names = match iam_store
.policy_db_get(&parent_user, &Some(authorization.groups.clone()))
.sts_policy_db_get(&parent_user, &Some(authorization.groups.clone()))
.await
.map_err(|_| FederatedSessionBindingError::Internal("failed to resolve OIDC policy mapping".to_string()))?
{
@@ -310,7 +310,13 @@ impl FederatedSessionBinding for DefaultFederatedSessionBinding {
#[cfg(test)]
mod tests {
use super::*;
use crate::admin::runtime_sources::{AppContext, publish_test_app_context};
use rustfs_iam::federation::{FederatedAuthorization, FederatedClaims};
use rustfs_iam::store::{Store, UserType, object::IAM_CONFIG_PREFIX};
use rustfs_kms::KmsServiceManager;
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq};
use serial_test::serial;
use std::sync::Arc;
fn transaction() -> FederatedSessionTransaction {
FederatedSessionTransaction {
@@ -348,14 +354,14 @@ mod tests {
}
#[test]
fn issued_credentials_and_replication_item_preserve_existing_shape() {
fn issued_credentials_and_replication_item_use_minio_parent_shape() {
let transaction = transaction();
let secret = "federated-session-test-signing-secret";
let selected_policy_names = vec!["readonly".to_string()];
let credentials =
issue_credentials(&transaction, &selected_policy_names, Some(secret)).expect("credential issuance should succeed");
assert_eq!(credentials.parent_user, "openid=pUmguI1petsjVfDFQppmmR9yqdmWnBAXGJhHV_s9W3I");
assert_eq!(credentials.parent_user, "TwyekekG2eMes0qk9Tgh7KXEitwGi1z2W1f2KccrXGA");
assert_eq!(credentials.groups, Some(vec!["devs".to_string()]));
let claims = rustfs_iam::sys::get_claims_from_token_with_secret(&credentials.session_token, secret)
@@ -364,11 +370,11 @@ mod tests {
assert_eq!(claims.get("oidc_provider"), Some(&serde_json::json!("default")));
assert_eq!(
claims.get("parent"),
Some(&serde_json::json!("openid=pUmguI1petsjVfDFQppmmR9yqdmWnBAXGJhHV_s9W3I"))
Some(&serde_json::json!("TwyekekG2eMes0qk9Tgh7KXEitwGi1z2W1f2KccrXGA"))
);
assert_eq!(
claims.get(OIDC_VIRTUAL_PARENT_CLAIM),
Some(&serde_json::json!("openid=pUmguI1petsjVfDFQppmmR9yqdmWnBAXGJhHV_s9W3I"))
Some(&serde_json::json!("TwyekekG2eMes0qk9Tgh7KXEitwGi1z2W1f2KccrXGA"))
);
assert_eq!(claims.get("policy"), Some(&serde_json::json!("readonly")));
assert_eq!(claims.get("groups"), Some(&serde_json::json!(["devs"])));
@@ -384,13 +390,77 @@ mod tests {
assert_eq!(replicated.access_key, credentials.access_key);
assert_eq!(replicated.secret_key, credentials.secret_key);
assert_eq!(replicated.session_token, credentials.session_token);
assert_eq!(replicated.parent_user, "openid=pUmguI1petsjVfDFQppmmR9yqdmWnBAXGJhHV_s9W3I");
assert_eq!(replicated.parent_user, "TwyekekG2eMes0qk9Tgh7KXEitwGi1z2W1f2KccrXGA");
assert_eq!(replicated.parent_policy_mapping, OIDC_STS_REQUIRES_VIRTUAL_PARENT_RECEIVER_POLICY);
assert!(replicated.parent_policy_mapping.trim().is_empty());
assert!(MappedPolicy::new(&replicated.parent_policy_mapping).to_slice().is_empty());
assert_eq!(replicated.api_version.as_deref(), Some(SITE_REPL_API_VERSION));
}
#[tokio::test]
#[serial]
async fn binding_uses_sts_policy_when_regular_mapping_collides() {
let _ = rustfs_credentials::init_global_action_credentials(
Some("TESTROOTACCESSKEY".to_string()),
Some("TESTROOTSECRET123".to_string()),
);
if current_ready_iam_handle().is_err() {
let env = rustfs_test_utils::TestECStoreEnv::builder()
.prefix("federated_binding_sts_policy")
.disk_count(1)
.init_bucket_metadata(false)
.build()
.await;
ObjectStore::new(Arc::clone(&env.ecstore))
.save_iam_config(serde_json::json!({"version": 1}), format!("{}/format.json", *IAM_CONFIG_PREFIX))
.await
.expect("seed IAM format");
let iam = rustfs_iam::build_iam_sys(Arc::clone(&env.ecstore))
.await
.expect("build test IAM");
publish_test_app_context(Arc::new(AppContext::with_default_interfaces(
env.ecstore,
iam,
Arc::new(KmsServiceManager::new()),
)));
}
let iam = current_ready_iam_handle().expect("test IAM should be ready");
let mut transaction = transaction();
transaction.authorization.claims.sub = "binding-sts-policy-subject".to_string();
transaction.authorization.policies = vec!["readwrite".to_string()];
let parent = transaction
.authorization
.oidc_virtual_parent()
.expect("test authorization should have a virtual parent");
iam.create_user(
&parent,
&AddOrUpdateUserReq {
secret_key: "regular-user-secret".to_string(),
policy: None,
status: AccountStatus::Enabled,
},
)
.await
.expect("create colliding regular user");
iam.policy_db_set(&parent, UserType::Reg, false, "readonly")
.await
.expect("store colliding regular policy mapping");
iam.policy_db_set(&parent, UserType::Sts, false, "writeonly")
.await
.expect("store STS policy mapping");
let credentials = DefaultFederatedSessionBinding
.bind(&transaction)
.await
.expect("production binding should issue credentials");
let signing_key = current_token_signing_key().expect("test signing key should be initialized");
let claims = rustfs_iam::sys::get_claims_from_token_with_secret(&credentials.session_token, &signing_key)
.expect("issued session token should verify");
assert_eq!(claims.get("policy"), Some(&serde_json::json!("writeonly")));
}
#[test]
fn invalid_session_policy_precedes_missing_signing_key() {
let mut transaction = transaction();
+2
View File
@@ -59,6 +59,8 @@ pub(crate) fn set_test_outbound_tls_generation(generation: u64) {
#[cfg(test)]
pub(crate) use context::install_test_app_context;
#[cfg(test)]
pub(crate) use context::publish_global_app_context as publish_test_app_context;
#[cfg(test)]
pub(crate) use context::{IamInterface, KmsInterface, NotificationSystemInterface, ServerConfigInterface, StorageClassInterface};