NK-WP-0054: service identity policy plan; crisis custody list; NK-WP-0053-T04
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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-<service> 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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
This commit is contained in:
tegwick 2026-10-24 19:30:52 +02:00
parent 1ce56365b9
commit 034eacab9b
6 changed files with 280 additions and 0 deletions

View file

@ -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 |

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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-<service>` 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-<service>` for binds, `<service>-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.