Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 4515@bnt-lap001 Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
129 lines
8.3 KiB
Markdown
129 lines
8.3 KiB
Markdown
# Bootstrap recovery transport check
|
|
|
|
Owner work: NK-WP-0049. Follow
|
|
[the exact recovery ceremony](../../docs/bootstrap-recovery-ceremony.md).
|
|
|
|
For an identity/custody outage, start with the
|
|
[Storage Share disaster recovery guide](../../docs/disaster-recovery-storage-share.md).
|
|
`retrieve-identity-offline.py` performs fixed-provider ciphertext retrieval from
|
|
privately entered independent provider access, without OpenBao/SSO. Its source
|
|
receipt pins the object, strong ETag, size and digest; failed downloads are removed.
|
|
`recovery-kit.py` builds an allowlisted standalone tools ZIP and can publish only
|
|
three new versioned guide/ZIP/checksum files beside the backups with exact
|
|
readback. `run-offline-recovery.sh` is the owner's private two-prompt handoff for
|
|
publication, outage-path retrieval and the existing archive recovery check.
|
|
Run secret prompts in the owner's own terminal, never an agent-captured PTY.
|
|
Actual owner publication/readback and outage-path archive recovery passed on
|
|
2026-10-16; see the [published location and receipts](../../docs/storage-share-recovery-publication.md).
|
|
Service restore admission remains in the
|
|
[isolated restore packet](../../docs/identity-service-restore-packet.md).
|
|
|
|
The first native-service stage is `run-isolated-lldap.sh USER TRANSFER_RECEIPT`.
|
|
It preflights the exact local Docker engine/images before asking privately for
|
|
the age key and backup-era existing-user password. `isolated-lldap.py` validates
|
|
the encrypted archive, restores original directory/key material only into
|
|
network-none/read-only/tmpfs containers, proves native LDAP bind and explicit
|
|
invalid-credential denial, compares durable identity rows and removes its owned
|
|
containers/plaintext. It uses no live Kubernetes/OpenBao endpoint or provider
|
|
grant. Actual-image synthetic tests and the real owner backup-recovery run
|
|
passed on 2026-10-16; T16 is done. See the packet and actual LLDAP receipt for
|
|
the exact target, resource limits, accepted scope and repeat-drill command.
|
|
|
|
`run-isolated-authelia.sh USER TRANSFER_RECEIPT` is the Authelia stage (T17,
|
|
prepared under T41). It asks for the same two private inputs. `isolated-authelia.py`
|
|
runs restored LLDAP as the network-none anchor with Authelia joining its
|
|
namespace, and checks the original config, storage encryption key, issuer
|
|
key, KeyCape's `keycape` client secret and existing-user first factor, with
|
|
wrong-password and wrong-client-secret refusal. Notifier, NTP and healthcheck
|
|
writes are confined; nothing leaves loopback. It imports `isolated-lldap.py`
|
|
unedited. Synthetic native tests and the actual owner run (2026-10-24) passed; T17 is done.
|
|
|
|
`run-isolated-privacyidea-mfa.sh TRANSFER_RECEIPT` is the existing-token MFA step (T19, prepared under
|
|
T42). After the age key it asks for your login name, then the PIN+code you would type at login.
|
|
`isolated-privacyidea-mfa.py` restores LLDAP, the database and the original keys into one network-free
|
|
namespace, repoints the archived LDAP resolver to the restored directory in the disposable copy only, and calls
|
|
`/validate/check` by user and realm, as KeyCape does: wrong key refused, wrong code refused, your code accepted.
|
|
It reuses `isolated-privacyidea.py` and `isolated-lldap.py` unedited. Fixture tests and the actual owner run (2026-10-24) passed; T19 is done.
|
|
|
|
`run-isolated-privacyidea.sh TRANSFER_RECEIPT` is the token-material/audit recovery step.
|
|
It asks only for the existing backup age key. `isolated-privacyidea.py` restores
|
|
the linked dump into a loopback-only PostgreSQL/container namespace, reads
|
|
retained token material internally, and verifies all stored audit signatures
|
|
plus one new isolated entry and wrong-public-key/tampering negatives. Original
|
|
keys are preserved; audit exceptions are reported without rewriting history.
|
|
No MFA code is consumed or native MFA acceptance claimed. Exit2 requires owner
|
|
review of audit exceptions; see the packet for bounds and safe receipts.
|
|
The actual owner run passed on 2026-10-16: five retained token materials,
|
|
279,209 historical signatures, new-entry and negative checks, and cleanup.
|
|
T20/T31 are done; actual existing-token MFA remains T19.
|
|
|
|
`verify-bundle.py` validates a digest-bound age-encrypted `secrets/` tar archive
|
|
using an explicit private temporary key and registered `keys/age.pub`. It checks
|
|
an exact required-file list, unrelated wrong-key refusal, safe isolated file
|
|
publication/readback and staging removal. No service or cluster is contacted.
|
|
|
|
It refuses links, special/duplicate/unreviewed entries, oversized content, private
|
|
age keys in the bundle, wrong digest/recipient and existing receipt destinations.
|
|
Only a closed metadata receipt leaves the process. Current service credentials,
|
|
independent/off-host custody, production acceptance and revocation remain separate
|
|
owner evidence. Same-UID/process-memory isolation and forensic secure erasure are
|
|
not provided by this local checker.
|
|
|
|
Tests use real age encryption/decryption with disposable keys and malicious
|
|
archive fixtures, never production credentials:
|
|
|
|
```bash
|
|
python3 -m pytest -q tests/test_bootstrap_recovery_transport.py
|
|
```
|
|
|
|
|
|
`inventory-storage-share.py` is a silent read-only child of the existing
|
|
attended Platform Backup-account procedure. It checks the caller's OIDC/admin
|
|
scope, reuses the owner custody accessor, and exports only bounded DAV names and
|
|
sizes. Warden revokes and cleans up the session. It never reads an age key or
|
|
object content and contains provider errors/redirects/proxies.
|
|
|
|
`inventory-scaleway.py` lists bounded metadata in the fixed backup bucket/prefix
|
|
using the existing scoped runtime delivery and pinned primary cluster. It uses
|
|
neither the bootstrap API key nor object GET/write/delete operations. Run through
|
|
the already admitted caller with cached boto3 1.40.18; it emits only its safe
|
|
receipt. Both inventories explicitly state their bounds and cannot prove asset
|
|
absence outside them, ciphertext integrity, decryption or native recovery.
|
|
|
|
`fetch-privacyidea-backup.py` retrieves only the fixed reviewed PI ciphertext
|
|
under the same attended Platform Backup-account procedure. Strong ETag binding,
|
|
exact length, digest and private no-overwrite output are required. It reads no
|
|
age key. `check-privacyidea-database.py` then runs through temporary human custody,
|
|
checks the transfer lineage/digest and recipient, tests wrong-key refusal and
|
|
restores the decrypted custom-format dump into pinned local PostgreSQL with no
|
|
network, ports or host data mounts. Only three fixed table counts are exported.
|
|
It removes the disposable database and plaintext and records cleanup. It does
|
|
not prove token decryption, audit signing continuity or full identity recovery.
|
|
See the exact prepared command in the ceremony, rather than inventing new inputs.
|
|
|
|
```bash
|
|
NK49_RUN_DISPOSABLE_POSTGRES_TEST=1 python3 -m pytest -q \
|
|
tests/test_privacyidea_offhost_recovery.py \
|
|
tests/test_storage_share_inventory.py tests/test_scaleway_backup_inventory.py
|
|
```
|
|
|
|
`identity-backup-profile.json` prepares missing full service coverage and names
|
|
the exact current workload/PVC/credential dependencies, proposed provider paths
|
|
and remaining implementation/admission gates. It is a proposal, not a new live
|
|
publisher grant. `prepare-identity-coverage.py --receipt NEW_PATH` verifies the
|
|
primary cluster and current deployment/PVC/reference bindings using metadata
|
|
only. It refuses replacement resources, unobserved/unready deployments and new
|
|
unreviewed references. It has no Secret-data, pod-exec, mutation or upload route.
|
|
Its receipt says `prepared_not_admitted`; it cannot prove a backup exists.
|
|
|
|
`kingpath-offsite.py --item NAME=PATH ... --receipt PATH` (NK-WP-0055-T13) seals
|
|
break-glass access material that otherwise lives only on the founder workstation,
|
|
such as the attended admin kubeconfig and the static tunnel key, to the registered
|
|
`railiance-backup` recipient. It uploads the result under a new name to the
|
|
Storage Share `Backup` account (`railiance-backups/net-kingdom-kingpath/`) and
|
|
reads it back with a hash check. Run it in your own terminal: it refuses unsafe
|
|
inputs (symlinks, group/world-readable, foreign-owned) before prompting for the
|
|
app password. Plaintext is packed in memory and never written to disk. The
|
|
receipt (0600) holds item names, sizes and the ciphertext digest, never values.
|
|
Recovery needs only the age key and the Storage Share password (crisis custody
|
|
list items 1 and 2). Contained tests: `tests/test_kingpath_offsite.py`.
|