Per-Org JWT Signing Key Rotation
Per-Org JWT Signing Key Rotation
Runbook for rotating an organization’s JWT signing keypair used to issue SPIFFE JWT-SVIDs. Verifiers consume the rotation through JWKS overlap — no change to workload IMDS URLs.
This is not site master encryption key (KEK) rotation, which re-wraps private keys at rest and does not publish new JWKS entries by itself.
Concepts
During overlap, tokens signed with either key must verify. After overlap, JWKS drops the retired key.
Prerequisites
- Per-org identity config already exists (Day 1).
TENANT_ADMINin the target org (REST API).- Know current
tokenTtlSecondsfor the org — overlap must be ≥ this value. - Know site
signing_key_overlap_max_sec(Day 0[machine_identity]) — overlap must be ≤ this bound. - Verifiers fetch JWKS from your published URLs — confirm they poll or cache with TTL ≤ overlap if they need seamless rotation.
Plan Overlap Duration
Choose signingKeyOverlapSeconds before rotating:
Practical guidance:
- Minimum: equal to
tokenTtlSecondsso tokens issued just before rotation remain valid untilexp. - Recommended:
tokenTtlSecondsplus verifier JWKS cache TTL plus a small buffer (for example 2× TTL or cache TTL + 300s, whichever is larger, capped by site max). - Long-lived tokens: if you increase
tokenTtlSeconds, plan overlap accordingly on the next rotation.
Example: tokenTtlSeconds = 300, verifier caches JWKS for 600s → use at least 600, often 900 if within site max.
Procedure
Step 1 — Record current state
Note the current signer and whether a previous rotation overlap is already in progress (two keys with expireAt on the inactive entry).
Step 2 — Rotate via REST
PUT the full config with rotateKey: true and required overlap. All other required fields must be supplied (PUT replaces config semantics for rotation path — include issuer, audiences, TTL):
Returns 200 OK. Core generates a fresh ES256 keypair in the inactive slot, switches the current signer, and sets the overlap timer for the previous key.
Do not send signingKeyOverlapSeconds when rotateKey is false — the API rejects it.
Step 3 — Rotate via nicocli TUI (alternative)
Answer yes to rotateKey, then enter signingKeyOverlapSeconds when prompted.
Step 4 — Verify overlap is active
Config:
Expect two entries: one with "currentSigner": true, one with "expireAt" (ISO timestamp).
JWKS:
Should be 2 during overlap. See Machine Identity Verification for full checks.
Issuance: sign a token (gRPC or IMDS) and confirm the JWT header kid matches the new current signer.
Step 5 — Wait for overlap to complete
No operator action is required to retire the old key from JWKS — Core drops it after expireAt.
Before decommissioning verifier config that references the old kid:
- Wait until
expireAtis in the past. - Re-fetch JWKS — only one key should remain.
- Confirm no in-flight tokens need the old key (all issued before overlap end have expired).
Rotation Without Config Changes
You can rotate keys without changing issuer, audiences, or TTL — supply the same values as GET config returns, plus rotateKey and signingKeyOverlapSeconds.
If you also need to change tokenTtlSeconds, you may do both in one PUT; recompute overlap against the new TTL.
To pause issuance without deleting config, set "enabled": false in a normal PUT (no rotation). Re-enable with "enabled": true when ready.
Troubleshooting
Related Documentation
- Machine Identity Verification — JWKS, gRPC, and IMDS checks after rotation
- Machine Identity (Day 1) — initial org config and token delegation
- Master Encryption Key Rotation (KEK) — site master key at rest (orthogonal to JWKS)
- SPIFFE JWT-SVID SDD — authoritative design