Files
jobtrackingapp/docs/operations/data-protection-key-rotation.md
T
cesnimda 955182b7c2
CI and Deploy / test (pull_request) Failing after 1m22s
CI and Deploy / deploy (pull_request) Has been skipped
fix: close release preflight gaps
Route public health checks to the API, backfill and synchronize job opportunities, stabilize SPA smoke tests, and document operator-only production steps.
2026-07-31 20:18:30 +02:00

3.1 KiB

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:

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:

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.