Master-key rotation
Safely deploy a dual-key ring, rehearse decryptability, rotate active secret envelopes, and retire an old key.
Tasuku rotates only each secret's current active envelope. Rotation is bounded, transactional per batch, and resumable: an envelope already using the active key is skipped on the next run. Output contains only counts, key versions, status, and error codes.
Preconditions — stop gate
Do not begin until all of these are true:
- A fresh Postgres backup has been restored successfully in an isolated rehearsal environment.
- The exact current key and version are available outside the database.
- A new base64-encoded 32-byte key has been generated and assigned a version greater than the current version.
- Server and worker deployment configuration can be rolled back while retaining both keys.
If any precondition fails, stop. Do not change the active version.
1. Deploy both keys
For an old version 1 and new version 2, deploy the new key as active and the old key as decrypt-only:
TASUKU_MASTER_KEY=<new-version-2-base64-key>
TASUKU_MASTER_KEY_VERSION=2
TASUKU_PREVIOUS_MASTER_KEYS={"1":"<old-version-1-base64-key>"}Replace the server and every worker together. Verify /readyz, worker startup, GitHub App access, and one secret-backed configuration read. Do not run mutation while any process lacks the complete key ring.
2. Dry-run — go/no-go gate
Run the operator from the shipped migration image:
docker compose run --rm --entrypoint tasuku-rotate-secrets migrate --dry-run --batch-size 100Proceed only when the final JSON line has "status":"COMPLETED", "unreadable":0, "rotated":0, and a plausible pending/remaining count. UNREADABLE_ENVELOPES is a hard stop: restore the missing previous key and repeat the dry-run. Dry-run never updates ciphertext.
3. Rotate in bounded batches
docker compose run --rm --entrypoint tasuku-rotate-secrets migrate --batch-size 100Each RUNNING JSON line records committed progress. SIGINT, container termination, or a database error may leave earlier batches committed; rerun the same command with the same key ring. Already-rotated envelopes are not encrypted twice.
4. Verify — stop gate
Repeat the dry-run. The final report must show:
{"status":"COMPLETED","pending":0,"unreadable":0,"remaining":0}Then verify server and worker readiness, GitHub App token use, a repository credential override, an MCP secret reference, and a controlled review. Keep the previous key through the observation window and at least one successful backup-and-restore rehearsal.
Roll back
Never roll back by deleting the new key. Configure the old key as active and retain the new key as previous, then replace server and workers:
TASUKU_MASTER_KEY=<old-version-1-base64-key>
TASUKU_MASTER_KEY_VERSION=1
TASUKU_PREVIOUS_MASTER_KEYS={"2":"<new-version-2-base64-key>"}This reads envelopes committed before and after the interruption. If the rollback must remain permanent, dry-run and rotate active envelopes back to version 1 before considering removal of version 2.
Retire the previous key
Previous keys are never removed automatically. Remove version 1 only after the zero-pending verification, observation window, and restore rehearsal all pass. Restart every server and worker, repeat application checks, and retain the escrowed old key according to your recovery policy; historical non-active secret versions are not rewritten by this command.