net-kingdom/tools/bootstrap-recovery
tegwick 5008e34f1a
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0049-T42: rebuild the MFA stage around the restored directory; check by user and realm
The founder's first real run stopped at ResolverError on all checks: real tokens
belong to an LDAP user and the archived resolver names the in-cluster directory.
Restored LLDAP now joins the database's network-free namespace, archived resolver
addresses are repointed to loopback in the disposable copy only (any other address
is refused), and the checks call POST /validate/check by user and realm as KeyCape
does. Fifteen native fixture tests pass (HOTP and TOTP+PIN, negatives, bundle).
Also covers the user-to-token resolver and the encrypted bind password.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:42:35 +02:00
..
capture-identity.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
check-privacyidea-database.py Prepare bounded off-host privacyIDEA recovery drill 2026-10-16 00:09:13 +02:00
fetch-privacyidea-backup.py Prepare bounded off-host privacyIDEA recovery drill 2026-10-16 00:09:13 +02:00
identity-backup-profile.json fix(recovery): bind admitted host and record live provider scope approval 2026-10-16 07:44:36 +02:00
identity-bundle.py fix(recovery): bind admitted host and record live provider scope approval 2026-10-16 07:44:36 +02:00
identity-credential-setup.py security(recovery): fence agent reads before publishing backup credentials 2026-10-16 07:49:48 +02:00
identity-custody.json feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
identity-files.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
identity-isolation.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
identity-offsite.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
identity-retention.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
identity-runtime.py feat(recovery): verify scoped delivery and create first full offsite bundle 2026-10-16 09:21:55 +02:00
install-identity-backup.py fix(recovery): bind admitted host and record live provider scope approval 2026-10-16 07:44:36 +02:00
inventory-scaleway.py Inventory off-host recovery assets through existing bounded backup routes 2026-10-15 23:58:41 +02:00
inventory-storage-share.py Prepare bounded off-host privacyIDEA recovery drill 2026-10-16 00:09:13 +02:00
isolated-authelia.py NK-WP-0049-T41: isolated Authelia restore stage, fixture-tested 2026-10-24 17:14:52 +02:00
isolated-keycape-session.py fix(identity): retain group provenance and prepare sealed recovery custody 2026-10-17 00:28:17 +02:00
isolated-keycape.py fix(recovery): require one original client and refine native owner gates 2026-10-17 04:15:30 +02:00
isolated-lldap.py Prepare native isolated directory recovery and offline service images 2026-10-16 20:33:57 +02:00
isolated-privacyidea-mfa.py NK-WP-0049-T42: rebuild the MFA stage around the restored directory; check by user and realm 2026-10-24 18:42:35 +02:00
isolated-privacyidea.py Prepare isolated privacyIDEA token and audit recovery checks 2026-10-16 21:00:36 +02:00
prepare-identity-coverage.py Record off-host database recovery and prepare identity backup coverage 2026-10-16 00:32:50 +02:00
prepare-isolated-keycape.py fix(identity): retain group provenance and prepare sealed recovery custody 2026-10-17 00:28:17 +02:00
README.md NK-WP-0049-T42: rebuild the MFA stage around the restored directory; check by user and realm 2026-10-24 18:42:35 +02:00
recovery-kit.py Fix private recovery prompts on nonseekable terminals 2026-10-16 19:56:06 +02:00
retrieve-identity-offline.py Fix private recovery prompts on nonseekable terminals 2026-10-16 19:56:06 +02:00
retrieve-identity.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
run-isolated-authelia.sh NK-WP-0049-T41: isolated Authelia restore stage, fixture-tested 2026-10-24 17:14:52 +02:00
run-isolated-keycape.sh fix(identity): retain group provenance and prepare sealed recovery custody 2026-10-17 00:28:17 +02:00
run-isolated-lldap.sh Prepare native isolated directory recovery and offline service images 2026-10-16 20:33:57 +02:00
run-isolated-privacyidea-mfa.sh NK-WP-0049-T42: rebuild the MFA stage around the restored directory; check by user and realm 2026-10-24 18:42:35 +02:00
run-isolated-privacyidea.sh Prepare isolated privacyIDEA token and audit recovery checks 2026-10-16 21:00:36 +02:00
run-offline-recovery.sh Prepare independent outage recovery and Storage Share documentation 2026-10-16 17:59:18 +02:00
verify-bundle.py Harden remaining bootstrap custody callers and prepare isolated recovery 2026-10-15 22:57:14 +02:00
verify-identity-delivery.py feat(recovery): verify scoped delivery and create first full offsite bundle 2026-10-16 09:21:55 +02:00
verify-identity.py feat(recovery): prepare full identity backup and bootstrap handoff 2026-10-16 01:37:11 +02:00
verify-keycape-attempt-receipt.py NK-WP-0049-T33: independent attempt receipt verifier, retained location and ACME reissue step 2026-10-23 00:22:50 +02:00

Bootstrap recovery transport check

Owner work: NK-WP-0049. Follow the exact recovery ceremony.

For an identity/custody outage, start with the Storage Share disaster recovery guide. 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. Service restore admission remains in the isolated restore packet.

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 pass; the actual owner run is still open.

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:

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.

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.