feat(admin): add MinIO-compatible IAM and IDP endpoints (#4334)

feat(admin): add MinIO-compatible IAM/IDP admin endpoints

Register and implement MinIO admin API compatibility for IAM/IDP:
- PUT /v3/import-iam-v2 and POST /v3/revoke-tokens/{userProvider}
- generic /v3/idp-config/{type}[/{name}] CRUD mapped onto existing config
- LDAP/OpenID service-account, policy-entities, and list-access-keys flows

Adds IamSys::delete_temp_account primitive to back STS token revocation.
revoke-tokens requires the broader ListUsers admin capability for
cross-user revocation (self-revocation only needs RemoveServiceAccount),
mirroring the cross-user guard used by the service-account handlers.
Registers admin route-policy inventory entries for every new route.
Unsupported LDAP/OpenID backends return honest compatibility errors.

Refs rustfs/backlog#609 #610 #616
This commit is contained in:
Zhengchao An
2026-07-07 05:12:30 +08:00
committed by GitHub
parent 3420f28c17
commit 8f11222a63
7 changed files with 1120 additions and 4 deletions
+21
View File
@@ -330,6 +330,27 @@ impl<T: Store> IamSys<T> {
Ok(())
}
/// Delete a single temporary/STS account by its access key.
///
/// Unlike [`Self::delete_user`], which operates on regular (`UserType::Reg`)
/// identities, this removes an STS credential (`UserType::Sts`) from both the
/// in-memory cache and the backing store, immediately invalidating the
/// associated session token. This is the primitive used by the admin
/// `revoke-tokens` endpoint to revoke STS credentials for a parent user.
pub async fn delete_temp_account(&self, access_key: &str, notify: bool) -> Result<()> {
self.store.delete_user(access_key, UserType::Sts).await?;
if notify && !self.has_watcher() {
for r in notify_iam_delete_user(access_key).await {
if let Some(err) = r.err {
warn!("notify delete_temp_account failed: {}", err);
}
}
}
Ok(())
}
async fn notify_for_user(&self, name: &str, is_temp: bool) {
if self.has_watcher() {
return;
File diff suppressed because it is too large Load Diff
+1
View File
@@ -26,6 +26,7 @@ pub mod group;
pub mod heal;
pub mod health;
pub(crate) mod iam_error;
pub mod idp_compat;
pub mod is_admin;
pub mod kms;
pub mod kms_dynamic;
+4 -1
View File
@@ -937,7 +937,10 @@ async fn handle_builtin_policy_entities(req: S3Request<Body>) -> S3Result<S3Resp
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
}
async fn handle_builtin_policy_association(req: S3Request<Body>, is_attach: bool) -> S3Result<S3Response<(StatusCode, Body)>> {
pub(crate) async fn handle_builtin_policy_association(
req: S3Request<Body>,
is_attach: bool,
) -> S3Result<S3Response<(StatusCode, Body)>> {
let Some(input_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
+4 -3
View File
@@ -33,9 +33,9 @@ mod console_test;
mod route_registration_test;
use handlers::{
audit, batch_job, bucket_meta, cluster_snapshot, config_admin, diagnostics, extensions, heal, health, kms, module_switch,
object_zip_download, oidc, plugins_catalog, plugins_instances, pools, profile_admin, quota as quota_handler, rebalance,
replication as replication_handler, scanner, site_replication, sts, system, table_catalog, tier, tls_debug, user,
audit, batch_job, bucket_meta, cluster_snapshot, config_admin, diagnostics, extensions, heal, health, idp_compat, kms,
module_switch, object_zip_download, oidc, plugins_catalog, plugins_instances, pools, profile_admin, quota as quota_handler,
rebalance, replication as replication_handler, scanner, site_replication, sts, system, table_catalog, tier, tls_debug, user,
};
use router::{AdminOperation, S3Router};
use s3s::route::S3Route;
@@ -86,6 +86,7 @@ fn register_admin_routes(r: &mut S3Router<AdminOperation>) -> std::io::Result<()
tls_debug::register_tls_debug_route(r)?;
kms::register_kms_route(r)?;
oidc::register_oidc_route(r)?;
idp_compat::register_idp_compat_route(r)?;
table_catalog::register_table_catalog_route(r)?;
Ok(())
+70
View File
@@ -68,6 +68,7 @@ const LIST_USERS: AdminActionRef = AdminActionRef::new("ListUsersAdminAction");
const PROFILING: AdminActionRef = AdminActionRef::new("ProfilingAdminAction");
const REBALANCE: AdminActionRef = AdminActionRef::new("RebalanceAdminAction");
const REGISTER_TABLE: AdminActionRef = AdminActionRef::new("RegisterTableAction");
const REMOVE_SERVICE_ACCOUNT: AdminActionRef = AdminActionRef::new("RemoveServiceAccountAdminAction");
const REMOVE_USER_FROM_GROUP: AdminActionRef = AdminActionRef::new("RemoveUserFromGroupAdminAction");
const RUN_TABLE_MAINTENANCE: AdminActionRef = AdminActionRef::new("RunTableMaintenanceAction");
const SERVER_INFO: AdminActionRef = AdminActionRef::new("ServerInfoAdminAction");
@@ -176,6 +177,55 @@ pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
),
admin(HttpMethod::Get, "/rustfs/admin/v3/export-iam", EXPORT_IAM, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/import-iam", IMPORT_IAM, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/import-iam-v2", IMPORT_IAM, RouteRiskLevel::High),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/revoke-tokens/{user_provider}",
REMOVE_SERVICE_ACCOUNT,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/idp-config/{idp_type}",
SERVER_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/idp-config/{idp_type}/{name}",
SERVER_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/idp-config/{idp_type}/{name}",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/idp-config/{idp_type}/{name}",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/idp-config/{idp_type}/{name}",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/idp/ldap/add-service-account",
CREATE_SERVICE_ACCOUNT,
RouteRiskLevel::High,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/idp/ldap/policy/{operation}",
ATTACH_POLICY,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/list-canned-policies",
@@ -1297,6 +1347,26 @@ pub const DEFERRED_ADMIN_ROUTE_POLICIES: &[DeferredAdminRoutePolicy] = &[
"/rustfs/admin/v3/idp/builtin/policy-entities",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/idp/ldap/policy-entities",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/idp/ldap/list-access-keys",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/idp/ldap/list-access-keys-bulk",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/idp/openid/list-access-keys-bulk",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(HttpMethod::Post, "/rustfs/admin/v3/service", DeferredRoutePolicyReason::NotImplemented),
deferred(
HttpMethod::Get,
@@ -318,6 +318,20 @@ fn expected_admin_route_matrix() -> Vec<RouteMatrixEntry> {
admin_route_sample(Method::PUT, "/v3/oidc/config/{provider_id}", "/v3/oidc/config/default"),
admin_route_sample(Method::DELETE, "/v3/oidc/config/{provider_id}", "/v3/oidc/config/default"),
admin_route(Method::POST, "/v3/oidc/validate"),
// idp_compat (rustfs/backlog#609 #610 #616)
admin_route(Method::PUT, "/v3/import-iam-v2"),
admin_route_sample(Method::POST, "/v3/revoke-tokens/{user_provider}", "/v3/revoke-tokens/builtin"),
admin_route_sample(Method::GET, "/v3/idp-config/{idp_type}", "/v3/idp-config/openid"),
admin_route_sample(Method::GET, "/v3/idp-config/{idp_type}/{name}", "/v3/idp-config/openid/default"),
admin_route_sample(Method::PUT, "/v3/idp-config/{idp_type}/{name}", "/v3/idp-config/openid/default"),
admin_route_sample(Method::POST, "/v3/idp-config/{idp_type}/{name}", "/v3/idp-config/openid/default"),
admin_route_sample(Method::DELETE, "/v3/idp-config/{idp_type}/{name}", "/v3/idp-config/openid/default"),
admin_route(Method::PUT, "/v3/idp/ldap/add-service-account"),
admin_route(Method::GET, "/v3/idp/ldap/list-access-keys"),
admin_route(Method::GET, "/v3/idp/ldap/list-access-keys-bulk"),
admin_route(Method::GET, "/v3/idp/ldap/policy-entities"),
admin_route_sample(Method::POST, "/v3/idp/ldap/policy/{operation}", "/v3/idp/ldap/policy/attach"),
admin_route(Method::GET, "/v3/idp/openid/list-access-keys-bulk"),
table_route(Method::GET, "/config"),
table_route_sample(Method::PUT, "/buckets/{warehouse}", "/buckets/analytics"),
table_route_sample(Method::GET, "/buckets/{warehouse}", "/buckets/analytics"),