fix(kms): restore persisted configuration after restart (#6821)

* fix(kms): restore persisted configuration after restart

* docs(kms): cover the reload route and startup load states

The admin contract matrix pins every dynamic KMS route for the rc and
console handoff, so the new POST /kms/reload needs a row there, and the
reload response reuses the configure snapshot shape rather than adding a
wire type. The observability runbook gains the operator procedure the
reload exists for: telling a load_failed startup apart from a server
that was never configured, and recovering without resubmitting secrets.
This commit is contained in:
唐小鸭
2026-08-29 16:21:22 +08:00
committed by GitHub
parent 9307d2c8a8
commit 11c6ee42ea
12 changed files with 407 additions and 79 deletions
+52
View File
@@ -259,6 +259,25 @@ impl KmsServiceManager {
(state.status.clone(), config)
}
/// Publish an initialization failure when no usable KMS state exists yet.
///
/// Startup configuration discovery happens outside this crate. Recording
/// its failure here keeps status truthful without allowing a late failure
/// to replace an already configured or running service.
pub async fn record_initialization_error(&self, message: impl Into<String>) {
let _guard = self.lifecycle_mutex.lock().await;
let current = self.state.load_full();
if current.config.is_some() || current.current_service.is_some() {
return;
}
self.state.store(Arc::new(RuntimeState {
config: None,
status: KmsServiceStatus::Error(message.into()),
current_service: None,
}));
}
fn redact_config(config: &mut KmsConfig) {
if let BackendConfig::Static(static_config) = &mut config.backend_config {
use zeroize::Zeroize;
@@ -849,6 +868,39 @@ mod tests {
assert!(manager.get_encryption_service().await.is_none());
}
#[tokio::test]
async fn initialization_error_is_visible_until_configuration_succeeds() {
let manager = KmsServiceManager::new();
manager
.record_initialization_error("persisted configuration could not be loaded")
.await;
assert_eq!(
manager.get_status().await,
KmsServiceStatus::Error("persisted configuration could not be loaded".to_string())
);
assert!(manager.get_config().await.is_none());
manager
.configure(static_config("key-a", 0x11))
.await
.expect("configure after startup failure");
assert_eq!(manager.get_status().await, KmsServiceStatus::Configured);
}
#[tokio::test]
async fn initialization_error_never_replaces_a_running_service() {
let manager = KmsServiceManager::new();
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
manager.start().await.expect("start");
manager.record_initialization_error("late startup failure").await;
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
assert!(manager.get_encryption_service().await.is_some());
}
#[tokio::test]
async fn configure_rejects_running_service_without_changing_snapshot() {
let manager = KmsServiceManager::new();