Auto-Rotation Policy¶
Cosmian KMS supports scheduled, policy-driven key rotation for SQL-backed symmetric keys and asymmetric key pairs. A per-key rotation policy is attached to a key object; a background scheduler then rotates any key whose interval has elapsed — without any operator action.
HSM keys support manual rotation only; the auto-rotation scheduler never picks up HSM UIDs. See HSM Key Rotation.
For the rotation flows and sequence diagrams for each key type, see Key Rotation.
Rotation policy attributes¶
All rotation-policy state is stored as vendor-extension KMIP attributes on the key object itself:
| Attribute | Type | Mutable | Description |
|---|---|---|---|
x-rotate-interval |
i64 (seconds) |
✅ | How often to rotate. 0 disables auto-rotation. |
x-rotate-name |
string |
✅ | Keyset name this key belongs to (see Key Rotation — Keysets). |
x-rotate-offset |
i64 (seconds) |
✅ | Shift the first rotation trigger by this many seconds after Initial Date. |
x-rotate-generation |
i32 |
❌ | Incremented on every rotation; 0 for never-rotated keys. Server-managed. |
x-rotate-date |
datetime |
❌ | Timestamp of the last rotation. Server-managed. |
x-rotate-latest |
bool |
❌ | true on the most-recent keyset member; false on all older keys. Server-managed. |
Read-only attributes¶
x-rotate-generation, x-rotate-date, and x-rotate-latest are set
exclusively by the server during the Re-Key operation. Any attempt to
modify them via AddAttribute, SetAttribute, ModifyAttribute, or
DeleteAttribute is rejected with Attribute_Read_Only.
These restrictions maintain two invariants relied on by the scheduler and the keyset resolution logic:
- Monotonic generation counter —
x-rotate-generationstarts at0and increments by exactly1per rotation. Within a keyset the generation is unique and strictly increasing, which lets the scheduler and client tooling identify the current key without inspecting every member. - Authoritative rotation timestamp —
x-rotate-dateis the only reliable source for “when was this key last rotated”. The scheduler computes the next trigger asx-rotate-date + x-rotate-interval(previously rotated) orinitial_date + x-rotate-offset + x-rotate-interval(never rotated). External modifications tox-rotate-datewould cause missed or premature rotations.
Assigning a rotation policy¶
Use ckms sym keys set-rotation-policy (or SetAttribute for key pairs) to
configure the mutable attributes on an existing key:
# Rotate every hour; first rotation is 60 seconds after Initial Date
ckms sym keys set-rotation-policy \
--key-id my-keyset \
--interval 3600 \
--offset 60 \
--name my-keyset
SetAttribute initialises x-rotate-generation = 0 and
x-rotate-latest = true on the key. Every subsequent Re-Key increments
the generation, marks the new key as latest, and marks the old key as
non-latest.
For asymmetric key pairs, use SetAttribute directly:
Server configuration¶
Enable the scheduler¶
The background scheduler is disabled by default. Enable it in kms.toml
or via the command-line flag:
Set the interval to a value smaller than the shortest x-rotate-interval
on any key — otherwise some rotations will be delayed by up to one scheduler
period.
HSM backend + KEK¶
The scheduler works with any database backend. If the server is started with
a --key-encryption-key, KEK-wrapped keys are rotated the same as plain keys
(the server unwraps in memory, generates fresh material, re-wraps):
cosmian_kms \
--database-type sqlite \
--hsm-model softhsm2 \
--hsm-slot 0 \
--hsm-password 12345678 \
--key-encryption-key "hsm::softhsm2::0::my-kek" \
--auto-rotation-check-interval-secs 300
How the scheduler works¶
On each tick, the scheduler:
- Queries all Active symmetric keys and private keys whose
x-rotate-interval > 0. - For each candidate, computes the next rotation deadline:
- Previously rotated key:
x-rotate-date + x-rotate-interval - Never-rotated key with Initial Date:
initial_date + x-rotate-offset + x-rotate-interval - Rotates every key whose deadline is in the past.
- Emits an OpenTelemetry counter
kms.key.auto_rotationlabelled withuidandalgorithmfor each successful rotation.
The scheduler never returns HSM UIDs — auto-rotation is SQL-only.
Attribute changes on auto-rotation¶
| Attribute | Old key | New key |
|---|---|---|
Unique Identifier |
unchanged | fresh UUID (or name@N for keysets) |
State |
Deactivated (§4.57) | Active |
Link[ReplacementObjectLink] |
→ new key UID | — |
Link[ReplacedObjectLink] |
— | → old key UID |
x-rotate-generation |
unchanged | old value + 1 |
x-rotate-date |
unchanged | timestamp of rotation |
x-rotate-interval |
set to 0 (cron skips old key) |
inherited from old key |
x-rotate-name |
unchanged | inherited from old key |
x-rotate-offset |
unchanged | inherited from old key |
x-initial-date |
cleared | set to now (resets next deadline) |
Cryptographic Algorithm |
unchanged | copied from old key |
Cryptographic Length |
unchanged | copied from old key |
Note:
x-rotate-intervalis inherited on auto-rotation (policy continues on the new key automatically). On manualRe-Keyit is set to0on the new key — the operator must re-arm the policy explicitly.
End-to-end setup¶
Step 1 — Create a key in a keyset¶
# SQL key: UID must equal the keyset name
ckms sym keys create --key-id my-keyset --algorithm aes --length 256
Step 2 — Attach a rotation policy¶
ckms sym keys set-rotation-policy \
--key-id my-keyset \
--name my-keyset \
--interval 3600 \
--offset 60
Step 3 — Enable the server scheduler¶
In kms.toml:
The scheduler will rotate my-keyset for the first time roughly 60 + 3600
seconds after its Initial Date, and every 3600 seconds thereafter.
Disabling auto-rotation on a key¶
Set x-rotate-interval to 0:
This prevents the scheduler from selecting the key without removing any other keyset metadata.
Keyset chain depth warning¶
When a Decrypt or Verify call resolves a keyset name (e.g. my-keyset)
rather than an explicit key UID, the server walks the generation chain
newest-to-oldest until one generation succeeds. A very deep chain indicates
that many ciphertexts are still bound to old key generations — a signal that
client-side re-encryption may be overdue.
To surface this, configure keyset_warn_depth in kms.toml:
# Emit a server warning when decryption succeeds at chain depth >= this value.
# Default: 5. 0 disables the warning.
keyset_warn_depth = 5
Or via the CLI flag:
When the threshold is reached, the server emits a WARN log line:
Decrypt: keyset chain depth 5 >= warn threshold 5 for uid my-keyset@0;
consider re-encrypting with the latest key
Traversal is unbounded
The chain walk has no hard limit. All key generations remain reachable
(subject to KMIP state rules). keyset_warn_depth is purely a
monitoring hint — it never prevents a decryption from succeeding.
Observability¶
The server increments the OpenTelemetry counter kms.key.auto_rotation with
labels uid and algorithm on every successful auto-rotation. Use your
OTel-compatible backend (Prometheus + Grafana, Datadog, …) to:
- Alert on unexpected gaps in rotation activity.
- Audit which keys were rotated and when.
- Track rotation throughput across the fleet.
See Monitoring for the full OTel setup guide.