NK-WP-0054: founder decision 9f0f7a87 - split goes live this week, admin dispositions, crisis list confirmed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
This commit is contained in:
tegwick 2026-10-28 18:53:10 +01:00
parent 417d88d80e
commit dcb5bc2540
4 changed files with 36 additions and 12 deletions

View file

@ -314,9 +314,9 @@
| task | NK-WP-0054-T02 | done | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T02 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T03 | done | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T03 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T04 | wait | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T04 | wait | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T05 | wait | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T05 | progress | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T06 | wait | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T06 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T07 | todo | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T07 | progress | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T08 | done | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-0054-T08 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0055-T01 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md | | task | NK-WP-0055-T01 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T02 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md | | task | NK-WP-0055-T02 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |

View file

@ -116,6 +116,18 @@ The founder's model, written in canon terms. This is what the declarations must
| provisioner account | `SERVICE` | `ADMINISTER @ directory/people,groups`, narrowest working scope | `STANDING` | `PRODUCTION` | | provisioner account | `SERVICE` | `ADMINISTER @ directory/people,groups`, narrowest working scope | `STANDING` | `PRODUCTION` |
| Claude Code, Codex, Grok sessions | `AGENT` | what the tools they can reach allow (SI-14) | per task | that of the person they act for | | Claude Code, Codex, Grok sessions | `AGENT` | what the tools they can reach allow (SI-14) | per task | that of the person they act for |
Per-service admin disposition (founder decision `9f0f7a87`, 2026-10-28, NK-WP-0054-T06). A service with no
local admin plane has no service-admin account; SI-4 applies where one exists.
| Service | Service-admin account | Custody | Day-to-day administration |
| --- | --- | --- | --- |
| LLDAP | `ld-admin`, configured super-admin (introduction rehearsed in isolation first) | Founder's password safe; crisis list | `admin` through an admin group |
| privacyIDEA | `pi-admin` (exists; SI-X5) | Founder's replicated browser password safe; crisis item 7 | `pi-admin` until an attributed admin path exists |
| Authelia | none: administered only through reviewed configuration | not applicable | reviewed config change |
| KeyCape | none: client registry is configuration; `/platform/authentication-policy` needs role `platform-operator` at `aal2` | not applicable | `admin` with `platform-operator` |
| OpenBao | none locally: federated `platform-admin` role (`aal2`); root only through the `generate-root` ceremony | unseal shares, crisis item 3 | `admin` via `platform-admin` |
| PostgreSQL | CNPG `postgres` superuser | its cluster Secret, used only through railiance-platform | railiance-platform owner operations |
`backup operator → OPERATE @ backup-system` and `developer → CONSTRUCT @ product` are the canon's own `backup operator → OPERATE @ backup-system` and `developer → CONSTRUCT @ product` are the canon's own
translations, and match the founder's intent that `admin` operates backups and `tegwick` builds. translations, and match the founder's intent that `admin` operates backups and `tegwick` builds.

View file

