2026-10-24 19:30:52 +02:00
---
id: NK-WP-0054
type: workplan
title: "Service identity and privilege policy, conformance check and bind-account split"
domain: infotech
repo: net-kingdom
2026-10-24 20:06:15 +02:00
status: active
2026-10-24 19:30:52 +02:00
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
2026-10-24 19:32:22 +02:00
state_hub_workstream_id: "36f61e43-f1f3-53fc-ad77-d1d797b3d5b1"
2026-10-24 19:30:52 +02:00
---
# 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
2026-10-24 19:58:23 +02:00
status: done
2026-10-24 19:30:52 +02:00
priority: high
assignee: claude-code
2026-10-24 19:32:22 +02:00
state_hub_task_id: "422918a5-c695-5fe3-ba3c-2b370b2ef4dc"
2026-10-24 19:30:52 +02:00
```
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.
2026-10-24 19:58:23 +02:00
Completed 2026-10-24. `docs/service-identity-inventory.md` covers people, LLDAP, Authelia, KeyCape,
privacyIDEA, the identity-provisioner and engines, and OpenBao (railiance-platform files read
read-only). No Secret or live service was read. It ends with a table of what is unknown and who
confirms it. Main finding: the `admin` credential has **five** consumers (the human, Authelia,
KeyCape, the privacyIDEA resolver and the identity-provisioner), so T05 needs four service
accounts, not two. Also recorded: the provisioner needs more than read access, `trigger-admin`
is an uninventoried legacy admin, and OpenBao `platform-admin` binds on a group claim alone
(a question for T02, not a defect).
2026-10-24 19:30:52 +02:00
## Draft the Service Identity and Privilege standard
```task
id: NK-WP-0054-T02
NK-WP-0054-T02: draft-1 of the Service Identity and Privilege standard
Thirteen rules (one identity one class, people and services never share a
credential, read-only per-service bind identities, one service-admin per
service, break-glass is not routine, assurance on privileged roles, custody
class, declaration, recovery, validation run by the operator identity), naming
svc-<service> and <short>-admin, five initial exceptions and six open questions.
Proposed; extends IAM Profile v0.3 and Credential Management v0.2. Hub decision
routes the review.
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
2026-10-24 20:02:30 +02:00
status: wait
2026-10-24 19:30:52 +02:00
priority: high
assignee: claude-code
NK-WP-0054-T02: draft-1 of the Service Identity and Privilege standard
Thirteen rules (one identity one class, people and services never share a
credential, read-only per-service bind identities, one service-admin per
service, break-glass is not routine, assurance on privileged roles, custody
class, declaration, recovery, validation run by the operator identity), naming
svc-<service> and <short>-admin, five initial exceptions and six open questions.
Proposed; extends IAM Profile v0.3 and Credential Management v0.2. Hub decision
routes the review.
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
2026-10-24 20:02:30 +02:00
needs_human: true
decision_id: "a3582e79-4ece-4902-9bfa-e962aef2b8f2"
2026-10-24 19:30:52 +02:00
depends_on: [NK-WP-0054-T01]
2026-10-24 22:50:01 +02:00
blocking_reason: "Draft-2 reflects the founder review of 2026-10-24 (four decisions). Await ratification and review by the service owners the inventory names."
2026-10-24 19:32:22 +02:00
state_hub_task_id: "5830e2eb-334a-52fa-9c55-a8c00d89c09f"
2026-10-24 19:30:52 +02:00
```
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.
2026-10-24 22:50:01 +02:00
Draft-2 (2026-10-24, after the founder's review): decisions applied are OpenBao `platform-admin` binds
`aal2` ; the human administrator keeps directory admin rights through an admin group; declarations start here
and move to the services; agents get their own class and rule SI-14. Rehearsals still to do (not decisions):
2026-10-24 22:57:23 +02:00
introducing `ld-admin` (LLDAP only) as the configured super-admin and demoting `admin` , the provisioner's narrowest scope, and whether a bind account can be kept
2026-10-24 22:50:01 +02:00
from signing in interactively. Finding F-1: the coding-agent role in OpenBao presents as a service against
IAM Profile v0.3. Draft-1 was delivered earlier the same day: `canon/standards/service-identity-privilege_v0.1.md` (proposed). Thirteen
NK-WP-0054-T02: draft-1 of the Service Identity and Privilege standard
Thirteen rules (one identity one class, people and services never share a
credential, read-only per-service bind identities, one service-admin per
service, break-glass is not routine, assurance on privileged roles, custody
class, declaration, recovery, validation run by the operator identity), naming
svc-<service> and <short>-admin, five initial exceptions and six open questions.
Proposed; extends IAM Profile v0.3 and Credential Management v0.2. Hub decision
routes the review.
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
2026-10-24 20:02:30 +02:00
rules (SI-1 to SI-13), naming, a target shape for today's services, the declaration fields, the
conformance result states, five initial exceptions (SI-X1 to SI-X5) and six open questions. It
extends IAM Profile v0.3 and Credential Management v0.2 without changing either. Review is the
founder's, then the service owners named in the inventory.
2026-10-24 19:30:52 +02:00
## Schema and offline conformance validator
```task
id: NK-WP-0054-T03
NK-WP-0054-T03: service identity schema, offline validator and first declarations
Schema, validator (30 tests), make targets and eight declarations authored from the
inventory, all unconfirmed by their owners. First run: 0 non-conformant, 12 unknown,
2 excepted, 5 planned, 6 conformant. Provisional: built against draft-1 before the
founder's review. Building it changed the draft: custody is a list, SI-X1 now covers
SI-9 and NAMING, and the reserved-name rule applies only to credential-holding
identities (open question 7). Unknown is a finding for every class.
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
2026-10-24 22:38:23 +02:00
status: done
2026-10-24 19:30:52 +02:00
priority: medium
assignee: claude-code
depends_on: [NK-WP-0054-T02]
2026-10-24 19:32:22 +02:00
state_hub_task_id: "a5c51d69-28ff-5458-83eb-10a35c8c5492"
2026-10-24 19:30:52 +02:00
```
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.
NK-WP-0054-T03: service identity schema, offline validator and first declarations
Schema, validator (30 tests), make targets and eight declarations authored from the
inventory, all unconfirmed by their owners. First run: 0 non-conformant, 12 unknown,
2 excepted, 5 planned, 6 conformant. Provisional: built against draft-1 before the
founder's review. Building it changed the draft: custody is a list, SI-X1 now covers
SI-9 and NAMING, and the reserved-name rule applies only to credential-holding
identities (open question 7). Unknown is a finding for every class.
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
2026-10-24 22:38:23 +02:00
Completed 2026-10-24, **provisionally** : built against draft-1 before the founder's review, so a rule
change in the standard changes the tool. `canon/schemas/service-identity_v0.1.schema.json` ;
`tools/service-identity/validate.py` (30 tests); `make service-identity-test` and
`make service-identity-check` ; eight declarations in `tools/service-identity/declarations/` , authored from
the inventory and all marked `owner_confirmed: false` . First run as of 2026-10-24: 0 non-conformant, 12
unknown, 2 excepted, 5 planned, 6 conformant. Unknown outnumbers conformant because the repository
cannot establish privileges for the KeyCape clients and others; that is the honest result, not a pass.
Building it changed the draft in three ways: custody became a list (the `admin` password lives in a
Secret and a person's safe), the SI-X1 exception grew to include SI-9 and NAMING, and the reserved-name
rule was scoped to credential-holding identities (standard section 10, question 7). SI-4, SI-5, SI-7,
SI-10 and SI-13 are not decidable from a declaration.
2026-10-24 19:30:52 +02:00
## Live conformance health check
```task
id: NK-WP-0054-T04
status: wait
priority: medium
assignee: claude-code
depends_on: [NK-WP-0054-T03]
NK-WP-0054-T03: service identity schema, offline validator and first declarations
Schema, validator (30 tests), make targets and eight declarations authored from the
inventory, all unconfirmed by their owners. First run: 0 non-conformant, 12 unknown,
2 excepted, 5 planned, 6 conformant. Provisional: built against draft-1 before the
founder's review. Building it changed the draft: custody is a list, SI-X1 now covers
SI-9 and NAMING, and the reserved-name rule applies only to credential-holding
identities (open question 7). Unknown is a finding for every class.
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
2026-10-24 22:38:23 +02:00
blocking_reason: "Validator delivered (T03, provisional). Await the T02 review and owner agreement on what read-only metadata each service can provide."
2026-10-24 19:32:22 +02:00
state_hub_task_id: "c8db4607-9306-572b-a3cb-c35ca5989ec7"
2026-10-24 19:30:52 +02:00
```
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."
2026-10-24 19:32:22 +02:00
state_hub_task_id: "c43d339a-fc8a-5526-885a-64693c930005"
2026-10-24 19:30:52 +02:00
```
2026-10-24 19:58:23 +02:00
Create `svc-authelia` , `svc-keycape` and `svc-privacyidea` as read-only directory binds and a
narrowly scoped provisioner account that can create users and groups without being the
directory super-admin (the inventory found these four consumers beside the human; the
narrowest working privilege for the provisioner must be tested against the pinned LLDAP image
2026-10-24 22:57:23 +02:00
first). Per the founder's decisions the human `admin` keeps its name and its administrative rights through an admin
group on its own password and OTP, and `ld-admin` is LLDAP's configured super-admin only. The migration
therefore introduces `ld-admin` by configuration on the existing directory and demotes `admin` ; this is
rehearsed in isolation before any change. Switch the Authelia, KeyCape and privacyIDEA resolver configurations and the
2026-10-24 19:58:23 +02:00
provisioner to them, make `admin` 's password
2026-10-24 19:30:52 +02:00
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."
2026-10-24 19:32:22 +02:00
state_hub_task_id: "8072b5cf-4161-58b9-8dfc-62fa102ccd16"
2026-10-24 19:30:52 +02:00
```
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
2026-10-24 22:57:23 +02:00
inventory and the standard. `ld-admin` is for LLDAP only; `admin` stays the human's name on every service. Introducing `ld-admin` as the
configured super-admin on an existing directory is untested here, so it needs a rehearsal first. Also rehearse
2026-10-24 22:50:01 +02:00
whether a read-only bind account can be kept from signing in interactively (SI-3).
2026-10-24 19:30:52 +02:00
**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."
2026-10-24 19:32:22 +02:00
state_hub_task_id: "ae8524ff-12d8-59c2-b46b-88f3bfd44dd8"
2026-10-24 19:30:52 +02:00
```
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.