feat: add admin route policy inventory (#3286)

* feat: add admin route policy inventory

* fix: align table route policy actions

* fix: align load table route policy action
This commit is contained in:
安正超
2026-06-09 10:17:50 +08:00
committed by GitHub
parent aabda41ae5
commit b9a3a2245a
5 changed files with 920 additions and 29 deletions
Generated
+1
View File
@@ -9105,6 +9105,7 @@ dependencies = [
"rustfs-s3select-api", "rustfs-s3select-api",
"rustfs-s3select-query", "rustfs-s3select-query",
"rustfs-scanner", "rustfs-scanner",
"rustfs-security-governance",
"rustfs-signer", "rustfs-signer",
"rustfs-targets", "rustfs-targets",
"rustfs-tls-runtime", "rustfs-tls-runtime",
+36 -29
View File
@@ -5,15 +5,17 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context ## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
- Branch: `overtrue/arch-security-governance-policy-types` - Branch: `overtrue/arch-admin-route-policy`
- Baseline: `upstream/main` at `7a9bf707ee66e779f85e6e00cedfaa10ec2af4c2` - Baseline: `origin/main` at `3d0e6ce0da93de0f4618beb194ae2241df71f344`
- PR type for this branch: `contract` - PR type for this branch: `contract`
- Runtime behavior changes: none - Runtime behavior changes: none
- Rust code changes: add redaction, serde policy, and artifact integrity - Rust code changes: add `rustfs/src/admin/route_policy.rs` as a pure
contract modules to the pure rustfs-security-governance crate, with typed admin-route policy inventory backed by `rustfs-security-governance` route
validation errors and unit tests. contract types, with explicit deferred entries for routes that need
contextual, S3-action, multi-action, credential-only, or not-implemented
contract support.
- CI/script changes: none - CI/script changes: none
- Docs changes: record the Phase 1 policy contract handoff. - Docs changes: record the S-006 route policy handoff.
## Phase 0 Tasks ## Phase 0 Tasks
@@ -88,52 +90,57 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
errors model digest, signature, and provenance requirements without changing errors model digest, signature, and provenance requirements without changing
release or CI behavior. release or CI behavior.
- Verification: `cargo test -p rustfs-security-governance`. - Verification: `cargo test -p rustfs-security-governance`.
- [ ] `S-006` Add `rustfs/src/admin/route_policy.rs` backed by these contract - [x] `S-006` Add `rustfs/src/admin/route_policy.rs` backed by these contract
types, without changing route registration or auth behavior. types, without changing route registration or auth behavior.
- Acceptance: direct `AdminRouteSpec` entries cover routes with a single
stable admin policy action, deferred inventory records routes that need
richer contract support, and tests prove the combined inventory covers every
registered admin route.
## Next PRs ## Next PRs
1. `contract`: add an admin route policy table that consumes the new admin 1. `contract`: add initial policy inventory tables for redaction, serde, or
route matrix types while preserving route registration and auth behavior.
2. `contract`: add initial policy inventory tables for redaction, serde, or
supply-chain governance only after the contract shape remains stable. supply-chain governance only after the contract shape remains stable.
## Pre-Push Review Log ## Pre-Push Review Log
| Expert | Status | Notes | | Expert | Status | Notes |
|---|---|---| |---|---|---|
| Quality/architecture | pass | Confirmed the staged diff includes the new modules, keeps a pure `contract` design, uses clear API names and typed errors, and does not introduce runtime/admin/router/auth/startup/storage/config/global-state integration | | Quality/architecture | pass | Pure contract inventory module; no runtime route registration, alias canonicalization, or handler auth mutation. Names and deferred reasons are explicit and avoid false policy precision. |
| Migration preservation | pass | Confirmed this PR only completes `S-003` through `S-005`, does not shift away from backlog #660, does not touch storage hot paths or global-state migration, and does not need compatibility markers | | Migration preservation | pass | Aligns with backlog #660: directory and contract boundary first, no global-state migration, no crate split, and no storage hot-path behavior drift. |
| Testing/verification | pass | Confirmed focused contract tests assert valid and invalid policy behavior, production logic was not changed to satisfy tests, focused checks passed, and full `make pre-commit` passed | | Testing/verification | pass | Route-policy tests cover validation, public exceptions, table-catalog scope, deferred contextual routes, and every registered admin route. Focused checks, guard scripts, and full pre-commit pass. |
## Verification Notes ## Verification Notes
Passed: Passed:
- `cargo check -p rustfs-security-governance` - `cargo test -p rustfs admin::route_policy`
- `cargo test -p rustfs-security-governance` - `cargo fmt --all`
- `cargo fmt --all --check` - `cargo fmt --all --check`
- `cargo tree -p rustfs-security-governance --edges normal` - `cargo check -p rustfs`
- `./scripts/check_architecture_migration_rules.sh` - `./scripts/check_architecture_migration_rules.sh`
- `./scripts/check_layer_dependencies.sh`
- `./scripts/check_metrics_migration_refs.sh` - `./scripts/check_metrics_migration_refs.sh`
- `./scripts/check_layer_dependencies.sh`
- `git diff --check` - `git diff --check`
- Rust quality scans for production `unwrap`/`expect`, narrowing casts,
string errors, boxed dynamic errors, debug printing, and relaxed atomics in
`rustfs/src/admin/route_policy.rs`
- `make pre-commit` - `make pre-commit`
Notes: Notes:
- `cargo test -p rustfs-security-governance` passed 20 unit tests. - `cargo test -p rustfs admin::route_policy` passed 7 route-policy tests.
- `make pre-commit` passed, including 5513 nextest tests, 105 skipped tests, - `make pre-commit` passed all checks, including 5526 nextest tests and
and workspace doctests. workspace doctests.
## Handoff Notes ## Handoff Notes
- Keep this Phase 1 branch as a pure `contract` PR. Do not add - Keep this S-006 branch as a pure `contract` PR. Do not change
`rustfs/src/admin` integration, route registration changes, auth enforcement, admin route registration, `/minio/admin` alias canonicalization, handler auth
Config moves, Storage API moves, Runtime moves, or ECStore moves. enforcement, Config moves, Storage API moves, Runtime moves, or ECStore moves.
- The new crate is allowed to depend on generic Rust libraries such as - `rustfs` may depend on `rustfs-security-governance` for contract metadata;
`thiserror`, but must stay independent from implementation crates and runtime the security-governance crate must stay independent from implementation
state. crates and runtime state.
- Do not add temporary compatibility code without a matching - Do not add temporary compatibility code without a matching
`RUSTFS_COMPAT_TODO(<task-id>)` marker and cleanup-register entry. `RUSTFS_COMPAT_TODO(<task-id>)` marker and cleanup-register entry.
- The next admin route policy PR may consume the contract types, but must - Deferred route policy entries must remain explicit until the contract crate
preserve current route registration, alias canonicalization, public gains richer support for contextual owner checks, S3 actions, multiple
exceptions, and handler-level authorization behavior. accepted actions, credential-only gates, and not-implemented routes.
+1
View File
@@ -82,6 +82,7 @@ rustfs-protos = { workspace = true }
rustfs-rio.workspace = true rustfs-rio.workspace = true
rustfs-s3-types = { workspace = true } rustfs-s3-types = { workspace = true }
rustfs-s3-ops = { workspace = true } rustfs-s3-ops = { workspace = true }
rustfs-security-governance = { workspace = true }
rustfs-data-usage = { workspace = true } rustfs-data-usage = { workspace = true }
rustfs-s3select-api = { workspace = true } rustfs-s3select-api = { workspace = true }
rustfs-s3select-query = { workspace = true } rustfs-s3select-query = { workspace = true }
+3
View File
@@ -16,6 +16,9 @@ mod auth;
pub mod console; pub mod console;
pub mod handlers; pub mod handlers;
mod plugin_contract; mod plugin_contract;
// Contract inventory is validated by tests before later runtime integration.
#[allow(dead_code)]
pub(crate) mod route_policy;
pub mod router; pub mod router;
pub mod service; pub mod service;
pub mod site_replication_identity; pub mod site_replication_identity;
+879
View File
@@ -0,0 +1,879 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use rustfs_security_governance::{
AdminActionRef, AdminRouteMatrixError, AdminRouteSpec, HttpMethod, PublicRouteKind, RouteRiskLevel,
validate_admin_route_specs,
};
const ALL_ADMIN: AdminActionRef = AdminActionRef::new("AllAdminActions");
const ADD_USER_TO_GROUP: AdminActionRef = AdminActionRef::new("AddUserToGroupAdminAction");
const ATTACH_POLICY: AdminActionRef = AdminActionRef::new("AttachPolicyAdminAction");
const CONFIG_UPDATE: AdminActionRef = AdminActionRef::new("ConfigUpdateAdminAction");
const COMMIT_TABLE: AdminActionRef = AdminActionRef::new("CommitTableAction");
const CREATE_POLICY: AdminActionRef = AdminActionRef::new("CreatePolicyAdminAction");
const CREATE_SERVICE_ACCOUNT: AdminActionRef = AdminActionRef::new("CreateServiceAccountAdminAction");
const CREATE_TABLE: AdminActionRef = AdminActionRef::new("CreateTableAction");
const DECOMMISSION: AdminActionRef = AdminActionRef::new("DecommissionAdminAction");
const DELETE_POLICY: AdminActionRef = AdminActionRef::new("DeletePolicyAdminAction");
const DELETE_TABLE: AdminActionRef = AdminActionRef::new("DeleteTableAction");
const DELETE_TABLE_NAMESPACE: AdminActionRef = AdminActionRef::new("DeleteTableNamespaceAction");
const DELETE_USER: AdminActionRef = AdminActionRef::new("DeleteUserAdminAction");
const ENABLE_GROUP: AdminActionRef = AdminActionRef::new("EnableGroupAdminAction");
const ENABLE_USER: AdminActionRef = AdminActionRef::new("EnableUserAdminAction");
const EXPORT_BUCKET_METADATA: AdminActionRef = AdminActionRef::new("ExportBucketMetadataAction");
const EXPORT_IAM: AdminActionRef = AdminActionRef::new("ExportIAMAction");
const GET_BUCKET_TARGET: AdminActionRef = AdminActionRef::new("GetBucketTargetAction");
const GET_GROUP: AdminActionRef = AdminActionRef::new("GetGroupAdminAction");
const GET_POLICY: AdminActionRef = AdminActionRef::new("GetPolicyAdminAction");
const GET_REPLICATION_METRICS: AdminActionRef = AdminActionRef::new("GetReplicationMetricsAction");
const GET_TABLE: AdminActionRef = AdminActionRef::new("GetTableAction");
const GET_TABLE_CATALOG: AdminActionRef = AdminActionRef::new("GetTableCatalogAction");
const GET_TABLE_METADATA: AdminActionRef = AdminActionRef::new("GetTableMetadataAction");
const GET_TABLE_NAMESPACE: AdminActionRef = AdminActionRef::new("GetTableNamespaceAction");
const HEAL: AdminActionRef = AdminActionRef::new("HealAdminAction");
const IMPORT_BUCKET_METADATA: AdminActionRef = AdminActionRef::new("ImportBucketMetadataAction");
const IMPORT_IAM: AdminActionRef = AdminActionRef::new("ImportIAMAction");
const KMS_CREATE_KEY: AdminActionRef = AdminActionRef::new("KMSCreateKeyAdminAction");
const KMS_KEY_STATUS: AdminActionRef = AdminActionRef::new("KMSKeyStatusAdminAction");
const LIST_GROUPS: AdminActionRef = AdminActionRef::new("ListGroupsAdminAction");
const LIST_TEMPORARY_ACCOUNTS: AdminActionRef = AdminActionRef::new("ListTemporaryAccountsAdminAction");
const LIST_TIER: AdminActionRef = AdminActionRef::new("ListTierAction");
const LIST_USER_POLICIES: AdminActionRef = AdminActionRef::new("ListUserPoliciesAdminAction");
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_USER_FROM_GROUP: AdminActionRef = AdminActionRef::new("RemoveUserFromGroupAdminAction");
const SERVER_INFO: AdminActionRef = AdminActionRef::new("ServerInfoAdminAction");
const SET_BUCKET_QUOTA: AdminActionRef = AdminActionRef::new("SetBucketQuotaAdminAction");
const SET_BUCKET_TARGET: AdminActionRef = AdminActionRef::new("SetBucketTargetAction");
const SET_TABLE: AdminActionRef = AdminActionRef::new("SetTableAction");
const SET_TABLE_NAMESPACE: AdminActionRef = AdminActionRef::new("SetTableNamespaceAction");
const SET_TIER: AdminActionRef = AdminActionRef::new("SetTierAction");
const SITE_REPLICATION_ADD: AdminActionRef = AdminActionRef::new("SiteReplicationAddAction");
const SITE_REPLICATION_INFO: AdminActionRef = AdminActionRef::new("SiteReplicationInfoAction");
const SITE_REPLICATION_OPERATION: AdminActionRef = AdminActionRef::new("SiteReplicationOperationAction");
const SITE_REPLICATION_REMOVE: AdminActionRef = AdminActionRef::new("SiteReplicationRemoveAction");
const SITE_REPLICATION_RESYNC: AdminActionRef = AdminActionRef::new("SiteReplicationResyncAction");
const STORAGE_INFO: AdminActionRef = AdminActionRef::new("StorageInfoAdminAction");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeferredRoutePolicyReason {
ContextualAuthorization,
CredentialOnly,
MultipleActions,
NotImplemented,
S3Action,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DeferredAdminRoutePolicy {
method: HttpMethod,
path: &'static str,
reason: DeferredRoutePolicyReason,
}
impl DeferredAdminRoutePolicy {
pub const fn new(method: HttpMethod, path: &'static str, reason: DeferredRoutePolicyReason) -> Self {
Self { method, path, reason }
}
pub const fn method(self) -> HttpMethod {
self.method
}
pub const fn path(self) -> &'static str {
self.path
}
pub const fn reason(self) -> DeferredRoutePolicyReason {
self.reason
}
}
pub const ADMIN_ROUTE_POLICY_SPECS: &[AdminRouteSpec] = &[
public(HttpMethod::Get, "/health", PublicRouteKind::Health, RouteRiskLevel::Normal),
public(HttpMethod::Head, "/health", PublicRouteKind::Health, RouteRiskLevel::Normal),
public(HttpMethod::Get, "/health/ready", PublicRouteKind::Health, RouteRiskLevel::Normal),
public(HttpMethod::Head, "/health/ready", PublicRouteKind::Health, RouteRiskLevel::Normal),
public(HttpMethod::Post, "/", PublicRouteKind::StsFormPost, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/profile/cpu", PROFILING, RouteRiskLevel::High),
admin(HttpMethod::Get, "/profile/memory", PROFILING, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/is-admin", ALL_ADMIN, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/rustfs/admin/v3/list-users", LIST_USERS, RouteRiskLevel::Sensitive),
admin(HttpMethod::Delete, "/rustfs/admin/v3/remove-user", DELETE_USER, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/set-user-status", ENABLE_USER, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/groups", LIST_GROUPS, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/rustfs/admin/v3/group", GET_GROUP, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/group/{group}",
REMOVE_USER_FROM_GROUP,
RouteRiskLevel::High,
),
admin(HttpMethod::Put, "/rustfs/admin/v3/set-group-status", ENABLE_GROUP, RouteRiskLevel::High),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/update-group-members",
ADD_USER_TO_GROUP,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/temporary-account-info",
LIST_TEMPORARY_ACCOUNTS,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/add-service-accounts",
CREATE_SERVICE_ACCOUNT,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/add-service-account",
CREATE_SERVICE_ACCOUNT,
RouteRiskLevel::High,
),
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::Get,
"/rustfs/admin/v3/list-canned-policies",
LIST_USER_POLICIES,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/info-canned-policy",
GET_POLICY,
RouteRiskLevel::Sensitive,
),
admin(HttpMethod::Put, "/rustfs/admin/v3/add-canned-policy", CREATE_POLICY, RouteRiskLevel::High),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/remove-canned-policy",
DELETE_POLICY,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/set-user-or-group-policy",
ATTACH_POLICY,
RouteRiskLevel::High,
),
admin(HttpMethod::Put, "/rustfs/admin/v3/set-policy", ATTACH_POLICY, RouteRiskLevel::High),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/idp/builtin/policy/attach",
ATTACH_POLICY,
RouteRiskLevel::High,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/idp/builtin/policy/detach",
ATTACH_POLICY,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/target/list",
GET_BUCKET_TARGET,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/target/{target_type}/{target_name}",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/target/{target_type}/{target_name}/reset",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/target/arns",
GET_BUCKET_TARGET,
RouteRiskLevel::Sensitive,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/info", SERVER_INFO, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/rustfs/admin/v3/storageinfo", STORAGE_INFO, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/pools/decommission",
DECOMMISSION,
RouteRiskLevel::High,
),
admin(HttpMethod::Post, "/rustfs/admin/v3/pools/cancel", DECOMMISSION, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/rebalance/start", REBALANCE, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/rebalance/status", REBALANCE, RouteRiskLevel::Sensitive),
admin(HttpMethod::Post, "/rustfs/admin/v3/rebalance/stop", REBALANCE, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/heal/", HEAL, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/heal/{bucket}", HEAL, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/heal/{bucket}/{prefix}", HEAL, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/background-heal/status", HEAL, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/tier", LIST_TIER, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/rustfs/admin/v3/tier-stats", LIST_TIER, RouteRiskLevel::Sensitive),
admin(HttpMethod::Get, "/rustfs/admin/v3/tier/{tier}", LIST_TIER, RouteRiskLevel::Sensitive),
admin(HttpMethod::Delete, "/rustfs/admin/v3/tier/{tiername}", SET_TIER, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/tier", SET_TIER, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/tier/{tiername}", SET_TIER, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/tier/clear", SET_TIER, RouteRiskLevel::High),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/set-bucket-quota",
SET_BUCKET_QUOTA,
RouteRiskLevel::High,
),
admin(HttpMethod::Put, "/rustfs/admin/v3/quota/{bucket}", SET_BUCKET_QUOTA, RouteRiskLevel::High),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/quota/{bucket}",
SET_BUCKET_QUOTA,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/export-bucket-metadata",
EXPORT_BUCKET_METADATA,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/export-bucket-metadata",
EXPORT_BUCKET_METADATA,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/import-bucket-metadata",
IMPORT_BUCKET_METADATA,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/import-bucket-metadata",
IMPORT_BUCKET_METADATA,
RouteRiskLevel::High,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/get-config-kv", CONFIG_UPDATE, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/set-config-kv", CONFIG_UPDATE, RouteRiskLevel::High),
admin(HttpMethod::Delete, "/rustfs/admin/v3/del-config-kv", CONFIG_UPDATE, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/help-config-kv", CONFIG_UPDATE, RouteRiskLevel::High),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/list-config-history-kv",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/clear-config-history-kv",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/restore-config-history-kv",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/config", CONFIG_UPDATE, RouteRiskLevel::High),
admin(HttpMethod::Put, "/rustfs/admin/v3/config", CONFIG_UPDATE, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/scanner/status", SERVER_INFO, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/audit/target/list",
GET_BUCKET_TARGET,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/audit/target/{target_type}/{target_name}",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/audit/target/{target_type}/{target_name}/reset",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/module-switches",
SERVER_INFO,
RouteRiskLevel::Sensitive,
),
admin(HttpMethod::Put, "/rustfs/admin/v3/module-switches", CONFIG_UPDATE, RouteRiskLevel::High),
admin(
HttpMethod::Get,
"/rustfs/admin/v4/plugins/catalog",
SERVER_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v4/plugins/instances",
GET_BUCKET_TARGET,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v4/plugins/instances/{id}",
GET_BUCKET_TARGET,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v4/plugins/instances/{id}",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v4/plugins/instances/{id}",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/replicationmetrics",
GET_REPLICATION_METRICS,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/set-remote-target",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/remove-remote-target",
SET_BUCKET_TARGET,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/add",
SITE_REPLICATION_ADD,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/remove",
SITE_REPLICATION_REMOVE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/site-replication/info",
SITE_REPLICATION_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/site-replication/metainfo",
SITE_REPLICATION_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/site-replication/status",
SITE_REPLICATION_INFO,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/site-replication/devnull",
SITE_REPLICATION_INFO,
RouteRiskLevel::High,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/site-replication/netperf",
SITE_REPLICATION_INFO,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/join",
SITE_REPLICATION_ADD,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/bucket-ops",
SITE_REPLICATION_OPERATION,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/iam-item",
SITE_REPLICATION_OPERATION,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/bucket-meta",
SITE_REPLICATION_OPERATION,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/site-replication/peer/idp-settings",
SITE_REPLICATION_ADD,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/edit",
SITE_REPLICATION_ADD,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/edit",
SITE_REPLICATION_ADD,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/peer/remove",
SITE_REPLICATION_REMOVE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/resync/op",
SITE_REPLICATION_RESYNC,
RouteRiskLevel::High,
),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/site-replication/state/edit",
SITE_REPLICATION_OPERATION,
RouteRiskLevel::High,
),
admin(HttpMethod::Get, "/rustfs/admin/debug/pprof/profile", PROFILING, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/debug/pprof/status", PROFILING, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/debug/tls/status", PROFILING, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/create-key", KMS_CREATE_KEY, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/key/create", KMS_CREATE_KEY, RouteRiskLevel::High),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/kms/describe-key",
KMS_KEY_STATUS,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/kms/key/status",
KMS_KEY_STATUS,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/kms/list-keys",
KMS_KEY_STATUS,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/kms/generate-data-key",
SERVER_INFO,
RouteRiskLevel::High,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/kms/status", SERVER_INFO, RouteRiskLevel::Sensitive),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/status", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Get, "/rustfs/admin/v3/kms/config", SERVER_INFO, RouteRiskLevel::Sensitive),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/clear-cache", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/configure", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/start", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/stop", SERVER_INFO, RouteRiskLevel::High),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/kms/service-status",
SERVER_INFO,
RouteRiskLevel::Sensitive,
),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/reconfigure", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Post, "/rustfs/admin/v3/kms/keys", KMS_CREATE_KEY, RouteRiskLevel::High),
admin(HttpMethod::Delete, "/rustfs/admin/v3/kms/keys/delete", SERVER_INFO, RouteRiskLevel::High),
admin(
HttpMethod::Post,
"/rustfs/admin/v3/kms/keys/cancel-deletion",
SERVER_INFO,
RouteRiskLevel::High,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/kms/keys", KMS_KEY_STATUS, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Get,
"/rustfs/admin/v3/kms/keys/{key_id}",
KMS_KEY_STATUS,
RouteRiskLevel::Sensitive,
),
public(
HttpMethod::Get,
"/rustfs/admin/v3/oidc/providers",
PublicRouteKind::OidcBootstrap,
RouteRiskLevel::Sensitive,
),
public(
HttpMethod::Get,
"/rustfs/admin/v3/oidc/authorize/{provider_id}",
PublicRouteKind::OidcBootstrap,
RouteRiskLevel::Sensitive,
),
public(
HttpMethod::Get,
"/rustfs/admin/v3/oidc/callback/{provider_id}",
PublicRouteKind::OidcBootstrap,
RouteRiskLevel::Sensitive,
),
public(
HttpMethod::Get,
"/rustfs/admin/v3/oidc/logout",
PublicRouteKind::OidcBootstrap,
RouteRiskLevel::Sensitive,
),
admin(HttpMethod::Get, "/rustfs/admin/v3/oidc/config", SERVER_INFO, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Put,
"/rustfs/admin/v3/oidc/config/{provider_id}",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/rustfs/admin/v3/oidc/config/{provider_id}",
CONFIG_UPDATE,
RouteRiskLevel::High,
),
admin(HttpMethod::Post, "/rustfs/admin/v3/oidc/validate", SERVER_INFO, RouteRiskLevel::High),
admin(HttpMethod::Get, "/iceberg/v1/config", GET_TABLE_CATALOG, RouteRiskLevel::Sensitive),
admin(
HttpMethod::Get,
"/iceberg/v1/{warehouse}/namespaces",
GET_TABLE_NAMESPACE,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Post,
"/iceberg/v1/{warehouse}/namespaces",
SET_TABLE_NAMESPACE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/iceberg/v1/{warehouse}/namespaces/{namespace}",
GET_TABLE_NAMESPACE,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Delete,
"/iceberg/v1/{warehouse}/namespaces/{namespace}",
DELETE_TABLE_NAMESPACE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables",
GET_TABLE,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Post,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables",
CREATE_TABLE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Post,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/register",
REGISTER_TABLE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Get,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
GET_TABLE_METADATA,
RouteRiskLevel::Sensitive,
),
admin(
HttpMethod::Post,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
COMMIT_TABLE,
RouteRiskLevel::High,
),
admin(
HttpMethod::Delete,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
DELETE_TABLE,
RouteRiskLevel::High,
),
];
pub const DEFERRED_ADMIN_ROUTE_POLICIES: &[DeferredAdminRoutePolicy] = &[
deferred(HttpMethod::Get, "/rustfs/admin/v3/accountinfo", DeferredRoutePolicyReason::S3Action),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/user-info",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Put,
"/rustfs/admin/v3/add-user",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Post,
"/rustfs/admin/v3/update-service-account",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/info-service-account",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/info-access-key",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/list-service-accounts",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/list-access-keys-bulk",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(
HttpMethod::Delete,
"/rustfs/admin/v3/delete-service-accounts",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Delete,
"/rustfs/admin/v3/delete-service-account",
DeferredRoutePolicyReason::ContextualAuthorization,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/idp/builtin/policy-entities",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(HttpMethod::Post, "/rustfs/admin/v3/service", DeferredRoutePolicyReason::NotImplemented),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/inspect-data",
DeferredRoutePolicyReason::NotImplemented,
),
deferred(
HttpMethod::Post,
"/rustfs/admin/v3/inspect-data",
DeferredRoutePolicyReason::NotImplemented,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/datausageinfo",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(HttpMethod::Get, "/rustfs/admin/v3/metrics", DeferredRoutePolicyReason::CredentialOnly),
deferred(HttpMethod::Get, "/rustfs/admin/v3/pools/list", DeferredRoutePolicyReason::MultipleActions),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/pools/status",
DeferredRoutePolicyReason::MultipleActions,
),
deferred(HttpMethod::Get, "/rustfs/admin/v3/get-bucket-quota", DeferredRoutePolicyReason::S3Action),
deferred(HttpMethod::Get, "/rustfs/admin/v3/quota/{bucket}", DeferredRoutePolicyReason::S3Action),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/quota-stats/{bucket}",
DeferredRoutePolicyReason::S3Action,
),
deferred(
HttpMethod::Post,
"/rustfs/admin/v3/quota-check/{bucket}",
DeferredRoutePolicyReason::S3Action,
),
deferred(
HttpMethod::Get,
"/rustfs/admin/v3/list-remote-targets",
DeferredRoutePolicyReason::CredentialOnly,
),
];
pub fn validate_admin_route_policy_specs() -> Result<(), AdminRouteMatrixError> {
validate_admin_route_specs(ADMIN_ROUTE_POLICY_SPECS)
}
const fn admin(method: HttpMethod, path: &'static str, action: AdminActionRef, risk_level: RouteRiskLevel) -> AdminRouteSpec {
AdminRouteSpec::admin(method, path, action, risk_level)
}
const fn public(method: HttpMethod, path: &'static str, kind: PublicRouteKind, risk_level: RouteRiskLevel) -> AdminRouteSpec {
AdminRouteSpec::public(method, path, kind, risk_level)
}
const fn deferred(method: HttpMethod, path: &'static str, reason: DeferredRoutePolicyReason) -> DeferredAdminRoutePolicy {
DeferredAdminRoutePolicy::new(method, path, reason)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::admin::router::{AdminOperation, S3Router};
use hyper::Method;
use serial_test::serial;
use std::collections::BTreeSet;
use temp_env::with_var;
#[test]
fn admin_route_policy_specs_validate() {
assert!(validate_admin_route_policy_specs().is_ok());
}
#[test]
fn route_policy_inventory_has_no_internal_overlap() {
let mut seen = BTreeSet::new();
for spec in ADMIN_ROUTE_POLICY_SPECS {
let key = route_key(spec.method(), spec.path());
assert!(seen.insert(key), "duplicate direct route policy for {}", spec.path());
}
for policy in DEFERRED_ADMIN_ROUTE_POLICIES {
let key = route_key(policy.method(), policy.path());
assert!(seen.insert(key), "deferred route overlaps direct policy for {}", policy.path());
}
}
#[test]
#[serial]
fn route_policy_inventory_covers_registered_routes() {
let router = with_var(rustfs_config::ENV_HEALTH_ENDPOINT_ENABLE, Some("true"), || {
let mut router = S3Router::<AdminOperation>::new(false);
super::super::register_admin_routes(&mut router).expect("register production admin routes");
router
});
let registered = router.registered_routes().iter().cloned().collect::<BTreeSet<_>>();
let inventory = route_policy_inventory_keys();
assert_eq!(inventory, registered, "admin route policy inventory must cover every registered route");
}
#[test]
fn route_policy_keeps_public_exceptions_explicit() {
assert_public(HttpMethod::Get, "/health", PublicRouteKind::Health);
assert_public(HttpMethod::Head, "/health/ready", PublicRouteKind::Health);
assert_public(HttpMethod::Post, "/", PublicRouteKind::StsFormPost);
assert_public(
HttpMethod::Get,
"/rustfs/admin/v3/oidc/authorize/{provider_id}",
PublicRouteKind::OidcBootstrap,
);
}
#[test]
fn route_policy_keeps_table_catalog_outside_admin_prefix() {
let table_specs = ADMIN_ROUTE_POLICY_SPECS
.iter()
.filter(|spec| spec.path().starts_with("/iceberg/v1"));
assert_eq!(table_specs.count(), 11);
assert_action(HttpMethod::Get, "/iceberg/v1/{warehouse}/namespaces", GET_TABLE_NAMESPACE);
assert_action(HttpMethod::Post, "/iceberg/v1/{warehouse}/namespaces/{namespace}/tables", CREATE_TABLE);
assert_action(
HttpMethod::Get,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
GET_TABLE_METADATA,
);
assert_action(
HttpMethod::Post,
"/iceberg/v1/{warehouse}/namespaces/{namespace}/tables/{table}",
COMMIT_TABLE,
);
}
#[test]
fn route_policy_keeps_contextual_auth_deferred() {
assert_deferred(
HttpMethod::Get,
"/rustfs/admin/v3/info-service-account",
DeferredRoutePolicyReason::ContextualAuthorization,
);
assert_deferred(
HttpMethod::Get,
"/rustfs/admin/v3/datausageinfo",
DeferredRoutePolicyReason::MultipleActions,
);
assert_deferred(HttpMethod::Get, "/rustfs/admin/v3/accountinfo", DeferredRoutePolicyReason::S3Action);
}
fn route_policy_inventory_keys() -> BTreeSet<String> {
ADMIN_ROUTE_POLICY_SPECS
.iter()
.map(|spec| route_key(spec.method(), spec.path()))
.chain(
DEFERRED_ADMIN_ROUTE_POLICIES
.iter()
.map(|policy| route_key(policy.method(), policy.path())),
)
.collect()
}
fn route_key(method: HttpMethod, path: &str) -> String {
format!("{}|{}", method.as_str(), path)
}
fn assert_action(method: HttpMethod, path: &str, action: AdminActionRef) {
let spec = ADMIN_ROUTE_POLICY_SPECS
.iter()
.find(|spec| spec.method() == method && spec.path() == path)
.expect("expected direct route policy");
assert_eq!(spec.access().admin_action(), Some(action));
}
fn assert_public(method: HttpMethod, path: &str, kind: PublicRouteKind) {
let spec = ADMIN_ROUTE_POLICY_SPECS
.iter()
.find(|spec| spec.method() == method && spec.path() == path)
.expect("expected public route policy");
assert_eq!(spec.access().public_kind(), Some(kind));
}
fn assert_deferred(method: HttpMethod, path: &str, reason: DeferredRoutePolicyReason) {
let policy = DEFERRED_ADMIN_ROUTE_POLICIES
.iter()
.find(|policy| policy.method() == method && policy.path() == path)
.expect("expected deferred route policy");
assert_eq!(policy.reason(), reason);
}
#[test]
fn method_mapping_matches_registered_route_format() {
assert_eq!(route_key(HttpMethod::Get, "/health"), Method::GET.as_str().to_string() + "|/health");
}
}