feat(admin): expose KMS backup and restore behind explicit guards (#5579)

* feat(kms): add backup and restore admin API

Wires the merged KMS backup contract, Local export and Local restore into
the admin API: export a sealed bundle, run a zero-write restore preflight,
execute a confirmed restore, roll an interrupted restore back, and report
subsystem readiness.

- Dedicated kms:Backup / kms:Restore actions, recorded in the admin route
  matrix. Neither is reachable through any other KMS action.
- Restore requires two independent confirmations: an echo of the bundle
  manifest's backup id, and an explicitly named conflict policy (the
  default never writes).
- The backup KEK comes from the environment and is refused when it reuses
  a secret of the configured backend, compared both as the literal value
  and as raw key bytes.
- No endpoint accepts a path: bundles are addressed by a validated name
  under a configured root, and the restore target is always the server's
  own configured key directory.
- Bundles now carry a sanitized configuration artifact built as an
  allowlist projection, so a future backend credential field cannot leak
  into a bundle by default. Restore verifies it and never applies it.
- Audit entries go through the existing KMS admin wiring and carry
  identifiers only.

* test(kms): pin the backup admin API gates

Fixes the test KEK to a real 32-byte value and drives the export refusal
from the configured backend rather than from the handle that happens to
be available, so a Local handle cannot export on behalf of a backend
whose material RustFS does not own.
This commit is contained in:
Zhengchao An
2026-08-01 22:31:25 +08:00
committed by GitHub
parent 02aa383598
commit f2d09d1426
14 changed files with 1847 additions and 1 deletions
+4
View File
@@ -1765,6 +1765,10 @@ impl KmsBackend for LocalKmsBackend {
self.client.health_check().await.map(|_| true)
}
fn local_backup_client(&self) -> Option<&LocalKmsClient> {
Some(&self.client)
}
fn capabilities(&self) -> BackendCapabilities {
// Rotation stays unadvertised until historical key versions can be
// retained (see LocalKmsClient::rotate_key); without version history
+13
View File
@@ -241,6 +241,19 @@ pub trait KmsBackend: Send + Sync {
async fn remove_expired_key(&self, _key_id: &str, _now: &Zoned) -> Result<ExpiredKeyRemoval> {
Err(KmsError::unsupported_capability("backend without deletion support", "remove_expired_key"))
}
/// The running client to export a full-material backup bundle from.
///
/// Only the Local backend owns key material RustFS is allowed to export in
/// full (see [`crate::backup::BackupResponsibility`]); every other backend
/// keeps its cryptographic root outside RustFS and returns `None` here.
///
/// The export must run against the *running* client so that its fence
/// actually blocks concurrent create/delete work — a second client opened
/// on the same key directory would fence nothing.
fn local_backup_client(&self) -> Option<&local::LocalKmsClient> {
None
}
}
/// Outcome of [`KmsBackend::remove_expired_key`].