@ -29,9 +29,9 @@ circular dependency and is recorded as a gap, not as custody.
| # | Item | Unlocks | Holder and location | Works with SSO and OpenBao down? | Last proven use | Gap or action | | # | Item | Unlocks | Holder and location | Works with SSO and OpenBao down? | Last proven use | Gap or action |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| 1 | **`railiance-backup` age private key** (recipient `age1zvry…50syr3tsz`). Not the custodian key (item 1b) | Decrypts the identity backup bundle and the break-glass copy on Storage Share (`net-kingdom-kingpath-*.tar.age`) | Founder, password safe (stored as `railiance-backup.key`) | Yes, offline | Identity stages 2026-10-24; king-path verify drill 2026-10-27 (the helper refused the custodian key three times before the correct key was entered, as designed) | `TO CONFIRM`: number of copies, who else could recover if the founder cannot | | 1 | **`railiance-backup` age private key** (recipient `age1zvry…50syr3tsz`). Not the custodian key (item 1b) | Decrypts the identity backup bundle and the break-glass copy on Storage Share (`net-kingdom-kingpath-*.tar.age`) | Two copies: founder's password safe (stored as `railiance-backup.key`) and a copy held by a named trusted person, who can recover if the founder cannot (founder, 2026-10-28) | Yes, offline | Identity stages 2026-10-24; king-path verify drill 2026-10-27 (the helper refused the custodian key three times before the correct key was entered, as designed) | Successor copy is not drill-proven; prove it once with `kingpath-offsite.py --verify` run by or with the successor |
| 1b | **Custodian age private key** (recipient `age1aq8t…qdepsh4`, `keys/age.pub` and `.sops.yaml`). Not the backup key | Decrypts the repository's SOPS custody files and greenfield bootstrap bundles (`docs/security-bootstrap-age-custody.md`) | Founder, password safe (stored as the custodian age private key) | Yes, offline | `TO CONFIRM` last proven use | Labels in the safe should name the recipient prefix so the two keys cannot be confused | | 1b | **Custodian age private key** (recipient `age1aq8t…qdepsh4`, `keys/age.pub` and `.sops.yaml`). Not the backup key | Decrypts the repository's SOPS custody files and greenfield bootstrap bundles (`docs/security-bootstrap-age-custody.md`) | Founder, password safe (stored as the custodian age private key) | Yes, offline | 2026-10, bootstrap/SOPS work (founder, 2026-10-28; not yet receipt-proven: run `make sops-custody-check`) | Labels in the safe should name the recipient prefix so the two keys cannot be confused |
| 2 | Storage Share `Backup` account app password | Downloads the protected ciphertext without OpenBao or SSO | Founder, independent custody `TO CONFIRM` | Yes, provider is outside the platform | Fresh retrieval 2026-10-24 | One provider copy only; residual risk accepted until 2027-01-31 (G1, NK-WP-0053) | | 2 | Storage Share `Backup` account app password | Downloads the protected ciphertext without OpenBao or SSO | Founder, in the independent password safe (founder, 2026-10-28) | Yes, provider is outside the platform | Fresh retrieval 2026-10-24 | One provider copy only; residual risk accepted until 2027-01-31 (G1, NK-WP-0053) |
| 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao; with the shares, `generate-root` mints a root token for break-glass (ceremony in `railiance-platform/docs/openbao.md`; production loopback listener 8210 live since 2026-10-28) | Founder as `platform-root` holds all three shares (single-holder custody: accepted exception, hub decision `37464824`, 2026-10-27), in the founder's replicated browser password safe under the `kingpath.credentials` pseudo-URL entry (confirmed 2026-10-27) | Yes: the safe is independent of NetKingdom SSO and OpenBao; it depends on the browser vendor's sync account and its own factor | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Single holder = single point of loss (reviewed at NK-WP-0055-T09). The founder holds the initial root token offline; the king-path drill (2026-10-28) found it **valid** as of the 2026-10-14 snapshot (root policy, no TTL, orphan). Founder decision `7ce684ec` (2026-10-28): stays escrowed during builder mode, revoked at production entry (NK-WP-0055-T16) | | 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao; with the shares, `generate-root` mints a root token for break-glass (ceremony in `railiance-platform/docs/openbao.md`; production loopback listener 8210 live since 2026-10-28) | Founder as `platform-root` holds all three shares (single-holder custody: accepted exception, hub decision `37464824`, 2026-10-27), in the founder's replicated browser password safe under the `kingpath.credentials` pseudo-URL entry (confirmed 2026-10-27) | Yes: the safe is independent of NetKingdom SSO and OpenBao; it depends on the browser vendor's sync account and its own factor | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Single holder = single point of loss (reviewed at NK-WP-0055-T09). The founder holds the initial root token offline; the king-path drill (2026-10-28) found it **valid** as of the 2026-10-14 snapshot (root policy, no TTL, orphan). Founder decision `7ce684ec` (2026-10-28): stays escrowed during builder mode, revoked at production entry (NK-WP-0055-T16) |
| 4 | `platform-root` / king account and factors | Final authority; break-glass | Independently held password safe (kingpath entry) | **No if used through KeyCape**. The king path with SSO down is offline material instead: unseal shares (item 3) plus the `generate-root` ceremony on the loopback listener, the admin kubeconfig and static tunnel key (item 9, also on Storage Share), the Host Europe console, and the admission signing key (item 5) | OTP validated against restored privacyIDEA 2026-10-24 (isolated). King path drilled 2026-10-28: Storage Share restore of item 9, `generate-root` on an isolated restore, read-only production SSO-down walk ([record](evidence/2026-10-28-king-path-drill.md)) | Not drilled: using the restored item-9 material against the cluster, and the Host Europe console. Both belong in the recurring drill (NK-WP-0053) | | 4 | `platform-root` / king account and factors | Final authority; break-glass | Independently held password safe (kingpath entry) | **No if used through KeyCape**. The king path with SSO down is offline material instead: unseal shares (item 3) plus the `generate-root` ceremony on the loopback listener, the admin kubeconfig and static tunnel key (item 9, also on Storage Share), the Host Europe console, and the admission signing key (item 5) | OTP validated against restored privacyIDEA 2026-10-24 (isolated). King path drilled 2026-10-28: Storage Share restore of item 9, `generate-root` on an isolated restore, read-only production SSO-down walk ([record](evidence/2026-10-28-king-path-drill.md)) | Not drilled: using the restored item-9 material against the cluster, and the Host Europe console. Both belong in the recurring drill (NK-WP-0053) |
| 5 | Founder admission signing key (SSH ed25519, principal `bernd`, fingerprint `SHA256:NekDABfw8K8IBYznjREg7jPpcK/BzCgO1pH1ebT/FXM`) | Signs restore and drill admissions for the OpenBao isolated restore (namespace `railiance-openbao-restore-attempt`) and secrets-engine admissions | Private key: founder workstation `~/.ssh/gexec_approver_ed25519` (passphrase-protected; signed the T40, `-02` and `-03` admissions); founder decision 2026-10-28: also keep it in the password safe under the kingpath entry, **not** in the Storage Share bundle, so the backup age key alone cannot admit a restore. Public anchor (non-secret, needed on any recovery workstation): `/etc/railiance-openbao-restore/allowed_signers` and `/etc/secrets-engine/admission/allowed_signers`, one line each: `bernd namespaces="railiance-openbao-restore-attempt" ssh-ed25519 ssh-ed25519` ; the secrets-engine file uses namespace `secrets-engine-recovery-attempt` | Yes, offline | Signed the T40 admission 2026-10-23 | Private key stored in the password safe kingpath entry (founder, 2026-10-28). Re-create both anchor files from this row on a recovery workstation. The file name says `gexec_approver`, but the key's role is admission signing | | 5 | Founder admission signing key (SSH ed25519, principal `bernd`, fingerprint `SHA256:NekDABfw8K8IBYznjREg7jPpcK/BzCgO1pH1ebT/FXM`) | Signs restore and drill admissions for the OpenBao isolated restore (namespace `railiance-openbao-restore-attempt`) and secrets-engine admissions | Private key: founder workstation `~/.ssh/gexec_approver_ed25519` (passphrase-protected; signed the T40, `-02` and `-03` admissions); founder decision 2026-10-28: also keep it in the password safe under the kingpath entry, **not** in the Storage Share bundle, so the backup age key alone cannot admit a restore. Public anchor (non-secret, needed on any recovery workstation): `/etc/railiance-openbao-restore/allowed_signers` and `/etc/secrets-engine/admission/allowed_signers`, one line each: `bernd namespaces="railiance-openbao-restore-attempt" ssh-ed25519 ssh-ed25519` ; the secrets-engine file uses namespace `secrets-engine-recovery-attempt` | Yes, offline | Signed the T40 admission 2026-10-23 | Private key stored in the password safe kingpath entry (founder, 2026-10-28). Re-create both anchor files from this row on a recovery workstation. The file name says `gexec_approver`, but the key's role is admission signing |

