mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-25 13:36:50 +00:00
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:
@@ -74,6 +74,7 @@ pub const LOCAL_BUNDLE_MANIFEST_FILE: &str = "manifest.json";
|
||||
const ARTIFACTS_DIR: &str = "artifacts";
|
||||
const KEYS_DIR: &str = "artifacts/keys";
|
||||
const SALT_ARTIFACT_PATH: &str = "artifacts/master-key.salt.enc";
|
||||
const CONFIG_ARTIFACT_PATH: &str = "artifacts/kms-config.json.enc";
|
||||
pub(crate) const AEAD_NONCE_LEN: usize = 12;
|
||||
/// Domain-separation context for the artifact AAD binding.
|
||||
const BUNDLE_AAD_CONTEXT: &str = "rustfs-kms-local-backup:v1";
|
||||
@@ -141,6 +142,15 @@ pub struct LocalBackupExportRequest {
|
||||
pub snapshot_generation: u64,
|
||||
/// Bundle output directory; must not exist yet or must be empty.
|
||||
pub destination: PathBuf,
|
||||
/// Serialized sanitized KMS configuration to seal into the bundle, if the
|
||||
/// caller produced one.
|
||||
///
|
||||
/// The persisted `KmsConfig` carries plaintext credentials (Vault token,
|
||||
/// AppRole secret id, Local master key), so the sanitized projection is
|
||||
/// owned by the admin layer and this module only seals the bytes it is
|
||||
/// handed. The artifact is evidence for an operator decision: restore
|
||||
/// verifies it opens but never applies a configuration.
|
||||
pub sanitized_config: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl LocalBackupExportRequest {
|
||||
@@ -410,6 +420,10 @@ async fn build_and_write_bundle(
|
||||
let descriptor = encrypt_and_write_artifact(kek, request, ArtifactKind::MasterKeySalt, SALT_ARTIFACT_PATH, salt).await?;
|
||||
artifacts.push(descriptor);
|
||||
}
|
||||
if let Some(config) = &request.sanitized_config {
|
||||
let descriptor = encrypt_and_write_artifact(kek, request, ArtifactKind::KmsConfig, CONFIG_ARTIFACT_PATH, config).await?;
|
||||
artifacts.push(descriptor);
|
||||
}
|
||||
|
||||
// Make the artifact directory entries durable before sealing: the sealed
|
||||
// manifest must never survive a crash that its artifacts did not.
|
||||
@@ -673,6 +687,7 @@ mod tests {
|
||||
rustfs_version: "1.0.0-test".to_string(),
|
||||
snapshot_generation: 7,
|
||||
destination,
|
||||
sanitized_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user