From a9f68fc8a2c48969741d532ac8910f39b749c277 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 24 Oct 2026 18:49:33 +0200 Subject: [PATCH] NK-WP-0049-T19: done; founder ran the isolated existing-token MFA check on the actual backup Restored LLDAP, database and original keys in one network-free namespace; archived resolver repointed to the restored directory in the disposable copy. By user and realm through /validate/check: wrong key refused (error 907), wrong code refused, the founder's TOTP PIN+code accepted. Loopback only; cleanup verified. Does not prove the KeyCape consumer login chain or production token validity. Co-Authored-By: Claude Sonnet 5.5 Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 328925@bnt-lap001 Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed --- WORK-RECORDS.md | 2 +- ...ual-isolated-privacyidea-mfa-recovery.json | 59 +++++++++++++++++++ ...ctual-isolated-privacyidea-mfa-review.json | 31 ++++++++++ docs/identity-service-restore-packet.md | 9 +-- tools/bootstrap-recovery/README.md | 2 +- ...-bootstrap-secret-free-recovery-handoff.md | 22 ++++++- 6 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-recovery.json create mode 100644 docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-review.json diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 269a8e3..41dfc64 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -268,7 +268,7 @@ | task | NK-WP-0049-T16 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T17 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T18 | wait | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | -| task | NK-WP-0049-T19 | wait | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | +| task | NK-WP-0049-T19 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T20 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T21 | wait | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T22 | wait | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | diff --git a/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-recovery.json b/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-recovery.json new file mode 100644 index 0000000..688fbdd --- /dev/null +++ b/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-recovery.json @@ -0,0 +1,59 @@ +{ + "schema": "netkingdom.isolated-privacyidea-mfa.v1", + "status": "isolated_privacyidea_mfa_verified", + "production_contacted": false, + "secret_values_exported": false, + "existing_token_mfa_proven": true, + "user_to_token_resolver_proven": true, + "container_cleanup_verified": true, + "temporary_plaintext_cleanup_verified": true, + "source_sha256": "d55c5f93c085907be6ae19b0eba6ef360fe863ef8922aa5b6ec205f4837efa16", + "reused_source_sha256": { + "isolated-privacyidea.py": "4df7196678aac1ad594f32abf45c68dc21a9d2760d9e8a536a215a4a3c5d277f", + "isolated-lldap.py": "35bfa8411186ad936a199bd450c7e18c2ad51059301a6db9650e6bd055ab1b81", + "identity-bundle.py": "524a03c065db4307fcde83d33af39503f3de19cb6ef3a8ca72c991d4a8c08ee6", + "verify-identity.py": "039864d0141b552887752bf17cfdcf1bef4bdc9126f946a9c0533e4fd54ebf4f", + "retrieve-identity-offline.py": "3421dbb3f3280a001d33ea2deb12ab609404a0b4254cd43c21518d77d166f972" + }, + "target": { + "engine_id": "74f93783-6cef-43a8-a3c4-f62586cd3bc1", + "engine_name": "bnt-lap001", + "images": [ + { + "reference": "docker.io/lldap/lldap@sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d", + "image_id": "sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d" + }, + { + "reference": "ghcr.io/gpappsoft/privacyidea-docker@sha256:af7841adad262f129e0c1d4f553af13f21cb2f4dc713533f316cfe43ed0b4473", + "image_id": "sha256:af7841adad262f129e0c1d4f553af13f21cb2f4dc713533f316cfe43ed0b4473" + }, + { + "reference": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70", + "image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70" + } + ], + "production_contacted": false, + "network_mode": "none", + "host_ports_published": false + }, + "ciphertext_sha256": "7117b3fe1e4840c86b301c94c85e6b16709e5626bb0884bd0580bde630e30e30", + "manifest_file_count": 36, + "all_manifest_hashes_verified": true, + "ldap_resolvers_rewritten_to_loopback": 1, + "container_isolation_verified_before_private_import": true, + "user_resolved_through_restored_resolver_and_directory": true, + "selected_user_sha256": "2868946f999c23cc527736fa53c6e35ca7960cf01d06b33ea792e1e164aabcc1", + "user_token_count": 1, + "user_usable_token_count": 1, + "user_usable_token_types": [ + "totp" + ], + "wrong_key_refused": true, + "wrong_key_refusal": "error_907", + "wrong_code_refused": true, + "wrong_code_refusal": "wrong_value", + "existing_token_native_check_accepted": true, + "only_loopback_interface_verified": true, + "external_routes_absent": true, + "finished_at": "2026-10-24T16:48:44.390108+00:00" +} diff --git a/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-review.json b/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-review.json new file mode 100644 index 0000000..9a3a532 --- /dev/null +++ b/docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/actual-isolated-privacyidea-mfa-review.json @@ -0,0 +1,31 @@ +{ + "schema": "netkingdom.actual-isolated-privacyidea-mfa-review.v1", + "status": "verified", + "accepted_at": "2026-10-24T16:48:44+00:00", + "receipt": "actual-isolated-privacyidea-mfa-recovery.json", + "source_commit": "5008e34", + "source_sha256_matches_committed_file": true, + "reused_source_sha256_match_committed_files": true, + "known_recovered_ciphertext_matches": "7117b3fe1e4840c86b301c94c85e6b16709e5626bb0884bd0580bde630e30e30", + "exact_local_target_matches": true, + "checks_passed": [ + "ldap_resolver_repointed_to_restored_directory", + "user_resolved_through_restored_resolver_and_directory", + "wrong_key_refused", + "wrong_code_refused", + "existing_token_native_check_accepted" + ], + "token_type_checked": "totp", + "usable_tokens_for_user": 1, + "wrong_key_refusal": "error_907", + "wrong_code_refusal": "wrong_value", + "container_and_plaintext_cleanup_verified": true, + "agent_received_private_credentials": false, + "earlier_attempt": "A first run of a serial-based design stopped at ResolverError on all checks (receipt retained in /tmp, not in Git); the acceptance gate correctly claimed no proof and the stage was rebuilt.", + "not_proven": [ + "KeyCape consumer login through Authelia, LLDAP and privacyIDEA together", + "event handlers or policies that call outside the isolated namespace", + "tokens of users other than the one named", + "current production token PIN or code validity" + ] +} diff --git a/docs/identity-service-restore-packet.md b/docs/identity-service-restore-packet.md index 4aabd57..501128c 100644 --- a/docs/identity-service-restore-packet.md +++ b/docs/identity-service-restore-packet.md @@ -2,7 +2,7 @@ NK-WP-0049-T15. Prepared 2026-10-16. **Actual LLDAP recovery accepted at 18:37 UTC; native privacyIDEA token-material/audit recovery accepted at -19:10 UTC; native Authelia recovery accepted 2026-10-24 at 15:24 UTC. Existing-token MFA and the KeyCape consumer run remain unexecuted.** +19:10 UTC; native Authelia recovery accepted 2026-10-24 at 15:24 UTC; existing-token MFA accepted 2026-10-24 at 16:48 UTC. The KeyCape consumer run remains unexecuted.** Accountable preparer: codex. Execution/acceptance: Railiance Platform and the LLDAP, Authelia, KeyCape and privacyIDEA service owners. @@ -257,8 +257,9 @@ Existing-token MFA remains T19; event completeness is not claimed. ## Prepared owner step: isolated existing-token MFA (T19) -Prepared and fixture-tested 2026-10-24 under T42; not yet run on the actual -backup. Execute in your own terminal with a verified transfer receipt (the T23 +Prepared and fixture-tested 2026-10-24 under T42. The actual owner run passed the +same day (16:48 UTC); evidence is in `docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/`. +T19 is done. The procedure below remains for a future drill. Execute in your own terminal with a verified transfer receipt (the T23 procedure) and your authenticator ready: ```bash @@ -330,6 +331,6 @@ the one user you name, not for every user. - Service-owner acceptance of network/config translations and all required grants. New grants or production effects require their own owner decisions. -Until resolved, T15 stays in progress and T18–T19 remain waiting; T16, T17, T20 and T31 are done. Production +Until resolved, T15 stays in progress and T18 remains waiting; T16, T17, T19, T20 and T31 are done. Production cutover, full-host/OpenBao restoration and application database recovery are separate owner procedures. diff --git a/tools/bootstrap-recovery/README.md b/tools/bootstrap-recovery/README.md index 54ee08b..6756f25 100644 --- a/tools/bootstrap-recovery/README.md +++ b/tools/bootstrap-recovery/README.md @@ -43,7 +43,7 @@ T42). After the age key it asks for your login name, then the PIN+code you would `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. +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 diff --git a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md index b9196a9..0869313 100644 --- a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md +++ b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md @@ -563,11 +563,10 @@ production password validity. ```task id: NK-WP-0049-T19 -status: wait +status: done priority: high assignee: codex depends_on: [NK-WP-0049-T15, NK-WP-0049-T16, NK-WP-0049-T31, NK-WP-0049-T42] -blocking_reason: "LLDAP T16, token-material T31 and audit T20 passed; T42 stage prepared and fixture-tested 2026-10-24 and rebuilt after the first run (resolver/directory now restored; checks run by user and realm). Await the founder-attended run: run-isolated-privacyidea-mfa.sh with a verified transfer receipt, the age key, a login name and a token PIN+code." state_hub_task_id: "6f8af1a7-2a3f-5fa8-8202-83103ebd55b2" ``` @@ -590,6 +589,23 @@ wrong-code/wrong-key authentication refusal. Internal decryption or padding success does not authenticate a user. No MFA code is needed for T20/T31; the selected user's private factor input and exact isolated MFA adapter remain T19. +Completed 2026-10-24 at 16:48 UTC. The founder ran `run-isolated-privacyidea-mfa.sh` +privately on the independently retrieved generation (ciphertext `7117b3fe…0e30`, all 36 +manifest hashes verified). Restored LLDAP, the database and the original keys ran in one +network-free namespace; the archived LDAP resolver was repointed to the restored +directory in the disposable copy only. The named user was found through the restored +resolver and directory (hashed in the receipt) and had one usable TOTP token. Through +`POST /validate/check` by user and realm: a wrong encryption key was refused (privacyIDEA +error 907), a wrong code was refused, and the founder's PIN and code were accepted with +the original key and retained token material. Only loopback existed; containers and tmpfs +staging were cleaned. No production endpoint was contacted and the agent received no +secret. Evidence: `actual-isolated-privacyidea-mfa-recovery.json` and +`actual-isolated-privacyidea-mfa-review.json` in +`docs/evidence/2026-10-24-nk49-t19-privacyidea-mfa/`. Not proven: the KeyCape consumer +login through all four services, event handlers or policies that call outside the +namespace, other users' tokens, and current production token validity. An earlier +serial-based attempt stopped at `ResolverError` and claimed no proof; see T42. + ## Prove historical and new privacyIDEA audit-signature continuity ```task @@ -633,7 +649,7 @@ status: wait priority: high assignee: codex depends_on: [NK-WP-0049-T15, NK-WP-0049-T16, NK-WP-0049-T19, NK-WP-0049-T32, NK-WP-0049-T33, NK-WP-0049-T40] -blocking_reason: "T33/T40 done. Issuer-side attempt verified 2026-10-23 (receipt 16d72f7c); original OpenBao restored in isolation 2026-10-24 (RPF-WP-0064, receipt 4e048dee). Await the live consumer login: RPF-WP-0064 phase B network plus the SECRETS-WP-0015-T04 consumer run, each founder-admitted, and T19 retained-factor MFA." +blocking_reason: "T33/T40 done. Issuer-side attempt verified 2026-10-23 (receipt 16d72f7c); original OpenBao restored in isolation 2026-10-24 (RPF-WP-0064, receipt 4e048dee). T19 retained-factor MFA passed 2026-10-24. Await the live consumer login: RPF-WP-0064 phase B network plus the SECRETS-WP-0015-T04 consumer run, each founder-admitted." state_hub_task_id: "141dd773-c1fa-56c1-a852-bbe2372e5125" ```