View file

@ -229,13 +229,12 @@ The founder's live export commands are in RPF-WP-0067-T03.
```task ```task
id: NK-WP-0054-T05 id: NK-WP-0054-T05
status: wait status: progress
priority: high priority: high
assignee: claude-code assignee: claude-code
needs_human: true needs_human: true
decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca" decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca"
depends_on: [NK-WP-0054-T01] depends_on: [NK-WP-0054-T01]
blocking_reason: "Production change across LLDAP, Authelia, privacyIDEA and their Secrets; needs the founder's go and the platform owners. The founder runs live Secret and rollout steps."
state_hub_task_id: "c43d339a-fc8a-5526-885a-64693c930005" state_hub_task_id: "c43d339a-fc8a-5526-885a-64693c930005"
``` ```
@ -258,17 +257,24 @@ filled-in commands; the founder runs the live steps.
password is not present in any service Secret, a fresh backup restores and passes the T17 password is not present in any service Secret, a fresh backup restores and passes the T17
and T19 drills under the new accounts, and the old shared credential is retired. and T19 drills under the new accounts, and the old shared credential is retired.
**2026-10-28 founder decision `9f0f7a87`: go, including the live run this week.** Order: guarded
helpers and filled-in commands; an isolated rehearsal on a restored copy of the directory covering
the `ld-admin` introduction, demotion of `admin` to an admin group, the narrowest provisioner
privilege on the pinned LLDAP image, and SI-3 (can a `svc-*` bind be kept from interactive login);
then the founder runs the live steps as soon as the rehearsal passes. KeyCape's bind is confirmed
as the directory admin from source (key-cape `a1ab8ff`, KEY-WP-0045-T03). The rotation lane is
`lldap-directory-bind` / `lldap-service-binds` in `tools/rotation-lanes/lanes.yaml`.
## Disposition of per-service admin accounts ## Disposition of per-service admin accounts
```task ```task
id: NK-WP-0054-T06 id: NK-WP-0054-T06
status: wait status: done
priority: medium priority: medium
assignee: claude-code assignee: claude-code
needs_human: true needs_human: true
decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca" decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca"
depends_on: [NK-WP-0054-T02] depends_on: [NK-WP-0054-T02]
blocking_reason: "Founder decision per service: which dedicated admin account exists, its name and its custody."
state_hub_task_id: "8072b5cf-4161-58b9-8dfc-62fa102ccd16" state_hub_task_id: "8072b5cf-4161-58b9-8dfc-62fa102ccd16"
``` ```
@ -281,16 +287,22 @@ whether a read-only bind account can be kept from signing in interactively (SI-3
**Done when:** every service has a recorded disposition and a custody location. **Done when:** every service has a recorded disposition and a custody location.
**Done 2026-10-28, founder decision `9f0f7a87`:** dispositions accepted as proposed and recorded in
the standard (section "Per-service admin disposition"): LLDAP `ld-admin`; privacyIDEA `pi-admin`;
Authelia and KeyCape none (configuration-only, no local admin plane); OpenBao federated
`platform-admin` with root only via `generate-root`; PostgreSQL the CNPG `postgres` superuser through
railiance-platform. The `ld-admin` introduction and the SI-3 check are rehearsed under T05.
## Maintain the crisis custody list ## Maintain the crisis custody list
```task ```task
id: NK-WP-0054-T07 id: NK-WP-0054-T07
status: todo status: progress
priority: high priority: high
assignee: claude-code assignee: claude-code
needs_human: true needs_human: true
decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca" decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca"
blocking_reason: "The founder confirms every TO CONFIRM row in docs/crisis-custody-list.md: share holders, copies of the age key, the crisis path to production, bundle location." blocking_reason: "No TO CONFIRM row remains (2026-10-28). Open: the machine-readable freshness check and the first admin-operated drill (NK-WP-0053) verifying the list."
state_hub_task_id: "ae8524ff-12d8-59c2-b46b-88f3bfd44dd8" state_hub_task_id: "ae8524ff-12d8-59c2-b46b-88f3bfd44dd8"
``` ```