2026-10-08 22:17:03 +02:00
|
|
|
---
|
|
|
|
|
id: NK-WP-0048
|
|
|
|
|
type: workplan
|
|
|
|
|
title: "Establish SecurityCaseTypes so that known security cases are light"
|
|
|
|
|
domain: infotech
|
|
|
|
|
repo: net-kingdom
|
2026-10-08 22:22:13 +02:00
|
|
|
status: active
|
2026-10-08 22:17:03 +02:00
|
|
|
flavor: implementation
|
|
|
|
|
owner: claude-code
|
|
|
|
|
topic_slug: netkingdom
|
|
|
|
|
created: "2026-10-08"
|
|
|
|
|
updated: "2026-10-08"
|
|
|
|
|
depends_on: []
|
|
|
|
|
related_repos:
|
|
|
|
|
- ops-mason
|
|
|
|
|
- ops-warden
|
|
|
|
|
- key-cape
|
|
|
|
|
- railiance-platform
|
|
|
|
|
- informed-decision
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
|
2026-10-08 22:17:03 +02:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
|
|
|
|
|
|
|
|
|
|
## Why
|
|
|
|
|
|
|
|
|
|
The founder's direction (2026-10-08): setting up a security integration of a
|
|
|
|
|
kind that has been done before should be easy, meaning accept the case, then
|
|
|
|
|
ops-mason arranges it and ops-warden follows through. Setting up a new kind
|
|
|
|
|
needs ample documentation and tests and an informed-decision, because its shape
|
|
|
|
|
is part of the security architecture.
|
|
|
|
|
|
|
|
|
|
Today every integration is hand-built: per-client helper scripts, per-lane
|
|
|
|
|
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
|
|
|
|
|
client), whose next step was going to be yet another hand-written rollout
|
|
|
|
|
script.
|
|
|
|
|
|
|
|
|
|
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
|
|
|
|
|
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
|
|
|
|
|
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
|
|
|
|
|
`canon/standards/security-case-types_v0.1.md` (proposed).
|
|
|
|
|
|
|
|
|
|
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
|
|
|
|
|
`WARDEN-WP-0044` (follow-through).
|
|
|
|
|
|
|
|
|
|
## Tasks
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T01
|
2026-10-08 22:29:13 +02:00
|
|
|
status: done
|
2026-10-08 22:17:03 +02:00
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T01 — Accept the terminology and the canon
|
|
|
|
|
|
|
|
|
|
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
|
|
|
|
|
section 10 open questions. On acceptance the canon status goes from `proposed`
|
|
|
|
|
to `accepted`.
|
|
|
|
|
|
2026-10-08 22:29:13 +02:00
|
|
|
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
|
|
|
|
|
section 10's open questions are carried forward to the first type (T04, T05).
|
|
|
|
|
|
2026-10-08 22:17:03 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T02
|
2026-10-08 22:21:40 +02:00
|
|
|
status: done
|
2026-10-08 22:17:03 +02:00
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T02 — Inventory the de-facto types already built by hand
|
|
|
|
|
|
|
|
|
|
Read-only. List every security integration built so far and group them by shape.
|
|
|
|
|
Known starting points:
|
|
|
|
|
|
|
|
|
|
- browser OIDC relying parties on key-cape: informed-decision-approver,
|
|
|
|
|
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
|
|
|
|
|
- confidential service clients with `client_credentials`: secrets-engine
|
|
|
|
|
approval, requester and openbao clients, approval-engine operator
|
|
|
|
|
- workload KV read lanes on OpenBao: the railiance-platform credential lane
|
|
|
|
|
designs and CCRs, for example `railiance-backup-offsite-lane`
|
|
|
|
|
- short-lived SSH certificates through ops-warden (`warden sign`)
|
|
|
|
|
- tunnelled operator access (ops-bridge)
|
|
|
|
|
|
|
|
|
|
For each: the repositories involved, the hand-built steps, attended human steps,
|
|
|
|
|
custody paths, verification evidence, and rollback (if any). Output
|
|
|
|
|
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
|
|
|
|
|
shape recurs and how much manual work each instance costs today.
|
|
|
|
|
|
2026-10-08 22:21:40 +02:00
|
|
|
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
|
|
|
|
|
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
|
|
|
|
|
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
|
|
|
|
|
rollout script. Confidential service clients need a registration plus two or
|
|
|
|
|
three KV-read CCRs each. A forge read identity is needed in bulk by
|
|
|
|
|
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
|
|
|
|
|
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
|
|
|
|
|
|
2026-10-08 22:17:03 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T03
|
2026-10-08 22:29:13 +02:00
|
|
|
status: done
|
2026-10-08 22:17:03 +02:00
|
|
|
depends_on: [NK-WP-0048-T01]
|
|
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T03 — Schema, template and validator
|
|
|
|
|
|
|
|
|
|
- `canon/schemas/security-case-type_v0.1.schema.json` and
|
|
|
|
|
`security-case_v0.1.schema.json`
|
|
|
|
|
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
|
|
|
|
|
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
|
|
|
|
|
checks a case against its type, and renders the case into the composer's
|
|
|
|
|
deterministic plan (still `execution.permitted: false`)
|
|
|
|
|
|
|
|
|
|
Acceptance: the validator rejects missing sections, secret-shaped parameter
|
|
|
|
|
values, unpinned providers and cases against non-`implemented` types, with tests
|
|
|
|
|
for each.
|
|
|
|
|
|
2026-10-08 22:29:13 +02:00
|
|
|
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
|
|
|
|
|
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
|
|
|
|
|
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
|
|
|
|
|
`security-case-types/_template/` (a valid, runnable example type over
|
|
|
|
|
`net-kingdom.local-identity`, with README sections, an example case and tests that
|
|
|
|
|
travel with every copy). 31 tests pass, including the composer's existing suite.
|
|
|
|
|
|
2026-10-08 22:17:03 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T04
|
2026-10-08 22:45:31 +02:00
|
|
|
status: done
|
|
|
|
|
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
|
2026-10-08 22:17:03 +02:00
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T04 — Define the first type: browser OIDC relying party
|
|
|
|
|
|
|
|
|
|
`security-case-types/oidc-browser-relying-party/`, version 1.0:
|
|
|
|
|
|
|
|
|
|
- parameters: client id, display name, exact redirect URIs, scopes, client type
|
|
|
|
|
(confidential or public), MFA pin, the groups the consuming service maps,
|
|
|
|
|
custody path for the secret (confidential only), consuming service and its
|
|
|
|
|
owner
|
|
|
|
|
- composition: key-cape client registration (key-cape declares it as a playbook
|
|
|
|
|
capability), secret minting into custody (railiance-platform), LLDAP group
|
|
|
|
|
existence and membership (net-kingdom identity-provisioner), handover to the
|
|
|
|
|
consuming service
|
|
|
|
|
- materialization (ops-mason) and follow-through (ops-warden) sections
|
|
|
|
|
- verification: discovery lists the redirect URI; a test sign-in returns the
|
|
|
|
|
expected claims for one human; anonymous access to the custody path is denied
|
|
|
|
|
- rollback: remove exactly this client and its custody path
|
|
|
|
|
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
|
|
|
|
|
still need an attended login and how that is minimised
|
|
|
|
|
- reference instances: informed-decision-approver, coulomb-social, user-engine
|
|
|
|
|
portal, openbao-admin
|
|
|
|
|
|
|
|
|
|
Acceptance: complete per canon section 3, tests green, status `proposed`.
|
|
|
|
|
|
2026-10-08 22:45:31 +02:00
|
|
|
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
|
|
|
|
|
`type.yaml` with 12 parameters (the custody reference is required only for
|
|
|
|
|
confidential clients), composition over key-cape's new
|
|
|
|
|
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
|
|
|
|
|
planned`), materialization and follow-through, verification and rollback, all
|
|
|
|
|
conditional on client type where it matters. One attended step,
|
|
|
|
|
`register-client`, justified by the live registry being a Kubernetes Secret.
|
|
|
|
|
README with threat model, alternatives, an honest fit table (portal and vergabe
|
|
|
|
|
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
|
|
|
|
|
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
|
|
|
|
|
(checked against the accepted request) and a public portal. The validator gained
|
|
|
|
|
`when` / `required_when` conditions and provider discovery. 43 tests pass.
|
|
|
|
|
ops-mason's and ops-warden's sections are drafted in the type. Their owners
|
|
|
|
|
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
|
|
|
|
|
(T05).
|
|
|
|
|
|
2026-10-08 22:17:03 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T05
|
|
|
|
|
status: wait
|
2026-10-08 22:45:31 +02:00
|
|
|
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
|
2026-10-08 22:17:03 +02:00
|
|
|
needs_human: true
|
|
|
|
|
blocking_reason: "A new SecurityCaseType is acknowledged as an informed-decision by the founder."
|
|
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T05 — Acknowledge the type through informed-decision
|
|
|
|
|
|
|
|
|
|
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
|
|
|
|
|
through informed-decision's production intake. On acknowledgment the type
|
|
|
|
|
moves to `acknowledged`.
|
|
|
|
|
|
2026-10-08 23:09:51 +02:00
|
|
|
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
|
|
|
|
|
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
|
|
|
|
|
one act, what acknowledging does not do, the shape, alternatives, attended
|
|
|
|
|
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
|
|
|
|
|
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
|
|
|
|
|
decision on answer). The envelope passes informed-decision's own
|
|
|
|
|
`validate_submission`. Queuing it is an attended operator step, a write inside
|
|
|
|
|
the production dispatcher pod:
|
|
|
|
|
|
|
|
|
|
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
|
|
|
|
|
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
|
|
|
|
|
< security-case-types/oidc-browser-relying-party/decision/intake.json
|
|
|
|
|
|
|
|
|
|
The answer then appears for review in informed-decision.
|
|
|
|
|
|
2026-10-08 23:23:58 +02:00
|
|
|
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
|
|
|
|
|
first envelope's idempotency key was not State Hub's stable
|
|
|
|
|
`state-hub.<decision>.<memo>` form, and the builder now validates against both
|
|
|
|
|
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
|
|
|
|
|
delivered). T05 waits for the founder's answer in informed-decision, which
|
|
|
|
|
resolves hub decision `e418263e`.
|
|
|
|
|
|
2026-10-08 23:27:22 +02:00
|
|
|
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
|
|
|
|
|
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
|
|
|
|
|
`platform/workloads/keycape/{client_id}`; policies are
|
|
|
|
|
`workload-kv-read-keycape-{client_id}-eso` and
|
|
|
|
|
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
|
|
|
|
|
body (`read` on that one data path); new `openbao_role`
|
|
|
|
|
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
|
|
|
|
|
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
|
|
|
|
|
the two rollback steps on the KV path collapse into one metadata delete. The
|
|
|
|
|
schema and validator now require a `body` on policy and role items. 105 tests
|
|
|
|
|
pass. Two consequences: the delivered memo describes the type before these
|
|
|
|
|
changes (a case-set custody reference, two differently named policies, no ESO
|
|
|
|
|
role), so the founder answers on a superseded shape unless the memo is
|
|
|
|
|
re-sent; and Forgejo's custody path moves from the accepted request's proposed
|
|
|
|
|
`platform/workloads/forgejo/netkingdom-oidc-client` to
|
|
|
|
|
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
|
|
|
|
|
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
|
|
|
|
|
(WARDEN-WP-0044-T01) is still in progress.
|
|
|
|
|
|
2026-10-08 23:41:32 +02:00
|
|
|
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
|
|
|
|
|
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
|
|
|
|
|
supersedes the first memo and passes informed-decision's and the dispatcher's
|
|
|
|
|
validation. It needs a new memo id because the dispatcher refuses new content
|
|
|
|
|
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
|
|
|
|
|
same attended step as above. Both memos serve hub decision `e418263e`; the
|
|
|
|
|
first answer resolves it, so the founder answers r2 and leaves the first memo
|
|
|
|
|
unanswered. key-cape and railiance-apps were told about Forgejo's new custody
|
|
|
|
|
path.
|
|
|
|
|
|
2026-10-08 22:17:03 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T06
|
|
|
|
|
status: wait
|
2026-10-08 23:03:00 +02:00
|
|
|
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
|
2026-10-08 22:17:03 +02:00
|
|
|
blocking_reason: "Implementation needs the acknowledged type and both executors' light paths."
|
|
|
|
|
priority: high
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T06 — First KnownSecurityCase: Forgejo
|
|
|
|
|
|
|
|
|
|
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
|
|
|
|
|
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
|
|
|
|
|
so that acceptance carries over as the case acceptance. ops-mason materializes it;
|
|
|
|
|
ops-warden follows through and verifies it. The type becomes `implemented` once
|
|
|
|
|
this reference case is live.
|
|
|
|
|
|
|
|
|
|
This replaces the hand-built path proposed for KEY-WP-0038-T02.
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T07
|
|
|
|
|
status: wait
|
|
|
|
|
depends_on: [NK-WP-0048-T06]
|
|
|
|
|
blocking_reason: "The next types follow once the first one is proven end to end."
|
|
|
|
|
priority: medium
|
2026-10-08 22:19:55 +02:00
|
|
|
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
|
2026-10-08 22:17:03 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T07 — Queue the next types
|
|
|
|
|
|
|
|
|
|
From the T02 ranking, open the next type drafts (likely the confidential service
|
|
|
|
|
client and the workload KV read lane), each to be acknowledged on its own.
|
|
|
|
|
Record which existing hand-built instances get adopted as cases, without
|
|
|
|
|
rewriting them in place.
|
2026-10-08 22:45:31 +02:00
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T08
|
2026-10-08 22:58:36 +02:00
|
|
|
status: done
|
2026-10-08 22:45:31 +02:00
|
|
|
priority: high
|
2026-10-08 22:46:16 +02:00
|
|
|
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
|
2026-10-08 22:45:31 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T08 — identity-provisioner: ensure service-access groups
|
|
|
|
|
|
|
|
|
|
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
|
|
|
|
|
<list> --members <group=user+user;...>`. The provisioner already has idempotent
|
|
|
|
|
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
|
|
|
|
|
the general operation (create if absent, add the listed memberships, never
|
|
|
|
|
remove others), its rollback (remove only this case's memberships), tests, and
|
|
|
|
|
the ops-warden route for its service identity.
|
|
|
|
|
|
2026-10-08 22:58:36 +02:00
|
|
|
2026-10-08: done. `identity-provisioner/provisioner.py` gained
|
|
|
|
|
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
|
|
|
|
|
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
|
|
|
|
|
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
|
|
|
|
|
already exist; everything is validated before any change; memberships are read
|
|
|
|
|
back before success; nothing else is ever removed; groups are deleted only when
|
|
|
|
|
asked and empty. 7 new tests (39 in the provisioner suite). The type's
|
|
|
|
|
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
|
|
|
|
|
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
|
|
|
|
|
|
2026-10-08 23:03:00 +02:00
|
|
|
```task
|
|
|
|
|
id: NK-WP-0048-T09
|
|
|
|
|
status: done
|
|
|
|
|
priority: high
|
2026-10-08 23:03:48 +02:00
|
|
|
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
|
2026-10-08 23:03:00 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### T09 — The `keycape-client` entry point and its declaration
|
|
|
|
|
|
|
|
|
|
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
|
|
|
|
|
live registration needs already lives in net-kingdom, where KeyCape is deployed
|
|
|
|
|
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
|
|
|
|
|
issuer pin. A copy in key-cape would drift.
|
|
|
|
|
|
|
|
|
|
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
|
|
|
|
|
client record from a case (`client-from-case`). It adds or removes exactly one
|
|
|
|
|
client with the existing safeguards (byte-preserving merge, exact match or
|
|
|
|
|
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
|
|
|
|
|
requires the Deployment env binding first for confidential clients, and requires
|
|
|
|
|
the case acceptance record for `apply` instead of a per-client founder decision
|
|
|
|
|
id. 15 tests, including a regression showing that the vergabe case reproduces
|
|
|
|
|
its hand-built registration exactly; all 71 helper tests pass. Declaration
|
|
|
|
|
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
|
|
|
|
|
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
|
|
|
|
|
and for confidential clients adds `deliver-secret` (delivery from custody into
|
|
|
|
|
the sso namespace) and `bind-env` (Deployment env), attended in the same session
|
|
|
|
|
as `register-client`. The per-client helpers stay until their clients are
|
|
|
|
|
adopted as cases.
|