diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index ead8037..ae6e3f7 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -338,7 +338,7 @@ | task | NK-WP-0056-T01 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T02 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T03 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | -| task | NK-WP-0056-T04 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | +| task | NK-WP-0056-T04 | done | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T05 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T06 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | | task | NK-WP-0056-T07 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md | diff --git a/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/01-setup.json b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/01-setup.json new file mode 100644 index 0000000..72e39af --- /dev/null +++ b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/01-setup.json @@ -0,0 +1,14 @@ +{ + "action": "setup", + "ccr_sha256": "97e8e4b17e12d6bcbffe5acd4f8fbd68f52fe340333968bb175e8da18ecb6513", + "coding_agents_denied": true, + "custody_is_current": true, + "kv_version": 1, + "lane": "svc-privacyidea", + "phase": "done", + "source_ccr": "CCR-2026-0055", + "source_sha256": "5b31b3567af07bba09530c6d3b0ed8b5c947df22bd13ac0442dc33c0d9b420c0", + "status": "custody_prepared", + "values_recorded": false, + "workload_access": false +} diff --git a/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/02-verify.json b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/02-verify.json new file mode 100644 index 0000000..2ccb87e --- /dev/null +++ b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/02-verify.json @@ -0,0 +1,14 @@ +{ + "action": "verify", + "ccr_sha256": "97e8e4b17e12d6bcbffe5acd4f8fbd68f52fe340333968bb175e8da18ecb6513", + "coding_agents_denied": true, + "kv_is_current": true, + "kv_version": 1, + "lane": "svc-privacyidea", + "phase": "done", + "source_ccr": "CCR-2026-0055", + "source_sha256": "5b31b3567af07bba09530c6d3b0ed8b5c947df22bd13ac0442dc33c0d9b420c0", + "status": "custody_verified", + "values_recorded": false, + "workload_access": false +} diff --git a/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/03-rotate-bind.json b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/03-rotate-bind.json new file mode 100644 index 0000000..b890c62 --- /dev/null +++ b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/03-rotate-bind.json @@ -0,0 +1 @@ +{"actions": ["rotate-bind"], "configuration_writes": 1, "mfa_verified": true, "onboarding_policy": "owner-managed", "policies_changed": false, "result": "configured"} diff --git a/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/README.md b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/README.md new file mode 100644 index 0000000..c7500cf --- /dev/null +++ b/docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/README.md @@ -0,0 +1,19 @@ +# NK-WP-0056-T04 — svc-privacyidea bind in platform custody (2026-10-31) + +Founder-run in the founder's terminal under railiance-platform CCR-2026-0055 (request type +`attended-service-credential-delivery`, approved by platform-owner and net-kingdom-owner, `04d9dc8`; sha256 +`97e8e4b1…6513`). privacyIDEA keeps the resolver `BINDPW` encrypted in its own database and accepts it only through +the `pi-admin` API, so there is no ESO or workload reader: the value is held in OpenBao and written through by an +attended procedure. Receipts are value-free. + +| Step | Result | Receipt | +| --- | --- | --- | +| setup (attended platform-admin, custody decrypted, re-sealed and shredded after) | custody value signs in to LLDAP as `svc-privacyidea` (current); coding agents fenced; KV version 1 (CAS=0); no policy or role | `01-setup.json` | +| verify | KV value signs in to LLDAP as `svc-privacyidea`; fence present; no workload role | `02-verify.json` | +| `realm_bootstrap.py --attended --rotate-bind --from-openbao` (preview) | `actions: ["rotate-bind"]`, no write | operator output | +| apply, attempt 1 (not counted) | one `BINDPW` write (same value), then the `--verify-user admin` check failed: the code entered was not `admin`'s (the prompt did not name the account; prompts now name the user) | operator output | +| apply, attempt 2 | one `BINDPW` write (same value from OpenBao); `admin` resolved through `lldap-coulomb` and passed token-backed MFA | `03-rotate-bind.json` | + +The resolver bind is now written from OpenBao (`platform/workloads/net-kingdom/directory-binds/svc-privacyidea`); the +founder's custody copy holds the same value as a fallback. Rotation: change the password in LLDAP, write the next KV +version with CAS, run `realm_bootstrap.py --attended --rotate-bind --from-openbao --apply --verify-user admin`. diff --git a/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age index 4c5f910..fe6ce4e 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age and b/sso-mfa/bootstrap/secrets.enc/authelia/secrets.env.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/breakglass/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/breakglass/secrets.env.age index bf20872..f0c80c1 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/breakglass/secrets.env.age and b/sso-mfa/bootstrap/secrets.enc/breakglass/secrets.env.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/keycape/key.pem.age b/sso-mfa/bootstrap/secrets.enc/keycape/key.pem.age index 823e66e..8cc97d7 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/keycape/key.pem.age and b/sso-mfa/bootstrap/secrets.enc/keycape/key.pem.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/keycape/pi_admin_token.age b/sso-mfa/bootstrap/secrets.enc/keycape/pi_admin_token.age index efa233d..2dff9ee 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/keycape/pi_admin_token.age and b/sso-mfa/bootstrap/secrets.enc/keycape/pi_admin_token.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/keycape/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/keycape/secrets.env.age index 2fa3340..e9c0073 100644 --- a/sso-mfa/bootstrap/secrets.enc/keycape/secrets.env.age +++ b/sso-mfa/bootstrap/secrets.enc/keycape/secrets.env.age @@ -1,6 +1,6 @@ age-encryption.org/v1 --> X25519 un+UWbG5oXAsm5dXauwEzU41cpG5+blwU2gbAkEmVXw -j7kyOG6iU3FlO8Vcc0p8LklEjMlPw5kC8GP9g3mffx0 ---- JmF8vLxSsPTLu5qhvDscgD2EwKpppCW+ELfpdjFsdvg -#ƒò╊�\]÷ R»kHG0É[O à7°‹3QšU0t¢ ŒŠ6¸áÄ2Ÿ¼Ê4‰ ØŠˆl—%qÔ'}߬›¯)qµYþ¬oùÖ GšÐ=¢ÂçÀø^9ƒÂ„nJï_}“ÞP" Ã¹O¯ž¦÷y5a…v�çžç‰—4nÕ)bôÄ‹ØÀ‚|š2]€õtg!�‰ 8²|;ÖÞ!ÆŽ¢ÖîQmŒlè¯[ʯ5”¡â5Wì¨_4+× -jŸöÕ8±S±Æ"Ä´­¹«èº·¡~vzÕD¿î;ÎŽönÂyfö³sl‚�-�GÃÖCb{´›kùlw~f5}� úH’ÿçŠóÛݸ¾«äú´ q~ÌÛx|†©0fæ{a,D½¥^›�¨ˆt†`ÜvNõösc‘%‹ �#‘F|XÒM÷ zK¢lÍÒve2\‰hì­¦æ™ï–«òä2¬_M�¬âí(Í�3¦fÕ¬±m"+ýícNY„ùÓ:}­‚Ú¨ï0ÎD8ì~ˆî»Þh¬LPZç0¸Öûù–ÉèZXå?Óq"‚„ \ No newline at end of file +-> X25519 QLBViIVzS9+rvilFUi/3fTJYoWmfLFnatUUgeSeLWkM +CPWJ3Dn1NY/Uo2+8rzQcwaIeaz9HD6jmCm34zPw4X94 +--- iHAsNfMnCuzehlSl+WmGOY7TORSrlB/xpV606yXPkcE +ÍB]M¥ñÒè2™=Ç=e̮ײ¾;ä`á‘Ô5_6 +«óì:HÜf:Wá:¯Õ¸®p‘ÿ]!üÏw¬^¾OÀ«™*ˆ ‘ʬõZ]U>ˆZªÁ“ FJ“⌻ݶÇøÏÌ©Ñ{Ѱe9Ð2þ×¶7[ðÏSô\²Íª4Þi z°\½Ñ mœ˜ŒK1 \ No newline at end of file diff --git a/sso-mfa/bootstrap/secrets.enc/lldap/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/lldap/secrets.env.age index ba01232..9e8934d 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/lldap/secrets.env.age and b/sso-mfa/bootstrap/secrets.enc/lldap/secrets.env.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age index 06768e8..f22d5ce 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age and b/sso-mfa/bootstrap/secrets.enc/postgres/secrets.env.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/privacyidea/pi.enc.age b/sso-mfa/bootstrap/secrets.enc/privacyidea/pi.enc.age index f52b5ee..242d0c7 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/privacyidea/pi.enc.age and b/sso-mfa/bootstrap/secrets.enc/privacyidea/pi.enc.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/privacyidea/private.pem.age b/sso-mfa/bootstrap/secrets.enc/privacyidea/private.pem.age index 598585f..bc9dde5 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/privacyidea/private.pem.age and b/sso-mfa/bootstrap/secrets.enc/privacyidea/private.pem.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/privacyidea/public.pem.age b/sso-mfa/bootstrap/secrets.enc/privacyidea/public.pem.age index f56887b..87d33fe 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/privacyidea/public.pem.age and b/sso-mfa/bootstrap/secrets.enc/privacyidea/public.pem.age differ diff --git a/sso-mfa/bootstrap/secrets.enc/privacyidea/secrets.env.age b/sso-mfa/bootstrap/secrets.enc/privacyidea/secrets.env.age index b8a7e68..f28cbd5 100644 Binary files a/sso-mfa/bootstrap/secrets.enc/privacyidea/secrets.env.age and b/sso-mfa/bootstrap/secrets.enc/privacyidea/secrets.env.age differ diff --git a/sso-mfa/k8s/privacyidea/realm_bootstrap.py b/sso-mfa/k8s/privacyidea/realm_bootstrap.py index 960f9b5..ca13d4b 100644 --- a/sso-mfa/k8s/privacyidea/realm_bootstrap.py +++ b/sso-mfa/k8s/privacyidea/realm_bootstrap.py @@ -233,7 +233,7 @@ def main(): phase = 'functional-verification' from verify_mfa import verify proof = verify(base, args.verify_user, REALM, RESOLVER, admin, - lambda:getpass.getpass('Fresh MFA code (include token PIN if required): ')) + lambda:getpass.getpass('Fresh MFA code of %s (realm %s; token PIN first if set; not pi-admin): ' % (args.verify_user, REALM))) if proof['result'] != 'PASS': raise Refused() report['mfa_verified'] = True print(json.dumps(report, sort_keys=True)) diff --git a/sso-mfa/k8s/privacyidea/verify_mfa.py b/sso-mfa/k8s/privacyidea/verify_mfa.py index e5ff337..dcf0c19 100644 --- a/sso-mfa/k8s/privacyidea/verify_mfa.py +++ b/sso-mfa/k8s/privacyidea/verify_mfa.py @@ -116,7 +116,9 @@ def main(argv=None): args.realm, args.resolver, password, - lambda: getpass.getpass("Fresh MFA code (include token PIN if required): "), + lambda: getpass.getpass( + f"Fresh MFA code of {args.user} (realm {args.realm}; token PIN first if set; not pi-admin): " + ), ) except (EOFError, KeyboardInterrupt, ValueError): report = {"result": "FAIL", "phase": "protected-input"} diff --git a/workplans/NK-WP-0056-bounded-executor-path.md b/workplans/NK-WP-0056-bounded-executor-path.md index 49872de..ac54629 100644 --- a/workplans/NK-WP-0056-bounded-executor-path.md +++ b/workplans/NK-WP-0056-bounded-executor-path.md @@ -117,7 +117,7 @@ it. ```task id: NK-WP-0056-T04 -status: todo +status: done priority: high assignee: claude-code needs_human: true @@ -227,6 +227,14 @@ agents and writes KV v1 (CAS=0); verify proves the KV value current the same way role. `realm_bootstrap.py --rotate-bind --from-openbao` rewrites only `BINDPW` on the service bind from OpenBao (attended platform-admin) and requires `--verify-user`. Correction: `pi-admin` signs in with a password; the MFA proof is the `--verify-user` login through the resolver. + +**Done 2026-10-31.** `svc-privacyidea` live: CCR-2026-0055 approved; founder-run setup and verify passed (KV v1, value +proven current by LLDAP sign-in, no workload role) and `realm_bootstrap.py --rotate-bind --from-openbao` wrote the +resolver from OpenBao with an `admin` MFA proof ([evidence](../docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/README.md); +a first apply wrote the same value but its check got a code that was not `admin`'s; both MFA prompts now name the user). +All four service binds are held in OpenBao: `svc-provisioner`, `svc-authelia` and `svc-keycape` ESO-delivered, +`svc-privacyidea` by attended write-through (founder decision 2026-10-31 amends the done criterion accordingly). +No bind rotation needs the custody copy decrypted. The custody copy keeps the current values as a fallback. the env. ## Synthetic verification instead of a founder sign-in