feat(kms): surface non-production backend positioning at runtime (#6633)

This commit is contained in:
唐小鸭
2026-08-26 13:25:49 +08:00
committed by GitHub
parent b49c9a07d1
commit b1b3655bf8
15 changed files with 91 additions and 11 deletions
+1
View File
@@ -821,6 +821,7 @@ impl KmsBackend for AwsKmsBackend {
.with_schedule_deletion(true)
.with_versioning(true)
.with_physical_delete(false)
.with_production_supported(true)
}
/// Observe, never destroy.
+4 -1
View File
@@ -2279,7 +2279,10 @@ impl KmsBackend for LocalKmsBackend {
fn capabilities(&self) -> BackendCapabilities {
// Rotation stays unadvertised until historical key versions can be
// retained (see LocalKmsClient::rotate_key); without version history
// there is also no versioning capability.
// there is also no versioning capability. `production_supported` stays
// false by positioning decision: the Local backend keeps its
// cryptographic root on the host filesystem and exists for
// development, testing and demos only.
BackendCapabilities::minimal()
.with_enable_disable(true)
.with_schedule_deletion(true)
+39
View File
@@ -615,6 +615,14 @@ pub struct BackendCapabilities {
pub update_key_metadata: bool,
/// Re-wrapping an existing data key envelope onto the key's current version
pub rewrap: bool,
/// Whether this backend is positioned for production use.
///
/// Backends that keep their cryptographic root on the local host (Local,
/// Static) are for development, testing and demos only; this flag is how
/// that positioning reaches logs, the status API and the console without
/// each consumer matching on backend names.
#[serde(default)]
pub production_supported: bool,
}
impl BackendCapabilities {
@@ -633,6 +641,7 @@ impl BackendCapabilities {
physical_delete: false,
update_key_metadata: false,
rewrap: false,
production_supported: false,
}
}
@@ -695,6 +704,12 @@ impl BackendCapabilities {
self.rewrap = rewrap;
self
}
/// Set whether the backend is positioned for production use
pub const fn with_production_supported(mut self, production_supported: bool) -> Self {
self.production_supported = production_supported;
self
}
}
impl Default for BackendCapabilities {
@@ -775,6 +790,30 @@ mod tests {
assert!(!capabilities.physical_delete);
assert!(!capabilities.update_key_metadata);
assert!(!capabilities.rewrap);
// Production positioning is an explicit claim, never inherited.
assert!(!capabilities.production_supported);
}
/// Older peers and consoles serialize capabilities without the
/// positioning flag; deserializing their payloads must not fail and must
/// default to the conservative claim.
#[test]
fn capabilities_without_positioning_field_deserialize_as_non_production() {
let legacy = serde_json::json!({
"encrypt": true,
"decrypt": true,
"generate_data_key": true,
"rotate": true,
"enable_disable": true,
"schedule_deletion": true,
"versioning": true,
"physical_delete": true,
"update_key_metadata": true,
"rewrap": true,
});
let capabilities: BackendCapabilities =
serde_json::from_value(legacy).expect("legacy capability payloads must stay deserializable");
assert!(!capabilities.production_supported);
}
#[tokio::test]
@@ -8,6 +8,7 @@ expression: capabilities_snapshot(backend.capabilities())
"encrypt": true,
"generate_data_key": true,
"physical_delete": false,
"production_supported": true,
"rewrap": false,
"rotate": true,
"schedule_deletion": true,
@@ -8,6 +8,7 @@ expression: capabilities_snapshot(backend.capabilities())
"encrypt": true,
"generate_data_key": true,
"physical_delete": true,
"production_supported": false,
"rewrap": false,
"rotate": false,
"schedule_deletion": true,
@@ -8,6 +8,7 @@ expression: capabilities_snapshot(backend.capabilities())
"encrypt": true,
"generate_data_key": true,
"physical_delete": false,
"production_supported": false,
"rewrap": false,
"rotate": false,
"schedule_deletion": false,
@@ -8,6 +8,7 @@ expression: capabilities_snapshot(backend.capabilities())
"encrypt": true,
"generate_data_key": true,
"physical_delete": true,
"production_supported": true,
"rewrap": true,
"rotate": true,
"schedule_deletion": true,
@@ -8,6 +8,7 @@ expression: capabilities_snapshot(backend.capabilities())
"encrypt": true,
"generate_data_key": true,
"physical_delete": true,
"production_supported": true,
"rewrap": true,
"rotate": true,
"schedule_deletion": true,
+3 -1
View File
@@ -413,7 +413,9 @@ impl KmsBackend for StaticKmsBackend {
fn capabilities(&self) -> BackendCapabilities {
// Static KMS is a read-only single-key backend: it only performs
// cryptographic operations and rejects every lifecycle mutation.
// cryptographic operations and rejects every lifecycle mutation. Its
// key material comes straight from configuration, so like Local it is
// a development/testing backend and never production_supported.
BackendCapabilities::minimal()
}
}
+1
View File
@@ -2257,6 +2257,7 @@ impl KmsBackend for VaultKmsBackend {
.with_physical_delete(true)
.with_update_key_metadata(true)
.with_rewrap(true)
.with_production_supported(true)
}
async fn remove_expired_key(&self, key_id: &str, now: &Zoned) -> Result<ExpiredKeyRemoval> {
+1
View File
@@ -1767,6 +1767,7 @@ impl KmsBackend for VaultTransitKmsBackend {
.with_physical_delete(true)
.with_update_key_metadata(true)
.with_rewrap(true)
.with_production_supported(true)
}
async fn remove_expired_key(&self, key_id: &str, now: &Zoned) -> Result<ExpiredKeyRemoval> {
+19 -5
View File
@@ -941,8 +941,15 @@ impl KmsConfig {
&& let Some(ref tls) = config.tls
&& !tls.skip_verify
{
// In production, we should have proper TLS configuration
if tls.ca_cert_path.is_none() && tls.client_cert_path.is_none() {
if tls.ca_cert_path.is_some() || tls.client_cert_path.is_some() || tls.client_key_path.is_some() {
// No configuration surface sets these paths today and the
// Vault client does not consume them; warn loudly instead
// of implying the certificates take effect.
tracing::warn!(
"Vault TLS certificate paths are configured but not applied to the Vault client; \
the connection still relies on the system CA store without a client identity"
);
} else {
tracing::warn!("Using HTTPS without custom TLS configuration - relying on system CA");
}
}
@@ -978,10 +985,17 @@ impl KmsConfig {
if config.address.starts_with("https://")
&& let Some(ref tls) = config.tls
&& !tls.skip_verify
&& tls.ca_cert_path.is_none()
&& tls.client_cert_path.is_none()
{
tracing::warn!("Using HTTPS without custom TLS configuration - relying on system CA");
if tls.ca_cert_path.is_some() || tls.client_cert_path.is_some() || tls.client_key_path.is_some() {
// Same as the KV2 branch: these paths are dead
// configuration until the client consumes them.
tracing::warn!(
"Vault TLS certificate paths are configured but not applied to the Vault client; \
the connection still relies on the system CA store without a client identity"
);
} else {
tracing::warn!("Using HTTPS without custom TLS configuration - relying on system CA");
}
}
}
BackendConfig::Static(config) => {
+14
View File
@@ -633,6 +633,20 @@ impl KmsServiceManager {
}
};
// Every path that can activate a backend (startup, persisted-config
// replay, dynamic configure, peer reload) converges here, so this is
// the one place a non-production backend is guaranteed to announce
// itself each time it starts serving.
if !backend.capabilities().production_supported {
warn!(
event = "kms_backend_positioning",
backend = config.backend.as_str(),
version,
"KMS backend is intended for development, testing and demos only; \
use Vault Transit, Vault KV2 or AWS KMS for production deployments"
);
}
// Create KMS manager
//
// The deletion reference checker is handed to the manager as well as to