railiance-platform/docs/backup-credential-recovery.md
codex 4a9d6e6c7f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Record verified Forgejo Scaleway backup and recovery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
2026-09-06 01:03:02 +02:00

147 lines
8.6 KiB
Markdown

# WP-0029 provider recovery procedure
Primary platform backup is Scaleway (RESOURCE-WP-0002). This procedure covers
the independent Nextcloud secondary-copy lane.
Scope: invalidate the exposed Nextcloud upload predecessor and prove replacement
encrypted upload and offsite recovery under CCR-2026-0004. The route is a
Nextcloud file-drop share, not a platform-admin OpenBao credential. OpenBao
administration does not confer Nextcloud share-owner authority.
## Required owner inputs
- Nextcloud share owner/account and the reviewed share-management session.
- A replacement file-drop share/token delivered through protected custody;
never chat, argv, Git or State Hub. Record only custody coordinates.
- Owner-authorized retrieval access for the encrypted offsite artifact. Do not
assume an upload-only file-drop token can list or download backups.
- A protected reference to the predecessor for a bounded rejection probe, or
accepted provider-side revocation evidence. Do not reconstruct it into logs.
## Execution order
1. Confirm current OpenBao KV version and preserve every existing field. The
lane contains `AGE_PRIVATE_KEY`; replacing only upload fields must use CAS
and preserve recovery escrow. Never use a partial `kv put` that drops it.
2. Confirm the exact Nextcloud share and all backup consumers. Besides the
workstation helpers, activity-core consumes `NC_WEBDAV_TOKEN` and
`NC_WEBDAV_URL` through ExternalSecret `actcore-backup-offsite`. Coordinate
its next invocation so process-cached credentials cannot produce a false
replacement-success claim.
3. Create the replacement under provider authority. Verify its permissions
match the file-drop lane; no production delete/admin authority is needed.
Seed both upload fields through contained OpenBao CAS and verify delivery
with boolean comparisons. Keep provider secrets out of evidence.
4. Upload a uniquely named age-encrypted artifact with the replacement. Require
HTTP 200/201/204; redirects and network errors are not success. Record only
artifact identity and safe status. A synthetic fixture can prove transport,
but it does not establish Forgejo application recovery.
5. Invalidate the predecessor at the provider. Verify rejection with the old
credential in protected memory against the same bounded operation. A 401/403
or accepted provider invalidation receipt is evidence; a timeout is not.
Preserve existing retained backup objects and access to their encryption key.
6. Retrieve the newly uploaded encrypted artifact through the separate owner
recovery path, decrypt in a protected temporary directory, and verify its
expected content. For Forgejo restore acceptance, use a fetched real backup
and the owning isolated restore procedure with repository/blob/database
checks. Require the local fetched artifact to exist before invoking the
historical infra drill: that script otherwise creates a fresh production
dump, which cannot prove offsite recovery.
7. Record provider invalidation, replacement KV version, upload/download,
decryption, isolated restore and cleanup receipts. Mark T02 done only after
the agreed recovery checks pass. No receipt may contain the predecessor's
value, fingerprint, length or shape.
## Recovery-key exposure remains separate
CCR-2026-0004 records historical exposure of the age recovery key as well as
upload credentials. Upload-share rotation alone must not clear that taint.
Retain old age recovery material until all retained artifacts are accounted for
and any owner-approved re-encryption is complete. Do not silently rotate or
discard the age key as part of this upload-token repair.
## Prepared transport
`lib/railiance-backup-common.sh` passes curl credentials and credential-bearing
URLs via stdin configuration, disables curlrc, suppresses backend diagnostics,
requires HTTPS and rejects non-success responses. The Python backend streams
the artifact, refuses redirects and suppresses credential-bearing exception
text. These source changes are preparation; no provider rotation, upload or
restore is claimed by their tests.
## Dedicated Backup account (2026-09-05)
The operator-provisioned account is `Backup` on `nx4069.your-storageshare.de`.
Its credentials remain in OpenBao KVv2 `operators/nextcloud/backup`, fields
`BACKUP_USERNAME` and `BACKUP_PASSWORD` (API `operators/data/nextcloud/backup`).
The UI's `secrets/` prefix is not a separate KV mount.
Future uploads use the account's `/railiance-backups` folder through a dedicated
create-only share. Only its derived `NC_WEBDAV_TOKEN` and `NC_WEBDAV_URL` enter
the existing workload lane and activity-core ExternalSecret; the account password
is not a production credential. Recovery download and cleanup use operator
account authority. Preserve `AGE_PRIVATE_KEY` and all unrelated KV fields with CAS.
The server-reported quota is **10 GiB (10,737,418,240 bytes)**. This is a hard
capacity limit shared by the account's files. Retention counts such as 14 daily
and four weekly backups do not guarantee they fit. Check quota before large
uploads; a quota rejection is a failed backup. Pruning retained backups requires
an explicit retention decision and operator authority; upload jobs cannot delete.
Existing Bernd-owned backups remain in their original location and require their
existing recovery access. This cutover neither migrates them nor revokes Bernd's
personal account or old share.
`scripts/migrate_nextcloud_backup_account.py` performs the attended account,
quota, create-only share, encrypted fixture recovery and CAS checks. Run it only
through `warden access openbao-platform-admin-login --exec -- ...`, with a fresh
private receipt path and `--confirm 'MIGRATE TO Backup'`. The helper is silent;
receipts contain only status and metadata. Review a failed receipt before retrying:
a failure can leave the dedicated folder/share or an identified test fixture, and
a cleanup failure after CAS can occur after credentials have already changed.
Refresh `actcore-backup-offsite`, verify delivery by contained comparison, and
restart its three environment consumers (`actcore-api`, `actcore-event-router`,
`actcore-worker`). Record their readiness and loaded-value comparisons.
## Real archive acceptance
`capture_forgejo_archive.py` requires the exact producer's successful exit marker,
checks every transferred chunk's length and the complete SHA-256 against the
producer, then validates ZIP structure and member CRCs before encryption. The
2026-09-05 recovery attempt exposed a truncated September 4 encrypted archive;
successful age decryption and upload alone cannot certify backup completeness.
`verify_nextcloud_offsite_restore.py` streams a real encrypted archive through
the current upload lane and downloads it separately through owner recovery
custody. It emits a hash-bound transfer receipt. Run this silent helper through
the attended Warden envelope, with fresh private staging and receipt paths.
`restore_forgejo_offsite_locally.py` requires both that receipt and the fetched
archive. Its disposable local Docker network is internal, exposes no host ports,
and uses an independent database password/configuration. Production app.ini is
never activated. It checks database import, application health, repository API
and clone/fsck, and package blob integrity; cleanup removes its named containers,
anonymous volumes, network and extracted staging. Remaining fetched plaintext
must be removed after the acceptance receipt is preserved.
This bounded application-data recovery does not test replacement runners or
prove that every restored package can be installed or every image pulled.
Package-consumer acceptance and broader disaster-recovery guarantees remain
with their owning assurance tasks. Full backups share the account's 10 GiB
quota; a newly measured archive size must inform the separate retention decision.
## WSL attended-login browser repair
If this host has no `xdg-open`, `x-www-browser` or `www-browser`, the installed
Bao browser launcher cannot open its login page. Warden contains that output,
so this presents as a failed callback before the owner command starts. Use:
```bash
python3 scripts/openbao-attended-exec.py -- /absolute/path/to/reviewed-silent-command
```
The wrapper keeps Warden's `openbao-platform-admin-login --exec` envelope and
adds a repository-scoped launcher only when all native providers are absent.
The launcher allows only the Keycloak HTTPS host and exact local OIDC callback;
it sends the URL to Windows PowerShell through stdin without recording it.
No token helper or captured Warden output may be inspected. A receipt written
by the owner command proves handoff; session cleanup is assessed separately
from the final Warden exit status.