mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-07 05:43:14 +00:00
fix(admin): retire the query-string form of immediate KMS key deletion
Immediate deletion destroys master key material outright, and every object encrypted under that key becomes permanently unreadable. The delete endpoint accepted that request as a query parameter, which is the form most easily issued by accident and the one that made the waiting window bypassable. The query string can now only schedule a deletion: `force_immediate` with any value other than `false`, or a `confirm_key_id` parameter, is refused with 400 rather than downgraded to a scheduled deletion, so a caller cannot read the answer as "destroyed". The JSON body form is unchanged and remains the single way to reach the service gate that enforces the server opt-in and the echoed confirmation. Classify the route accordingly: `RouteRiskLevel` gains `Critical` for routes whose worst case is permanent loss of user data, and the KMS key deletion route is the only member, pinned in both directions by a matrix test. Endpoint-level coverage for the 7-30 day window bound is added for every configured backend. Refs rustfs/backlog#1585 (part of rustfs/backlog#1562)
This commit is contained in:
@@ -126,6 +126,51 @@ async fn assert_key_deletion_lifecycle(base_url: &str, access_key: &str, secret_
|
||||
assert_eq!(cancelled["success"], true);
|
||||
assert_eq!(cancelled["key_metadata"]["key_state"], "Enabled");
|
||||
|
||||
// A window outside 7-30 days is refused at the endpoint, whatever the
|
||||
// backend: the bound is enforced once in the service, so no backend can
|
||||
// stretch or skip it (rustfs/backlog#1585).
|
||||
for days in [6, 31] {
|
||||
let refused = kms_admin_request(
|
||||
base_url,
|
||||
http::Method::DELETE,
|
||||
"/rustfs/admin/v3/kms/keys/delete",
|
||||
Some(
|
||||
&serde_json::json!({
|
||||
"key_id": key_id,
|
||||
"pending_window_in_days": days
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
access_key,
|
||||
secret_key,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.ok_or_else(|| format!("a {days}-day deletion window must be refused"))?;
|
||||
assert!(
|
||||
refused.to_string().contains("400 Bad Request"),
|
||||
"a {days}-day deletion window must report a client error: {refused}"
|
||||
);
|
||||
}
|
||||
|
||||
// Immediate deletion is no longer reachable through the query string, so it
|
||||
// fails before the service gate is even consulted.
|
||||
let refused = kms_admin_request(
|
||||
base_url,
|
||||
http::Method::DELETE,
|
||||
&format!("/rustfs/admin/v3/kms/keys/delete?keyId={key_id}&force_immediate=true"),
|
||||
None,
|
||||
access_key,
|
||||
secret_key,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.ok_or("immediate KMS key deletion must not be reachable through the query string")?;
|
||||
assert!(
|
||||
refused.to_string().contains("400 Bad Request"),
|
||||
"a query-string immediate deletion must report a client error: {refused}"
|
||||
);
|
||||
|
||||
// A default server refuses to skip the waiting window (rustfs/backlog#1585):
|
||||
// immediate deletion is unrecoverable and takes every object encrypted under
|
||||
// the key with it, so the endpoint must reject it rather than honour it.
|
||||
@@ -151,7 +196,7 @@ async fn assert_key_deletion_lifecycle(base_url: &str, access_key: &str, secret_
|
||||
"refused immediate deletion must report a client error: {refused}"
|
||||
);
|
||||
|
||||
// The refused request left the key alone, so the window-bounded path still
|
||||
// The refused requests left the key alone, so the window-bounded path still
|
||||
// has something to schedule.
|
||||
let described = kms_admin_request(
|
||||
base_url,
|
||||
|
||||
@@ -417,6 +417,22 @@ async fn test_vault_kms_key_crud(
|
||||
|
||||
info!("✅ Read: Successfully listed keys, found test key");
|
||||
|
||||
// A waiting window outside 7-30 days is refused at the endpoint for this
|
||||
// backend too: the bound is enforced once in the service (rustfs/backlog#1585).
|
||||
for days in [6, 31] {
|
||||
let window_error = crate::common::execute_awscurl(
|
||||
&format!("{base_url}/rustfs/admin/v3/kms/keys/delete?keyId={key_id}&pending_window_in_days={days}"),
|
||||
"DELETE",
|
||||
None,
|
||||
access_key,
|
||||
secret_key,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.ok_or_else(|| format!("A {days}-day deletion window must be refused"))?;
|
||||
info!("✅ Delete window {} correctly refused: {}", days, window_error);
|
||||
}
|
||||
|
||||
// Delete
|
||||
let delete_response = crate::common::execute_awscurl(
|
||||
&format!("{base_url}/rustfs/admin/v3/kms/keys/delete?keyId={key_id}"),
|
||||
@@ -449,9 +465,10 @@ async fn test_vault_kms_key_crud(
|
||||
|
||||
info!("✅ Delete verification: Key state correctly changed to: {}", key_state);
|
||||
|
||||
// Force Delete - a default server refuses to skip the waiting window
|
||||
// (rustfs/backlog#1585): destroying the key material immediately would take
|
||||
// every object encrypted under the key with it.
|
||||
// Force Delete - the query string can no longer ask for immediate deletion,
|
||||
// and a default server refuses it in any case (rustfs/backlog#1585):
|
||||
// destroying the key material immediately would take every object encrypted
|
||||
// under the key with it.
|
||||
let force_delete_error = crate::common::execute_awscurl(
|
||||
&format!("{base_url}/rustfs/admin/v3/kms/keys/delete?keyId={key_id}&force_immediate=true"),
|
||||
"DELETE",
|
||||
|
||||
@@ -63,6 +63,14 @@ pub enum RouteRiskLevel {
|
||||
Normal,
|
||||
Sensitive,
|
||||
High,
|
||||
/// A single authorized request can destroy stored data beyond every
|
||||
/// recovery path the server offers — no undo, no waiting window, no
|
||||
/// backup taken on the caller's behalf.
|
||||
///
|
||||
/// This is deliberately narrower than [`Self::High`], which covers routes
|
||||
/// that change state an operator can put back. Reserve it for routes whose
|
||||
/// worst case is permanent loss of user data.
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
||||
Reference in New Issue
Block a user