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.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user