fix(kms): persist and report the Vault KV2 key rotation timestamp (#5594)

* fix(kms): persist and report the Vault KV2 key rotation timestamp

The rotation-age gauge reads KeyInfo::rotated_at and falls back to
created_at when it is absent. The KV2 backend never persisted a rotation
time and hardcoded None in describe_key, so a key rotated many times and
a key that was never rotated reported the same age.

Record the rotation time on the same check-and-set write that switches
the current version, and report the stored value from describe_key and
from the recovered-create path. Records written before the field existed
keep deserializing and stay unstamped: no timestamp is invented for a
rotation this node cannot vouch for.

* test(kms): cover Vault KV2 rotation timestamp persistence and legacy records
This commit is contained in:
Zhengchao An
2026-08-02 05:31:51 +08:00
committed by GitHub
parent da6fc5314d
commit a29ae4e5cd
3 changed files with 178 additions and 4 deletions
+11
View File
@@ -107,6 +107,17 @@ impl KeyCensus {
KeyStatus::PendingDeletion => self.pending_deletion += 1,
KeyStatus::Deleted => self.tombstones += 1,
KeyStatus::Active | KeyStatus::Disabled => {
// A missing rotation time means either "never rotated" or "the
// build that rotated it did not record when". Both fall back to
// creation, and the two are not worth separate series: for the
// first, creation *is* the correct baseline; for the second it
// is an upper bound on the true age, so the gauge can only
// overstate how overdue a key is, never hide an overdue one.
// The overstatement is self-healing — the next rotation stamps
// the record — and erring loud is the right direction for a
// rotation-readiness alert. Backends never fabricate a
// timestamp to close the gap, so nothing here can turn an
// unstamped key into a freshly rotated one.
let rotated_at = key.rotated_at.as_ref().unwrap_or(&key.created_at);
self.oldest_rotation_age_seconds = self.oldest_rotation_age_seconds.max(seconds_between(rotated_at, now));
}