mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-07 05:43:14 +00:00
test(admin): pin the KMS admin route contract as a snapshot (#5766)
Adding a KMS route already fails `route_registration_test`, and `route_policy` pins each route's action and risk with individual assertions, but nothing records the surface as a whole. A change to an existing route's action or risk therefore lands as an edited assertion rather than as a visible before/after — and these are the routes where that matters, since the action decides who may reach key material and the risk level decides which confirmations the route demands. Every field is derived from ADMIN_ROUTE_POLICY_SPECS, so the snapshot cannot drift from the routing table; there is nothing to keep in sync by hand. A second test asserts the snapshot actually covers the surface it claims to: every listed route must be under /kms/ and must gate on a dedicated kms:* action, so a KMS route registered outside the policy table or falling back to a generic admin action cannot leave the snapshot green. Per-key authorization scoping is deliberately not restated here. It is enforced and tested where it is implemented, by single_key_endpoints_reject_a_key_outside_the_policy_scope in handlers::kms_keys; a second hand-maintained list would be a claim nothing checks.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// 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.
|
||||
|
||||
//! Reviewable snapshot of the KMS admin route surface.
|
||||
//!
|
||||
//! `route_registration_test` already fails when a route appears or disappears,
|
||||
//! and `route_policy` pins each route's action and risk with individual
|
||||
//! assertions. Neither records the surface as a whole, so a change to an
|
||||
//! existing route's action or risk lands as an edited assertion rather than as
|
||||
//! a visible before/after — and the KMS routes are exactly where that matters,
|
||||
//! since the action decides who may reach key material and the risk level
|
||||
//! decides which confirmations the route demands.
|
||||
//!
|
||||
//! Every field here is derived from [`ADMIN_ROUTE_POLICY_SPECS`], so this file
|
||||
//! cannot drift from the routing table: there is nothing to keep in sync by
|
||||
//! hand. Per-key authorization scoping is deliberately *not* restated here —
|
||||
//! it is enforced and tested where it is implemented, by
|
||||
//! `single_key_endpoints_reject_a_key_outside_the_policy_scope` in
|
||||
//! `handlers::kms_keys`, and a second hand-maintained list would be a claim
|
||||
//! nothing checks.
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::admin::route_policy::ADMIN_ROUTE_POLICY_SPECS;
|
||||
|
||||
/// Path fragment identifying a KMS admin route.
|
||||
const KMS_PATH_MARKER: &str = "/kms/";
|
||||
|
||||
/// One KMS admin route as the authorization layer sees it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||
struct KmsRouteContract {
|
||||
method: String,
|
||||
path: String,
|
||||
/// The action a caller's policy must allow, or `null` for a public route.
|
||||
action: Option<String>,
|
||||
risk: String,
|
||||
}
|
||||
|
||||
/// Every KMS admin route, in a stable order.
|
||||
fn kms_route_contract() -> Vec<KmsRouteContract> {
|
||||
let mut routes: Vec<KmsRouteContract> = ADMIN_ROUTE_POLICY_SPECS
|
||||
.iter()
|
||||
.filter(|spec| spec.path().contains(KMS_PATH_MARKER))
|
||||
.map(|spec| KmsRouteContract {
|
||||
method: format!("{:?}", spec.method()).to_uppercase(),
|
||||
path: spec.path().to_string(),
|
||||
action: spec.access().admin_action().map(|action| action.as_str().to_string()),
|
||||
risk: format!("{:?}", spec.risk_level()),
|
||||
})
|
||||
.collect();
|
||||
routes.sort();
|
||||
routes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{KMS_PATH_MARKER, kms_route_contract};
|
||||
|
||||
/// The reviewable record. A route whose action or risk changes shows up as
|
||||
/// a snapshot diff instead of as an edited assertion buried in
|
||||
/// `route_policy`.
|
||||
#[test]
|
||||
fn kms_admin_route_contract_is_stable() {
|
||||
insta::assert_json_snapshot!("kms_admin_route_contract", kms_route_contract());
|
||||
}
|
||||
|
||||
/// The snapshot is only evidence if it actually covers the surface. A KMS
|
||||
/// route registered outside the policy table, or a path spelling the filter
|
||||
/// does not match, would otherwise leave the snapshot green and the route
|
||||
/// unrecorded.
|
||||
#[test]
|
||||
fn every_kms_route_is_covered_and_authorized() {
|
||||
let routes = kms_route_contract();
|
||||
assert!(routes.len() > 20, "expected the full KMS surface, got {}", routes.len());
|
||||
|
||||
for route in &routes {
|
||||
assert!(route.path.contains(KMS_PATH_MARKER), "unexpected path in the KMS contract: {route:?}");
|
||||
let action = route
|
||||
.action
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| panic!("no KMS route may be public: {route:?}"));
|
||||
assert!(
|
||||
action.starts_with("kms:"),
|
||||
"a KMS route must gate on a dedicated kms:* action, not on {action}: {route:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ pub mod utils;
|
||||
#[cfg(test)]
|
||||
mod console_test;
|
||||
#[cfg(test)]
|
||||
mod kms_contract;
|
||||
#[cfg(test)]
|
||||
mod route_registration_test;
|
||||
|
||||
use handlers::{
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
---
|
||||
source: rustfs/src/admin/kms_contract.rs
|
||||
expression: kms_route_contract()
|
||||
---
|
||||
[
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/rustfs/admin/v3/kms/keys/delete",
|
||||
"action": "kms:DeleteKey",
|
||||
"risk": "Critical"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/backup",
|
||||
"action": "kms:Backup",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/config",
|
||||
"action": "kms:Configure",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/describe-key",
|
||||
"action": "kms:DescribeKey",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/key/status",
|
||||
"action": "kms:DescribeKey",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/keys",
|
||||
"action": "kms:ListKeys",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/keys/{key_id}",
|
||||
"action": "kms:DescribeKey",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/list-keys",
|
||||
"action": "kms:ListKeys",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/service-status",
|
||||
"action": "kms:ServiceControl",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/rustfs/admin/v3/kms/status",
|
||||
"action": "kms:ServiceControl",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/backup",
|
||||
"action": "kms:Backup",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/clear-cache",
|
||||
"action": "kms:ClearCache",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/configure",
|
||||
"action": "kms:Configure",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/create-key",
|
||||
"action": "kms:Configure",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/generate-data-key",
|
||||
"action": "kms:GenerateDataKey",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/key/create",
|
||||
"action": "kms:Configure",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys",
|
||||
"action": "kms:Configure",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/cancel-deletion",
|
||||
"action": "kms:DeleteKey",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/disable",
|
||||
"action": "kms:DisableKey",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/enable",
|
||||
"action": "kms:EnableKey",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/rotate",
|
||||
"action": "kms:RotateKey",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/tag",
|
||||
"action": "kms:TagResource",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/untag",
|
||||
"action": "kms:UntagResource",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/keys/update-description",
|
||||
"action": "kms:UpdateKeyDescription",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/reconfigure",
|
||||
"action": "kms:Configure",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/restore",
|
||||
"action": "kms:Restore",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/restore/abort",
|
||||
"action": "kms:Restore",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/restore/dry-run",
|
||||
"action": "kms:Restore",
|
||||
"risk": "Sensitive"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/start",
|
||||
"action": "kms:ServiceControl",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/status",
|
||||
"action": "kms:ServiceControl",
|
||||
"risk": "High"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/rustfs/admin/v3/kms/stop",
|
||||
"action": "kms:ServiceControl",
|
||||
"risk": "High"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user