diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index e623a14..6a27ce3 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -290,6 +290,7 @@ | task | NK-WP-0049-T38 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T39 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0049-T40 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | +| task | NK-WP-0049-T41 | progress | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md | | task | NK-WP-0050-T01 | done | — | workplans/NK-WP-0050-playwright-consumer-registration.md | | task | NK-WP-0050-T02 | progress | — | workplans/NK-WP-0050-playwright-consumer-registration.md | | task | NK-WP-0050-T03 | wait | — | workplans/NK-WP-0050-playwright-consumer-registration.md | diff --git a/docs/identity-service-restore-packet.md b/docs/identity-service-restore-packet.md index 0645d68..cb36547 100644 --- a/docs/identity-service-restore-packet.md +++ b/docs/identity-service-restore-packet.md @@ -135,7 +135,7 @@ current production password validity, MFA or audit continuity. | Task / owner lane | Positive check | Negative check | Safe evidence | | --- | --- | --- | --- | | T16 / LLDAP | Preserved user/group/membership identities; existing-user bind/login | Wrong password rejected | Image/target IDs, aggregate comparisons and booleans | -| T17 / Authelia | Native storage decryption; same signing identity/client configs; isolated authentication | Wrong credentials or unregistered client refused | Public key thumbprint, test outcome, notification suppression | +| T17 / Authelia | Original config validates; storage key opens store; issuer JWKS equals archived key; `keycape` client verifier accepts KeyCape's archived secret; existing-user first factor against restored LLDAP | Wrong password and wrong client secret refused; no grant issued | Public key thumbprint, outcomes, file-notifier only (stage prepared in T41) | | T19 / privacyIDEA MFA | Existing retained token decrypts internally and actual existing-token MFA succeeds | Wrong enckey and invalid code refused in disposable copies | Token material never exported; bounded outcomes only | | T20 / privacyIDEA audit | Verify historical signatures; create and verify one isolated entry with original key | Mismatched public key/tampered disposable record rejected | Signature counts, continuity result, cleanup | | T18 / KeyCape | Same issuer/JWKS, registry/policy/receipt continuity; named current consumer authenticates | Wrong secret and denied scope rejected | Public fingerprints, owner-selected test IDs and outcomes | 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 88fc61e..c8e5c39 100644 --- a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md +++ b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md @@ -8,7 +8,7 @@ status: active owner: codex topic_slug: netkingdom created: "2026-10-12" -updated: "2026-10-17" +updated: "2026-10-24" flavor: implementation depends_on: [] state_hub_workstream_id: "17d315f0-829e-57c7-85cc-3a7914152804" @@ -508,8 +508,8 @@ id: NK-WP-0049-T17 status: wait priority: high assignee: codex -depends_on: [NK-WP-0049-T15, NK-WP-0049-T16] -blocking_reason: "LLDAP recovery T16 passed; await the exact isolated Authelia adapter/procedure and private authentication-flow inputs from T15." +depends_on: [NK-WP-0049-T15, NK-WP-0049-T16, NK-WP-0049-T41] +blocking_reason: "LLDAP recovery T16 passed. Await the T41 adapter (prepared and fixture-tested), then a founder-attended run with the age key and an existing-user password." state_hub_task_id: "b47f9975-d6d7-5bb8-93ce-886deb1dfa70" ``` @@ -525,6 +525,25 @@ positive/negative results using the restored directory. Disable outbound notifications and verify cleanup. Archive integrity/RSA parsing alone are not native Authelia acceptance. +2026-10-24 redraft, after design review with the founder. KeyCape's source +(`key-cape/src/internal`, `config/dev-config.yaml`) confirms the human-login chain: +KeyCape delegates first factor to Authelia as upstream OIDC with the `keycape` +client secret, Authelia binds against LLDAP, and KeyCape adds MFA through +privacyIDEA. The durable Authelia state that matters is in the Secret and config: +the OIDC issuer key, HMAC secret, `keycape` client verifier, storage encryption key +and LDAP bind password. The SQLite store holds little under `one_factor`, so +storage decryption is a cheap check here, not the main proof. T17 acceptance is: +(1) the original configuration validates with the original secrets, notifier +switched to a tmpfs file notifier; (2) the storage encryption key opens the +restored store; (3) the issuer JWKS public key equals the archived issuer key; +(4) the restored `keycape` client verifier accepts KeyCape's archived client +secret and refuses a wrong one, at the token endpoint, with no grant issued; +(5) first-factor login through Authelia against the restored LLDAP succeeds for +an existing user, a wrong password is refused. The full KeyCape→Authelia→LLDAP→ +privacyIDEA human-login chain is not part of T17. T18's selected consumer +(SECRETS-WP-0015-T04) is a workload login and does not pass through Authelia; +a human-login consumer acceptance would need its own selection and admission. + ## Prove retained privacyIDEA token decryption and existing-token MFA ```task @@ -1448,6 +1467,46 @@ attempt readiness, T18 actual recovery/consumer acceptance and T19 retained-fact MFA remain open. No actual archive, private key, Secret object, token exchange, OpenBao login, rotation, production action or owner assent was exercised. +## Prepare and fixture-test the isolated Authelia restore stage + +```task +id: NK-WP-0049-T41 +status: progress +priority: high +assignee: claude-code +depends_on: [NK-WP-0049-T16, NK-WP-0049-T29] +``` + +**Accountable worker:** claude-code; the founder retains the actual private +recovery inputs and runs the T17 stage. + +Add `isolated-authelia.py` and `run-isolated-authelia.sh` to +`tools/bootstrap-recovery/`, the Authelia counterpart of T29. Design, from the +2026-10-24 review: + +- Topology: the anchor-namespace pattern already used by the KeyCape session. + Restored LLDAP is the `--network none` anchor; Authelia and the probe client + join `container:` and talk over loopback only. No new network. +- Reuse without edits: import helpers from `isolated-lldap.py` and the other + existing modules. Do not modify `isolated-lldap.py` or the KeyCape files; + they are in the pinned source-hash list of the T38/T40 attempt. +- Inputs: the verified transfer receipt, the age key via + `sops-custody-unlock.sh`, an existing-user password by private prompt. + Original Authelia secrets and the `keycape` client secret come from the + archive, never from argv, environment listings or files outside tmpfs. +- Confinement: LDAP URL rewritten to loopback, SMTP notifier replaced by a + tmpfs file notifier, `authelia-smtp` not imported. Containment inspection + before private import; container and tmpfs cleanup verified on every exit. +- Checks: the five T17 acceptance items. Receipt records booleans, counts, the + issuer key thumbprint and image IDs only. + +**Done when:** unit tests cover config assembly (original secrets kept, notifier +and LDAP rewritten, path escape refused, missing secret refused), and an opt-in +synthetic-fixture test (`NK49_RUN_ISOLATED_AUTHELIA_TEST=1`) passes on the pinned +local images with disposable credentials only. The packet in +`docs/identity-service-restore-packet.md` names the stage and its exact command. +This prepares T17; it is not T17 acceptance and touches no production service. + ## Accept remaining recovery and caller evidence and close the G08 handoff ```task