MASON-WP-0010-T01: review oidc-service-client 0.1 materialization (acceptable with changes)

Eleven findings (R1-R11) on net-kingdom's proposed SecurityCaseType
oidc-service-client 0.1 for request f61cc81e: type changes R3, R4, R6, R7,
R8; ops-mason builder follow-ups R10; rollback addition R11.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
tegwick 2026-10-31 23:02:47 +01:00
parent 827781f318
commit 3c69a25442

View file

@ -9,7 +9,7 @@ flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-11"
updated: "2026-10-31"
depends_on: []
related_repos:
- net-kingdom
@ -98,6 +98,111 @@ NK-WP-0048-T05 still waits on WARDEN-WP-0044-T01 and a founder answer.
2026-10-09: the founder accepted memo r4; hub decision `e418263e` resolved and
the type is `acknowledged` (net-kingdom `0e86f95`). T02 is unblocked.
**Review (2026-10-31) of net-kingdom `security-case-types/oidc-service-client/type.yaml` 0.1**
(proposed, net-kingdom `5d5f15d`, memo `nk-wp-0056-t14-oidc-service-client-r1`,
hub decision `f8044f81`, request message `f61cc81e`). Checked against the
browser type 1.1, the live CCR-2026-0041/0042 placement and the bounded
executor's native lanes CCR-2026-0058/0059, and by a trial render of
`examples/nk-bind-executor-approval.yaml` through `case_plan.render` and
`case_build` on a scratch copy marked `acknowledged` (no live survey, no
OpenBao). All six items render; names come out as
`workload-kv-read-keycape-nk-bind-executor-approval-{eso,bounded-executor}`,
`external-secrets-keycape-nk-bind-executor-approval` and catalog entry
`bounded-executor-keycape-nk-bind-executor-approval`.
Verdict: **acceptable with changes.** The structure is right and reuses the
browser type's custody, ESO and policy families. R3, R4, R6, R7 and R8 change
the type (so memo r2 replaces r1); R10 is ops-mason's own builder work and does
not block acknowledgment, but must land before the first case is built.
- **R1 — client-secret-path: ok.** Derived, not case-settable, same
`platform/workloads/keycape/{client_id}` / `CLIENT_SECRET` as the browser type
and CCR-2026-0033. Unconditional (the type is always confidential), so no
`when`. `client_id` is unique in KeyCape, so browser and service clients
cannot collide in the one `keycape/` subtree.
- **R2 — keycape-reader-policy and naming family: ok.** Unchanged from the
browser type; `workload-kv-read-keycape-<client>-<reader>` is respected for
both readers. Longest rendered name is 68 characters, within OpenBao limits.
- **R3 — eso-role: change needed (small).** Bound to
`external-secrets/external-secrets`, one policy, 15m, no default policy:
right. But the body omits `audience: openbao` and `token_explicit_max_ttl:
15m`, which the consumer role states and the live
`external-secrets-keycape-forgejo` role has; ops-mason's builder fills them
in silently today. Add both so the template is what gets built, and the two
roles read the same.
- **R4 — consumer-reader-policy self-lifecycle stanzas: change needed.** The
`auth/token/lookup-self` (read) and `auth/token/revoke-self` (update) stanzas
are correct and needed (a native per-call reader should revoke its token
after the read, and the platform applier refuses a `token_self_lifecycle:
true` policy without them, see T04 2026-10-10). But only the consumer policy
declares them, while the ESO policy needs them equally. Declare them in
**both** policy bodies, so each body is the complete policy the review
checks (item 3 of the 1.0 review). ops-mason then stops appending them (R10).
- **R5 — consumer-reader-role (native Kubernetes auth): ok.** One
ServiceAccount, one namespace, audience `openbao`, 15m ttl/max/explicit max,
one policy, no default policy: this is exactly the CCR-2026-0058/0059 shape
that is live and verified for `sso/nk-bind-executor`, and "policies are never
combined in one role" holds (the executor gets a third separate role). Role
name equal to the policy name matches the native-lane convention. Dropping
ESO delivery to the consumer removes a Secret copy and a SecretStore per
case; ops-mason prefers it.
- **R6 — consumer binding parameters: change needed.** The patterns admit
`consumer_service_account: default` and namespaces such as
`external-secrets`, `openbao` or `kube-system`. Refuse `default` (any pod in
the namespace would read the client secret; CCR-2026-0059 verifies the
default SA is denied), and refuse the `external-secrets` and `openbao`
namespaces, in the type's constraints or the validator.
- **R7 — shared namespace with KeyCape's copy: change needed.** The first case
binds `sso/nk-bind-executor`, and `sso` is where `deliver-secret` writes
`keycape-{client_id}-client`. So "no Secret copy in the consumer's namespace"
(README, purpose) is false for this case; isolation rests on the consumer SA
having no Secret read in its namespace (true today: rapp-bounded-executor
`manifests/rbac.yaml` grants no Secret access). Add a verification:
`{consumer_namespace}/{consumer_service_account}` cannot get Secrets in its
namespace (in particular `sso/keycape-{client_id}-client`), `can-i` denial as
proof; and reword the README claim to "the type creates no Secret copy for
the consumer".
- **R8 — verification wording: change needed (small).**
`custody-allowed-to-consumer` says the policy grants read on the path "and
nothing else"; with R4 it also grants token self-lookup/revoke. Say "and no
other path under `platform/`, apart from token self-lifecycle". Optionally
add the same allow check for the ESO policy.
- **R9 — catalog-entry: ok, one question.** `{consuming_service}-keycape-{client_id}`
instead of the browser type's `{consuming_service}-keycape-client` is right:
a service holds one client per audience, so the browser form would collide.
Question: `consuming_service: bounded-executor` differs from the workload name
the platform already uses for this executor (`net-kingdom-bind-executor` in
CCR-2026-0058/0059 and their catalog ids). The case should use the
established name, or the type README should say which one `workload_ref`
carries.
- **R10 — ops-mason builder (ours, not a type change).** The trial build shows
four defects to fix before the first service-client case is built:
(a) `_policy_hcl` always appends the self-lifecycle stanzas, so the consumer
policy gets them twice; append only when absent. (b) A policy with a role is
treated as ESO: the consumer CCR gets `access_frontdoor.type:
external-secrets`; for a native role it must be `platform-owner` (as
CCR-2026-0059) with a `delivery: {surface: native-workload-login, namespace,
service_account}` block. (c) `catalog_id` is hard-coded to
`{consuming_service}-keycape-client`; take it from the catalog item.
(d) `audience` and `token_explicit_max_ttl` are hard-coded / copied from
`token_max_ttl`; take them from the role body (with R3). The consumer CCR is
no longer `in_flight`: with the role in the type it is complete `proposed`,
`bound_claims_confirmed: false` until the platform owner confirms the SA.
- **R11 — rollback order: ok, with one addition.** Consumer configuration,
registration, env binding, delivery, then custody (one metadata delete, after
the ExternalSecret is gone, per item 6 of the 1.0 review), roles before their
policies, catalog last: consistent with the browser type. Addition: deleting
the consumer role does not revoke issued tokens; state that rollback is
complete only after the consumer's last token has expired (at most 15m after
the role is deleted) or was revoked, as the T03 deactivate note does.
Suggested, not required: remove `consumer-reader-role` directly after
`hand-over` is reversed, so consumer access ends first. (ops-mason's own
`rollback.yaml` lists `removes_exactly` in plain reverse order, which differs
from the type's steps; fixed with R10.)
T04 is unchanged by this type: `secrets-engine-openbao`'s second reader is an
attended OIDC operator, not a ServiceAccount, so it fits neither type.
```task
id: MASON-WP-0010-T02
status: done