oidc-service-client 0.1 owner review; draft CCR-2026-0060/0061 (proposed)
All checks were successful
CCR intake gate / gate (push) Successful in 1m12s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Review of net-kingdom's proposed SecurityCaseType oidc-service-client 0.1
(hub message 4ec57765): mint grant covers the keycape/<client_id> family but
the broker does not admit the type yet (R1); consumer role shape consistent
(R2); ESO bodies, consumer role name and rotation text need a type r2
(R3-R5, R7); per-case CCRs required (R6), drafted for nk-bind-executor-approval.

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:
codex 2026-10-31 23:03:46 +01:00
parent 7bdf1996f0
commit 6858bef858
5 changed files with 417 additions and 0 deletions

View file

@ -0,0 +1,162 @@
# oidc-service-client 0.1: custody lane and consumer role (owner review, railiance-platform)
Owner review from railiance-platform for net-kingdom's proposed SecurityCaseType `oidc-service-client` 0.1
(net-kingdom `security-case-types/oidc-service-client/`, commit 5d5f15d; hub decision `f8044f81`; memo
`nk-wp-0056-t14-oidc-service-client-r1`). Request: State Hub message `4ec57765`. Per canon
security-case-types_v0.1 section 8 this review comes before the founder acknowledges the type.
Everything here is source review and proposed drafts. No OpenBao role, policy, KV entry or Secret was
created, read or changed.
## Verdict
**Shape accepted with changes.** The custody path family, the mint grant and the consumer-role shape fit
railiance-platform's contracts. Four type-side corrections are needed (R3, R4, R5, R7), which makes this a
memo r2. One platform-side change is needed before the first mint (R1). Each case needs its own CCRs (R6).
For the first case they are drafted: CCR-2026-0060 (ESO reader) and CCR-2026-0061 (consumer reader), both
`proposed`.
## Findings
### R1. Mint lane: the OpenBao grant covers the path, the broker does not admit the type yet. Verdict: platform change required
- The grant (CCR-2026-0040, policy `keycape-client-secret-generate`) is create-only on
`platform/data/workloads/keycape/+`. It denies exactly the reserved `secrets-engine-openbao`.
`nk-bind-executor-approval` is one segment, matches the type's `client_id` pattern (the same pattern as
the browser type) and is not reserved. The issuer binding (CCR-2026-0043, net-kingdom-admins + aal2)
covers it too. **At the OpenBao layer no new grant, policy or CCR is needed** for service-client paths.
- The broker entry point `scripts/keycape_client_secret_generate.py` refuses the case today, for three
reasons:
- `ACKNOWLEDGED_TYPES` admits only `oidc-browser-relying-party@1.1`. The result is
`case_type_unacknowledged`.
- It requires `parameters.client_type == confidential`, but the service type has no `client_type`
parameter, because it is confidential by construction. The result is `case_client_not_confidential`.
- It requires case status `materialized`.
- Needed: an owner-reviewed source change that adds `oidc-service-client@<acknowledged version>` to
`ACKNOWLEDGED_TYPES`, with its type-byte and `PARAMETER_PINS` fingerprint. The change must make the
confidential check type-specific: the service type is implicitly confidential. Refusal tests must cover an
unacknowledged service type, a drifted source and a client_id mismatch. It follows the RPF-WP-0060-T01
rule: no speculative pin before genuine acknowledgment. It can therefore only land after the founder
acknowledges the type. Until then, `mint-secret` for this case refuses (fail closed).
- The KeyCape secret format (48 bytes, URL-safe, confirmed by key-cape for confidential clients) applies
unchanged to `client_credentials` clients.
### R2. Consumer-reader role shape. Verdict: consistent
The role shape matches the one-role-per-lane rule and the CCR-2026-0058/0059 shape:
- Kubernetes auth;
- one ServiceAccount and one namespace;
- audience `openbao`;
- `ttl`, `token_max_ttl` and `token_explicit_max_ttl` all 15m;
- `token_no_default_policy`;
- exactly one policy.
It is a separate role from the executor's CCR-2026-0058 write role, the CCR-2026-0059 journey reader and the
ESO reader. The policies are never combined. `scripts/credential-change.py` enforces this: the validator
requires `policies == [policy_name]`, and the applier requires "only openbao.policy_name".
The `auth/token/lookup-self` (read) and `auth/token/revoke-self` (update) stanzas are **correct and
expected**. They are exactly what `token_self_lifecycle: true` generates (`generated_policy_hcl`), and they
match CCR-2026-0033's amendment, CCR-2026-0041/0042, CCR-2026-0059 and the 0058 write policy. Together they let
the reader end its own token after each call. The browser type omitted them, and the platform had to add them
itself for CCR-2026-0042. The service type gets this right.
Minor wording: verification `custody-allowed-to-consumer` says the policy grants read on the path "and
nothing else". It should say "and nothing else besides its own token's lookup-self and revoke-self". (Type
text; fold into r2.)
### R3. ESO reader (keycape-reader-policy, eso-role) lags behind the platform shape. Verdict: change the type (r2)
The type inherited the browser type's bodies:
- the policy has no self-lifecycle stanzas;
- the role has no `audience: openbao` and no `token_explicit_max_ttl`.
The applied browser instance (CCR-2026-0041) and CCR-2026-0033 carry `token_self_lifecycle: true`,
`audience: openbao` and `token_explicit_max_ttl: 15m`. A policy rendered from the type as written would fail
the applier's "policy_file does not match generated CCR policy" check. Align both bodies with the consumer
reader. The same gap exists in `oidc-browser-relying-party`; that is a separate note for its owner, not a
blocker here.
### R4. Consumer role name: the delegated applier refuses the template. Verdict: change the type (r2)
The type names the role the same as the policy: `workload-kv-read-keycape-{client_id}-{consuming_service}`.
The delegated applier (`applier_policy_violations`) admits Kubernetes roles only if they end with
`-workload-kv-read` or `-secrets-read`, or start with `external-secrets-`. As written, every case could only
be applied by an attended out-of-band tool. CCR-2026-0059's role was applied that way: its name fails the same
check, and it was applied through net-kingdom `executor-lanes.py`.
Proposed template: `keycape-{client_id}-{consuming_service}-workload-kv-read`. The policy name stays as it
is, because it satisfies the required `workload-kv-read-` prefix. The `hand-over` step, the
`consumer-login-bound` verification and the rollback text must use the role name. CCR-2026-0061 is drafted
with the proposed name, and `applier_policy_violations` returns no violations for it.
### R5. Rotation is not mint-secret. Verdict: change the type text (r2)
The README ("Rotation is mint-secret, then the consumer's next exchange") and the catalog entry ("rotation:
mint-secret then verification") are wrong for this lane. The grant is create-only (CAS 0). It refuses an
existing entry with exit 3 and can never write a second version. No rotation lane exists:
- RPF-WP-0054-T07 was cancelled as transferred;
- the follow-up is the residual RPF-WP-0055, which needs its own CAS/update authority and ESO and KeyCape
coordination.
State it plainly: until that lane exists, rotation is an attended platform-admin write with CAS=current,
followed by an ESO refresh and a KeyCape restart. The consumer needs no change, because the path stays the
same.
### R6. Each case needs its own CCRs; the type does not stand in. Verdict: per-case CCRs required (drafted for the first case)
This is the same as for browser cases. The forgejo-netkingdom-oidc case got its own CCR-2026-0041 (ESO
reader) and CCR-2026-0042 (consumer reader), rendered by ops-mason and carrying a `security_case` block.
What the acknowledged type replaces:
- the shape review: the platform does not re-argue the role and policy form;
- a fresh executive summary: the case acceptance is cited instead.
What it does not replace is the per-case platform-owner approval of the exact names, paths and bound claims,
nor `binding_confirmed`, nor the per-case apply and verification receipts. Only the mint needs no per-case
CCR: the standing CCR-2026-0040 and CCR-2026-0043 grant covers the family, gated by the broker's case
admission (R1).
For nk-bind-executor-approval, drafted `proposed` (both pass `scripts/credential-change.py validate` and the
delegated-applier checks; `bound_claims_confirmed: false`, so apply stays blocked):
- `credential-change-requests/CCR-2026-0060-nk-bind-executor-approval-keycape-eso-reader.yaml`
- role `external-secrets-keycape-nk-bind-executor-approval`
- policy `openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl`
- approver: platform-owner
- `credential-change-requests/CCR-2026-0061-nk-bind-executor-approval-consumer-reader.yaml`
- role `keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read`
- policy `openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl`
- bound to `sso/nk-bind-executor`, audience openbao, 15m
- approvers: platform-owner and net-kingdom-owner (an executor lane, as CCR-2026-0058/0059)
Activation conditions for both:
- the type is acknowledged;
- the case is accepted;
- the custody entry is minted (after R1);
- the CCR is approved and the binding confirmed.
If ops-mason later renders the case, these drafts are what the render must reconcile to; they are not
duplicates to be added beside it. The full-repository validate still reports the known, unrelated
CCR-2026-0035 failure.
### R7. KeyCape gets a Secret copy; the consumer does not. Verdict: accepted, with a note
KeyCape's ESO target `sso/keycape-<client_id>-client` is a Secret in namespace `sso`, the same namespace as
the consumer. "No Secret copy in the consumer namespace" holds for the consumer's own delivery, but not for
the namespace. The README's control text should say "no copy for the consumer" and name the KeyCape Secret
as the one at-rest copy, as CCR-2026-0033's risk note does ("one value, two copies"). This is a wording
change for r2; the boundary is unchanged (sso namespace writers are already inside it, CCR-2026-0033).
## Open items
1. net-kingdom: memo r2 with R3, R4, R5 and R7, plus the R2 wording.
2. Founder: acknowledge the type (r2), then accept the case.
3. railiance-platform: broker admission for `oidc-service-client@<version>` after acknowledgment (R1). This
needs a workplan task here.
4. Founder: approve CCR-2026-0060 and CCR-2026-0061 and confirm their bindings (R6).