Quarterly key rotation drill
Goal: prove you can rotate the master key, a machine token and a secret, restore from the key backup, and offboard a person, and know how long it takes. Do it on a scratch instance first, then on production. Fake names below.
Record start and end time of each part in the table at the bottom.
A. Rotate the master key (data stays readable, ciphertext untouched)
Section titled “A. Rotate the master key (data stays readable, ciphertext untouched)”When the key lives in a writable key file (the generated /keys/master_key, or one made with key init):
- Run it on the host. It adds a version to the key file and rewraps every data key and stored authenticator secret
to it, together with the
key_rotateaudit row, in one transaction:Output:Terminal window docker compose exec kuukura /ku-server key rotateRewrapped N secret versions and M TOTP secrets to manual:v2.The key file now holds1:...and2:...; the highest version wraps new data. If the rewrap fails after the version was added, fix the cause and runkey rotate --rewrap-only(notkey rotateagain, which would add a third version). - Restart the server so it reads the new key file (
docker compose up -d --force-recreate kuukura). Until then the running process only knows version 1 and cannot read the rewrapped data. - Back up the new version:
docker compose exec -it kuukura /ku-server key showprints the whole ring once for this version; store it in your password manager. (The server logs a warning at every start until the active version has been shown.) - Check:
ku run ... -- truestill works;ku workspace auditshowskey_rotate;key rotate --rewrap-onlyagain rewraps 0. - You may remove the
1:line from the key file only when you will never restore an older database backup, and after the old backup entry is clearly marked retired.
When the key comes from VAULT_MASTER_KEY or a read-only secret file, key rotate cannot write it and says so. Do
the two steps yourself: add a line 2:<base64 of 32 random bytes> (for example from openssl rand -base64 32)
where the key lives, keeping the old lines, restart the server, then run ku-server key rotate --rewrap-only and
key show as above.
AWS KMS. Either let AWS rotate the key material (nothing to do here), or move to a new KMS key:
- Give the server’s AWS identity
kms:Encrypton the new key and keepkms:Decrypton the old one. - Set
VAULT_KMS_KEY_ARNto the new key and restart. New data is wrapped under the new key; existing rows keep the ARN they were wrapped with (stored per row) and are still read through it. - Run
ku-server key rotate. With aws-kms it rewraps every row to the configured ARN (authenticator secrets too) and prints... to aws-kms:<new arn>.(key showsays “not applicable”: the key never leaves KMS.) - Only then remove
kms:Decrypton the old key and schedule it for deletion (keep the long deletion window). Keep it if you might restore an older database backup.
Switching between the two providers (a key file to KMS, or back) is not supported: a stored key id such as
manual:v2 is read only by the manual provider and aws-kms:... only by KMS, and there is no migration that
rewraps across them. Pick one at the start; changing needs a purpose-built migration that does not exist yet.
A2. Verify the audit log
Section titled “A2. Verify the audit log”The audit log is a hash chain; nothing in the running server re-checks it, so run it on a schedule (weekly is reasonable) and after any incident:
docker compose exec kuukura /ku-server audit verifyOutput: OK: audit log verified, 1432 rows, head 3f5a.... It exits non-zero and says where the chain breaks if
a row was edited or removed in the middle. Copy the row count and head hash to somewhere the database’s operator
cannot edit (a ticket, a git repo, another system). The chain alone cannot show that the newest rows were cut
off; comparing a later run with a stored head can: the head you stored must still be in the chain, and the row
count must not have gone down.
B. Rotate one machine token
Section titled “B. Rotate one machine token”Machine tokens last about 90 days. To renew: issue a new machine, short overlap, disable the old one.
ku workspace invite ci-deploy-2 --machine(copy the token) andku workspace grant ci-deploy-2 read <tags>.- Update
KU_TOKENin CI; run one pipeline. ku workspace disable ci-deploy(ends its tokens immediately).
C. Rotate one secret
Section titled “C. Rotate one secret”ku secret set STRIPE_KEY app:shop env:prodwith the new value from the provider (new version).- Deploy consumers (
ku run). If the old value leaked:ku secret revoke STRIPE_KEY env:prodfirst (blocks reads until the nextset), then set the new value.
D. Restore from the key backup
Section titled “D. Restore from the key backup”On a scratch machine:
- Copy the key text from your password manager (the ring
key showprinted, all versions) into a key file (VAULT_MASTER_KEY_FILE) or intoVAULT_MASTER_KEY. - Start a scratch instance with a copy of the database and this key; read one secret with
ku run. A wrong or incomplete key stops the server at start with “the master key does not match the data (key manual:vN cannot open it)”. - Destroy the scratch copy.
If you need an encrypted file instead of a password-manager entry, encrypt the text with your own tool (age, gpg, sops).
E. Rehearse offboarding
Section titled “E. Rehearse offboarding”ku workspace disable leaver@example.com(disables login, ends sessions, disables machines they own).- List what they could read:
ku workspace auditfor theirread/revealrows, and check their grants/groups. - Rotate each of those secrets (part C).
Record
Section titled “Record”| Part | Start | End | Minutes | Notes |
|---|---|---|---|---|
| A master key | ||||
| A2 audit verification | rows and head stored where: | |||
| B machine token | ||||
| C one secret | ||||
| D restore from backup | ||||
| E offboarding |