mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
fix(kms): honour the configured metadata cache TTL and metrics switch (#5569)
* fix(kms): honour the configured metadata cache TTL and metrics switch KmsManager::new built the KmsCache from cache_config.max_keys alone, so cache_config.ttl was dead configuration: every deployment ran the hardcoded 300s window whatever the admin configure API was given, while CacheSummary and the KMS config endpoint reported the configured value back. cache_config.enable_metrics was never read anywhere. Build the cache from the whole CacheConfig. The documented default is reconciled down to the 300s the cache has always used rather than up to the advertised 3600s, and now lives in one place (DEFAULT_CACHE_TTL) instead of being duplicated across the four configure-request converters, so the default path behaves exactly as before. Behaviour change: a deployment configured through the admin API already has ttl 3600 persisted, because the old converters wrote that default into the stored config, so its describe_key staleness window widens from an effective 300s to the 3600s it asked for. No cryptographic or authorization path widens - encrypt, decrypt and generate_data_key go straight to the backend and never read this cache. The Vault Transit backend's own metadata cache, which does gate crypto through ensure_key_state_allows, stays fixed at 300s and is now documented as deliberately not operator-tunable. The configured duration now reaches moka's builder, which panics above 1000 years, so CacheConfig::effective_ttl clamps to a 24h maximum the way effective_timeout already clamps its own, and validate rejects a zero TTL beside the existing max_keys check. Both config summaries and the KMS config endpoint report the effective value, so the admin API cannot advertise a lifetime the cache does not honour. enable_metrics gates publication of the rustfs_kms_metadata_cache_* families only; the counters behind the admin status API keep running either way. No configure-request field sets it yet. Refs rustfs/backlog#1584 * docs(kms): state why the Transit metadata TTL is not bound to the default The comment claimed the constant matches config::DEFAULT_CACHE_TTL, which reads as an invariant the code does not enforce. Say plainly that the equality is a coincidence rather than a contract, and why binding the two would be wrong: this cache gates crypto through ensure_key_state_allows, so a later change to the operator-facing describe-cache default must not be able to widen its staleness window.
This commit is contained in:
@@ -46,7 +46,7 @@ pub struct KmsManager {
|
||||
impl KmsManager {
|
||||
/// Create a new KMS manager with the given backend and config
|
||||
pub fn new(backend: Arc<dyn KmsBackend>, config: KmsConfig) -> Self {
|
||||
let cache = Arc::new(RwLock::new(KmsCache::new(config.cache_config.max_keys as u64)));
|
||||
let cache = Arc::new(RwLock::new(KmsCache::new(&config.cache_config)));
|
||||
if config.allow_immediate_deletion {
|
||||
warn!(
|
||||
"KMS immediate key deletion is enabled: a DeleteKey request may destroy key material without any waiting window, and every object encrypted under that key becomes permanently unreadable"
|
||||
@@ -462,6 +462,7 @@ mod tests {
|
||||
use jiff::Zoned;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Sink that keeps every record so tests can assert on the audit trail.
|
||||
@@ -971,6 +972,45 @@ mod tests {
|
||||
assert!(health);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn configured_cache_ttl_bounds_how_long_metadata_is_reused() {
|
||||
let temp_dir = tempdir().expect("Failed to create temp dir");
|
||||
let mut config = KmsConfig::local(temp_dir.path().to_path_buf()).with_insecure_development_defaults();
|
||||
config.cache_config.ttl = Duration::from_millis(100);
|
||||
|
||||
let backend = Arc::new(LocalKmsBackend::new(config.clone()).await.expect("Failed to create backend"));
|
||||
let manager = KmsManager::new(backend, config);
|
||||
|
||||
let key_id = manager
|
||||
.create_key(CreateKeyRequest {
|
||||
key_name: Some("cache-ttl-wiring".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("Failed to create key")
|
||||
.key_id;
|
||||
|
||||
// Creating the key populated the cache, so this describe is served from it.
|
||||
manager
|
||||
.describe_key(DescribeKeyRequest { key_id: key_id.clone() })
|
||||
.await
|
||||
.expect("describe should succeed");
|
||||
assert_eq!(manager.cache_stats().await.expect("cache is enabled").hits, 1);
|
||||
|
||||
// Past the configured lifetime the entry is gone and the describe falls
|
||||
// through to the backend. A cache built with a hardcoded lifetime would
|
||||
// still be serving the entry here.
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
manager
|
||||
.describe_key(DescribeKeyRequest { key_id })
|
||||
.await
|
||||
.expect("describe should succeed");
|
||||
|
||||
let stats = manager.cache_stats().await.expect("cache is enabled");
|
||||
assert_eq!(stats.hits, 1);
|
||||
assert_eq!(stats.misses, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lifecycle_round_trip_invalidates_cached_metadata() {
|
||||
let temp_dir = tempdir().expect("Failed to create temp dir");
|
||||
|
||||
Reference in New Issue
Block a user