104 lines
5.3 KiB
Markdown
104 lines
5.3 KiB
Markdown
# Cutover Runbook — bermooda_usermanager → UserManager (IdP)
|
|
|
|
Operational steps for cutting Hamsoo over to UserManager as the identity
|
|
provider, and for rolling back if verification fails. Every gate below has a
|
|
command; do not advance while a gate is red.
|
|
|
|
## 1. Preconditions (all must be green)
|
|
|
|
| Gate | Command | Required result |
|
|
|---|---|---|
|
|
| Hamsoo suite | `manage.py test --noinput` (in `hamsoo_backend`) | `OK` (1 documented skip) |
|
|
| UserManager suite | `manage.py test --noinput` (in `apps/api`) | `OK` |
|
|
| Migration drift | `manage.py makemigrations --check` (in `apps/api`) | `No changes detected` |
|
|
| Export | `python apps/identity/migration/export_identity_dump.py --db-name <legacy-db> --output dump.json` | counts match the legacy tables |
|
|
| Import dry-run | `manage.py import_legacy_identity <dump> --report import_report.json` | `has_problems: false`, exit 0, **0 collisions** |
|
|
| Phone OTP | covered by `manage.py test apps.sms` (43 tests) | `OK` |
|
|
| Shadow divergence | Hamsoo: `manage.py auth_divergence_report` | `blocking` = 0 |
|
|
|
|
Export notes:
|
|
|
|
* The exporter reads MySQL directly (`authmanager_useraccount`,
|
|
`workspacemanager_workspace`, `workspacemanager_workspacemember`) and needs
|
|
no Django. It runs with any Python that has `pymysql` or `mysqlclient`; in a
|
|
venv without a driver it falls back to the machine's global CPython 3.12
|
|
site-packages (`mysqlclient`).
|
|
* Connection settings: `--db-host/--db-port/--db-name/--db-user/--db-password`
|
|
or `LEGACY_DB_*` environment variables. No password default.
|
|
* Rehearsal source on this machine: `auth_staging_new_2`
|
|
(144 users / 198 workspaces / 0 members — verified against the 10-11 dump).
|
|
Point `--db-name` at the production legacy database for the real cutover.
|
|
|
|
## 2. Production environment
|
|
|
|
### UserManager (`apps/api`)
|
|
|
|
| Variable | Value | Notes |
|
|
|---|---|---|
|
|
| `DJANGO_ENV` | `production` | boot refuses dev defaults |
|
|
| `DJANGO_SECRET_KEY` | *secret* | |
|
|
| `JWT_SIGNING_KEY` | *secret* (≥32 bytes) | boot fails on dev default |
|
|
| `OIDC_ISSUER` | `https://<idp-host>` | embedded in `iss`; must match what clients validate |
|
|
| `SSO_COOKIE_DOMAIN` | `.hamsoo.me` | parent-domain cookie for SSO |
|
|
| `SSO_TRUSTED_REDIRECT_HOSTS` | `hamsoo.me,www.hamsoo.me` | post-login allow-list |
|
|
| `SMS_PROVIDER` | `kavenegar` | boot refuses `console` in production |
|
|
| `KAVENEGAR_API_KEY` / `KAVENEGAR_SENDER` | *secret* / sender id | |
|
|
| `EMAIL_FROM_ADDRESS` | no-reply domain address | |
|
|
| `MAIL_DOMAIN`, `MAIL_INBOUND_SECRET` | mailbox domain + *secret* | |
|
|
| `MEDIA_ROOT` | persistent volume | mailbox uploads |
|
|
| `DJANGO_ALLOWED_HOSTS`, `CORS_ALLOWED_ORIGINS`, `CSRF_TRUSTED_ORIGINS` | IdP host + `https://hamsoo.me` | |
|
|
|
|
### Hamsoo backend (`hamsoo_backend`)
|
|
|
|
| Variable | Value | Notes |
|
|
|---|---|---|
|
|
| `IS_PRODUCTION` | `1` | |
|
|
| `IDENTITY_PLATFORM_BASE_URL` | `https://<idp-host>` | |
|
|
| `IDENTITY_PLATFORM_ISSUER` | same as `OIDC_ISSUER` | `iss` validation |
|
|
| `IDENTITY_PLATFORM_CLIENT_ID` / `CLIENT_SECRET` | *secret* | BFF-only; never expose to the browser |
|
|
| `IDENTITY_PLATFORM_REDIRECT_URI` | `https://hamsoo.me/sso/callback` | registered in the `hamsoo` OAuth client |
|
|
| `IDENTITY_PLATFORM_JWKS_URL` / `AUDIENCE` | JWKS endpoint / audience | |
|
|
| `SSO_FRONTEND_BASE_URL` | `https://hamsoo.me` | |
|
|
|
|
## 3. Cutover sequence
|
|
|
|
1. **Freeze** writes to the legacy usermanager (announce maintenance window).
|
|
2. **Export** the legacy database → fresh dump (schema_version 1).
|
|
3. **Dry-run** `import_legacy_identity <dump> --report import_report.json`;
|
|
confirm `has_problems: false` and zero phone/username/email collisions.
|
|
4. **Backup** the UserManager database (`mysqldump`/snapshot).
|
|
5. **Apply** `import_legacy_identity <dump> --apply --report import_report.json`.
|
|
The import is idempotent — re-running only updates, never duplicates.
|
|
6. **Verify counts** from the report (users / organizations / memberships /
|
|
profiles match the dry-run numbers exactly).
|
|
7. **Phone OTP** spot-check: sign in with a migrated number, receive + verify
|
|
a code, receive access + refresh tokens.
|
|
8. **Switch traffic**: point Hamsoo at the IdP env (section 2), deploy
|
|
`hamsoo_backend` + frontend, enable the SSO login path.
|
|
9. **Watch** `manage.py auth_divergence_report` during the window — every
|
|
`blocking` row is a session that would break; target is 0 before full traffic.
|
|
|
|
## 4. Rollback
|
|
|
|
Roll back **before** the legacy service is decommissioned — the legacy database
|
|
is the restore point for the whole window.
|
|
|
|
1. Stop the SSO login path in Hamsoo (feature flag / previous frontend build).
|
|
2. Re-point Hamsoo auth config to the previous identity service
|
|
(previous env values + previous deployment).
|
|
3. Restore UserManager DB from the step-4 backup if it was mutated (the import
|
|
only added rows; restoring removes them cleanly).
|
|
4. Confirm: legacy login works, `auth_divergence_report` shows no new blocking
|
|
rows for the rolled-back path.
|
|
5. Keep the dump + `import_report.json` artifacts for the next attempt.
|
|
|
|
Rolling back does not lose data as long as the legacy database was frozen only
|
|
for the window and un-frozen at step 1 of rollback.
|
|
|
|
## 5. After cutover (deferred, needs explicit approval)
|
|
|
|
- Remove `bermooda_usermanager` code/tables and the shadow tables.
|
|
- Rewrite git history for the RSA key committed in `54d5891` (rotate first).
|
|
- Normalize legacy phone numbers (`normalize_phones` backfill).
|
|
- Decommission the legacy service.
|