From 034eacab9b3c15258e0a011bc342f1f19463419e Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 24 Oct 2026 19:30:52 +0200 Subject: [PATCH] NK-WP-0054: service identity policy plan; crisis custody list; NK-WP-0053-T04 The founder's three-identity model and the finding that the human admin is also the directory bind account lead to a proposed workplan: svc- bind accounts, per-service admin accounts, a governing standard and a conformance health check. docs/crisis-custody-list.md is the first concrete list of what unlocks recovery, with unknowns marked TO CONFIRM. NK-WP-0053-T04 extends the quarterly drill to the Authelia and by-user MFA stages with admin as operator. Hub decision routes the three founder gates. 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 | 9 + docs/crisis-custody-list.md | 56 ++++++ docs/disaster-recovery-storage-share.md | 5 + docs/identity-restore-drill.md | 1 + ...-WP-0053-identity-restore-drill-cadence.md | 26 +++ .../NK-WP-0054-service-identity-policy.md | 183 ++++++++++++++++++ 6 files changed, 280 insertions(+) create mode 100644 docs/crisis-custody-list.md create mode 100644 workplans/NK-WP-0054-service-identity-policy.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 1cb1824..b32304e 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -51,6 +51,7 @@ | workplan | NK-WP-0051 | finished | — | workplans/NK-WP-0051-sign-in-diagnosis.md | | workplan | NK-WP-0052 | finished | — | workplans/NK-WP-0052-wave2-resource-right-sizing.md | | workplan | NK-WP-0053 | active | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md | +| workplan | NK-WP-0054 | proposed | — | workplans/NK-WP-0054-service-identity-policy.md | | task | NK-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md | | task | NK-WP-ADHOC-2026-07-02-T02 | done | — | workplans/ADHOC-2026-07-02.md | | task | NK-WP-ADHOC-2026-08-14-T01 | done | — | workplans/ADHOC-2026-08-14.md | @@ -306,6 +307,14 @@ | task | NK-WP-0053-T01 | done | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md | | task | NK-WP-0053-T02 | wait | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md | | task | NK-WP-0053-T03 | wait | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md | +| task | NK-WP-0053-T04 | todo | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md | +| task | NK-WP-0054-T01 | todo | — | workplans/NK-WP-0054-service-identity-policy.md | +| task | NK-WP-0054-T02 | todo | — | workplans/NK-WP-0054-service-identity-policy.md | +| task | NK-WP-0054-T03 | 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-T06 | wait | — | workplans/NK-WP-0054-service-identity-policy.md | +| task | NK-WP-0054-T07 | todo | — | workplans/NK-WP-0054-service-identity-policy.md | | intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md | | intake | NET-IN-0001 | open | — | intakes/intakes.md | diff --git a/docs/crisis-custody-list.md b/docs/crisis-custody-list.md new file mode 100644 index 0000000..160f2f0 --- /dev/null +++ b/docs/crisis-custody-list.md @@ -0,0 +1,56 @@ +# Crisis custody list + +NK-WP-0054-T07. Drafted 2026-10-24 from repository documents and the founder's statements +that day. **This list holds no secret value, ever.** It names what unlocks recovery, who +holds it, where, and whether using it needs the very services being recovered. Anything +the repository cannot establish is marked `TO CONFIRM`; the founder confirms each one. + +It belongs with the backup documentation: +[disaster recovery guide](disaster-recovery-storage-share.md), +[identity restore drill](identity-restore-drill.md) and +[Credential Management Standard §4](../canon/standards/credential-management_v0.2.md). +That standard names the custody categories. This list makes them concrete. + +## The one rule + +For every row, answer: **does using this item work when SSO and OpenBao are down?** +A recovery path that is satisfied only by logging in to something being restored is a +circular dependency and is recorded as a gap, not as custody. + +## Roles used below + +| Role | Identity | Used for | +| --- | --- | --- | +| Crisis operator | `admin` | Runs recovery and routine validation (founder intent, 2026-10-24) | +| Authorizer | `platform-root` / king credential | Break-glass authority from offline custody; nothing routine | +| Developer | `tegwick` | Builds and tests tooling; holds no recovery keys | + +## Items + +| # | Item | Unlocks | Holder and location | Works with SSO and OpenBao down? | Last proven use | Gap or action | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | Custodian/backup age private key (recipient `age1zvry…50syr3tsz`, labelled `railiance-backup`) | Decrypts the identity backup bundle and the SOPS custody files | Founder, independently held password safe or offline packet (Standard §4) | Yes, offline | Entered by the founder at hidden prompts 2026-10-24 (LLDAP, Authelia, privacyIDEA stages) | `TO CONFIRM`: number of copies, who else could recover if the founder cannot | +| 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) | +| 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao | Holders `TO CONFIRM` (custody doc: escrow holders "to be named") | Yes, offline | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Name the three holders and where each share is kept; confirm none depends on SSO | +| 4 | `platform-root` / king account and factors | Final authority; break-glass | Independently held password safe or offline packet (Standard §4; custody doc) | **No if used through KeyCape**: its login and OTP are validated by the services being recovered | OTP validated against restored privacyIDEA 2026-10-24 (isolated) | `TO CONFIRM` the offline path that works with SSO down (OpenBao shares, item 3) and record it | +| 5 | Founder admission signing key (SSH, principal `bernd`) | Signs attempt and restore admissions | Founder, workstation key; verifier anchor `/etc/secrets-engine/admission/allowed_signers` | Yes | Signed the T40 admission 2026-10-23 | `TO CONFIRM` which identity this key belongs to; the anchor is workstation-bound, so a backup of key and anchor is needed | +| 6 | `admin` password and OTP token | Day-to-day platform administration and, per intent, recovery operation | Founder; OTP enrolled 2026-10-24 | **No**: login and token checks need KeyCape, LLDAP and privacyIDEA | Token not yet in any restored backup; first restore proof after the 2026-10-25 03:45 UTC capture | Same account as the directory bind used by Authelia and privacyIDEA until the split in NK-WP-0054-T05 | +| 7 | `pi-admin` credentials (privacyIDEA internal admin) | Token repair and re-enrolment | Founder's personal password safe (stated 2026-10-24) | Custody yes if the safe opens offline; use needs a running privacyIDEA | Not yet drilled | Confirm the safe is reachable offline and mirrored; add a pi-admin check to the drill | +| 8 | Ops bundle (age-encrypted tar of service secrets, `make creds-bundle`) | Point-in-time service secrets | Offsite location `TO CONFIRM` (Standard §7) | Yes, needs item 1 | Not drilled | Confirm location and age; decide whether it duplicates the identity bundle | +| 9 | Operator access to the production host or cluster for a real restore (SSH certificate or kubeconfig) | Applying a restore to production | `TO CONFIRM` | `TO CONFIRM`: ops-warden issues SSH certificates; whether that works with SSO down is unknown | Not drilled | Establish the crisis path and record it | + +## Non-secret recovery assets + +These need no custody but must be reachable: the published recovery kit on Storage Share +(guide, tools ZIP, checksum; verify the checksum against a trusted copy), the pinned +container images, and a trusted checkout of this repository. TLS for `kc.coulomb.social` +has no custody: it is reissued through ACME and needs working public DNS and HTTP-01. + +## Maintenance + +- Review at every restore drill (NK-WP-0053), and record the date each item was last + proven. An item never proven stays marked as such. +- Loss or suspected exposure of any item goes to its owner's rotation and revocation + process (Standard §6); this list rotates nothing. +- A machine-readable, non-secret record with a freshness check, modelled on + `tools/custody-record`, is proposed in NK-WP-0054-T07. Until then this document is the list. diff --git a/docs/disaster-recovery-storage-share.md b/docs/disaster-recovery-storage-share.md index 7926f0d..198b8f6 100644 --- a/docs/disaster-recovery-storage-share.md +++ b/docs/disaster-recovery-storage-share.md @@ -120,6 +120,11 @@ approve a production cutover, including target identity, rollback, DNS/ingress, current consumers, credential dispositions and monitoring. This guide does not authorize a production restore or automatic credential rotation. +## Who and what unlocks recovery + +The [crisis custody list](crisis-custody-list.md) names every item that unlocks recovery, who holds +it and whether it still works with SSO and OpenBao down. It holds no secret values. + ## Schedule, protection and limits Daily capture is scheduled at 03:45 UTC with up to five minutes jitter. Hourly diff --git a/docs/identity-restore-drill.md b/docs/identity-restore-drill.md index 4121d6d..0cb4142 100644 --- a/docs/identity-restore-drill.md +++ b/docs/identity-restore-drill.md @@ -26,6 +26,7 @@ effect). ## Before the drill +- Review the [crisis custody list](crisis-custody-list.md): every row must have a holder, a location and a last-proven date, and no row may rely on a login to a service being restored. The operator of record is `admin`. - Pick the generation from the daily backup (the newest that passed health). Record its date, not its content. - Stage the pinned images locally. Each launcher refuses if they are missing. diff --git a/workplans/NK-WP-0053-identity-restore-drill-cadence.md b/workplans/NK-WP-0053-identity-restore-drill-cadence.md index 5811875..e2053ad 100644 --- a/workplans/NK-WP-0053-identity-restore-drill-cadence.md +++ b/workplans/NK-WP-0053-identity-restore-drill-cadence.md @@ -74,6 +74,32 @@ silently. **Done when:** a dated, verified evidence set exists and the next due date is recorded. +## Extend the drill to Authelia and by-user MFA, operated by admin + +```task +id: NK-WP-0053-T04 +status: todo +priority: medium +assignee: claude-code +depends_on: [NK-WP-0053-T01] +``` + +The drill document was written before the Authelia stage (`run-isolated-authelia.sh`) and +the by-user MFA stage (`run-isolated-privacyidea-mfa.sh`) existed, and it lists retained-factor +MFA as out of scope. Add both stages to `docs/identity-restore-drill.md`, and name `admin` +as the operator of record and the MFA test subject (founder intent, 2026-10-24: backup +validation is platform operations, with `platform-root` kept for break-glass). Add the check +of `docs/crisis-custody-list.md` to the drill's preparation, and record in the evidence which +identity ran each stage. + +`admin@coulomb` received an OTP on 2026-10-24, so it is first present in the backup captured +after 2026-10-25 03:45 UTC. The first `admin`-operated MFA run must use a generation from +after that capture; an older generation holds no token for `admin` and proves nothing. + +**Done when:** the drill document lists all five stages with `admin` as operator, the first +`admin`-operated MFA run has passed on a post-enrolment generation, and its evidence names +the identity used. + ## Review the G1 residual-risk acceptance ```task diff --git a/workplans/NK-WP-0054-service-identity-policy.md b/workplans/NK-WP-0054-service-identity-policy.md new file mode 100644 index 0000000..b296958 --- /dev/null +++ b/workplans/NK-WP-0054-service-identity-policy.md @@ -0,0 +1,183 @@ +--- +id: NK-WP-0054 +type: workplan +title: "Service identity and privilege policy, conformance check and bind-account split" +domain: infotech +repo: net-kingdom +status: proposed +flavor: implementation +owner: claude +topic_slug: netkingdom +created: "2026-10-24" +updated: "2026-10-24" +related: + - NK-WP-0049 + - NK-WP-0053 + - NK-WP-0031 +--- + +# NK-WP-0054 — Service identity and privilege policy + +Intake from the founder's direction on 2026-10-24, during NK-WP-0049 backup validation. +Three identities exist today: `platform-root` (all-mighty build-mode access, not for +production use), `tegwick` (developer) and `admin` (platform administrator, intended +for operations and for backup validation and crisis recovery). Findings that led here: + +- The T19 MFA proof ran as `platform-root`, the identity meant to leave production use. +- The human `admin` is LLDAP `uid=admin`, which is also the directory bind account for + Authelia and the privacyIDEA resolver. One account has one password, so the human + password is the service secret held in Kubernetes Secrets and in every backup bundle. + An OTP on `admin` protects KeyCape logins but not the bind path. +- `pi-admin` already exists for privacyIDEA and is separate; its credentials are in the + founder's personal password safe. +- No concrete crisis custody list existed. A first draft is `docs/crisis-custody-list.md`. + +Direction: dedicated `svc-` bind accounts, a dedicated admin account per +service (`ld-admin`, `pi-admin`, …), a NetKingdom standard that governs which identities +and privileges may exist for the services that are part of NetKingdom, and a health check +that validates conformance. This plan prepares and proposes; it grants nothing and changes +no live service. IAM Profile v0.3 already distinguishes human, service and agent +principals in tokens; this standard governs the accounts behind them and must extend, +not contradict, that profile and Credential Management Standard v0.2. + +## Inventory current service identities and privileges + +```task +id: NK-WP-0054-T01 +status: todo +priority: high +assignee: claude-code +``` + +Read-only inventory from repository manifests, ConfigMaps and owner-provided metadata. +No Secret is read. For each NetKingdom service (LLDAP, Authelia, KeyCape, privacyIDEA, +OpenBao, user-engine, tenant-engine, flex-auth and the platform consumers) list every +identity: name, class (human, break-glass, service bind, service admin, workload), +privileges, the credential's custody location, its consumers and its owner. + +**Done when:** `docs/service-identity-inventory.md` lists every identity with its source, +and states plainly which entries are unknown and who must confirm them. + +## Draft the Service Identity and Privilege standard + +```task +id: NK-WP-0054-T02 +status: todo +priority: high +assignee: claude-code +depends_on: [NK-WP-0054-T01] +``` + +Write `canon/standards/service-identity-privilege_v0.1.md` as a proposed standard: +identity classes; naming (`svc-` for binds, `-admin` for admins); +the rules the founder's findings imply (one identity, one purpose; a bind account is +read-only and never a login; no human uses a service credential; humans and admins +require MFA; every identity has an owner, a custody class and a rotation path; the +break-glass identity is not routine); the exception record; and its relation to IAM +Profile v0.3, Credential Management v0.2 and the Security Zones standard. + +**Done when:** the draft is reviewed by the founder and the service owners it names. + +## Schema and offline conformance validator + +```task +id: NK-WP-0054-T03 +status: wait +priority: medium +assignee: claude-code +depends_on: [NK-WP-0054-T02] +blocking_reason: "Needs the reviewed T02 standard." +``` + +A declaration schema in `canon/schemas/` and an offline validator in +`tools/service-identity/`, in the style of `tools/tenancy-posture` and +`tools/custody-record`: each service declares its identities, the validator checks the +declaration against the standard, refuses secret-shaped values and reports counts only. + +**Done when:** valid and invalid fixtures pass and fail as expected, and the declarations +for the current services are checked in. + +## Live conformance health check + +```task +id: NK-WP-0054-T04 +status: wait +priority: medium +assignee: claude-code +depends_on: [NK-WP-0054-T03] +blocking_reason: "Needs the validator and owner agreement on what read-only metadata each service owner can provide." +``` + +Compare declared against observed: for example, which DN each consumer's configuration +binds as, which group the bind accounts belong to, and which policies the admin roles +carry. Observed state comes from owner-provided read-only metadata and repository +manifests, never from reading Secrets. Findings feed the existing proposal-only posture +feedback (`tools/posture-feedback`) with an evidence-freshness bound. It changes nothing. + +**Done when:** a dated run reports conformant, non-conformant and unknown per identity, +with each owner named for every finding. + +## Split the directory bind from the human admin + +```task +id: NK-WP-0054-T05 +status: wait +priority: high +assignee: claude-code +needs_human: true +decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca" +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." +``` + +Create `svc-authelia` and `svc-privacyidea` as read-only directory binds, switch the +Authelia and privacyIDEA resolver configurations to them, make `admin`'s password +human-only and rotate it, and remove the shared secret from the service Secrets. Update +the backup profile, the restore adapters (`isolated-lldap.py`, `isolated-authelia.py`, +`isolated-privacyidea-mfa.py`, which read the directory password from `lldap-secrets` and +rewrite the resolver address) and the restore packet. Prepare guarded helpers and +filled-in commands; the founder runs the live steps. + +**Done when:** both services bind with their own read-only accounts, the human `admin` +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. + +## Disposition of per-service admin accounts + +```task +id: NK-WP-0054-T06 +status: wait +priority: medium +assignee: claude-code +needs_human: true +decision_id: "9f0f7a87-7adb-44f6-8360-db6a80e3ceca" +depends_on: [NK-WP-0054-T02] +blocking_reason: "Founder decision per service: which dedicated admin account exists, its name and its custody." +``` + +For each service decide whether a dedicated admin account exists and what it is called: +LLDAP (`ld-admin`), privacyIDEA (`pi-admin`, existing), Authelia, KeyCape (client +registry), OpenBao (the `platform-admin` role) and others. Record the result in the +inventory and the standard. LLDAP's built-in admin name is configurable but renaming it +on an existing directory is untested here, so it needs a rehearsal before any decision. + +**Done when:** every service has a recorded disposition and a custody location. + +## Maintain the crisis custody list + +```task +id: NK-WP-0054-T07 +status: todo +priority: high +assignee: claude-code +needs_human: true +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." +``` + +Keep `docs/crisis-custody-list.md` current, and propose a non-secret machine-readable +record with a freshness check, modelled on `tools/custody-record`, so a drill can verify +that each row has a holder, a location and a last-proven date. + +**Done when:** no row is marked `TO CONFIRM`, the freshness check exists and the first +`admin`-operated drill (NK-WP-0053) has verified the list.