# Production DataProtection key rotation The repository history contains one DataProtection key: - exposed key id: `9a89a42c-d2bd-4770-83fb-5930685432db` - historical path: `JobTrackerApi/keys/key-9a89a42c-d2bd-4770-83fb-5930685432db.xml` Do not rotate blindly. Production stores its key ring in `/data/keys` inside the backend volume. First prove whether production ever used the exposed key. ## 1. Non-destructive fingerprint check From `/opt/job-tracker/app` on the production host: ```bash docker compose exec -T backend sh -lc \ 'for key in /data/keys/key-*.xml; do [ -f "$key" ] && basename "$key"; done' ``` - If the exposed key id is **absent**, save the command output as release evidence and close the rotation blocker. Production keys were not the committed key; deleting a healthy ring would only cause an outage. - If the exposed key id is **present**, continue below. Do not delete or move anything yet. ## 2. Back up before a confirmed rotation First complete the database backup and scratch-restore checklist in `docs/deployment/backup-restore.md`. Then take a restricted backup of the key ring: ```bash cd /opt/job-tracker/app umask 077 docker compose exec -T backend tar -C /data -czf - keys \ > "/opt/job-tracker/backups/dataprotection-keys-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" ``` Store that archive offline with production secrets. It contains decryption keys and must not enter Git, chat, CI artifacts, or ordinary application logs. ## 3. Inventory the impact This ring protects more than browser cookies. Before revoking an exposed key, count affected records without printing their encrypted values: - users with `TotpSecretEncrypted` or `TotpPendingSecretEncrypted`; - Gmail connections with encrypted access/refresh tokens; - Microsoft Graph connections with encrypted access/refresh tokens; - IMAP connections with encrypted passwords; - encrypted application-export files that still need to remain restorable. Password-reset/email-confirmation tokens and active authentication cookies will also stop validating. ## 4. Choose the cutover ### No protected records Use a short maintenance window, replace the `/data/keys` ring, restart the backend, then verify login, password reset, email confirmation, and a new encrypted backup. Existing sessions will be signed out. ### Protected records exist Do not simply delete the old ring: that would strand TOTP secrets and mail credentials. Keep the old ring available only for a one-time re-protection operation, create a new active key, re-protect every persistent secret, verify the affected integrations, then remove the exposed key and restart. The repository does not yet include that production-only migration because it is unnecessary unless the fingerprint check proves the exposed key is active. ## Completion evidence - production key-id listing captured without key contents; - database scratch restore passed; - key-ring backup stored outside the repository; - affected-record counts recorded without encrypted values; - login, password reset, TOTP, Gmail/Graph/IMAP, and encrypted backup behavior verified as applicable; - exposed key id absent from the active `/data/keys` directory.