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
9.7 KiB
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 onplatform/data/workloads/keycape/+. It denies exactly the reservedsecrets-engine-openbao.nk-bind-executor-approvalis one segment, matches the type'sclient_idpattern (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.pyrefuses the case today, for three reasons:ACKNOWLEDGED_TYPESadmits onlyoidc-browser-relying-party@1.1. The result iscase_type_unacknowledged.- It requires
parameters.client_type == confidential, but the service type has noclient_typeparameter, because it is confidential by construction. The result iscase_client_not_confidential. - It requires case status
materialized.
- Needed: an owner-reviewed source change that adds
oidc-service-client@<acknowledged version>toACKNOWLEDGED_TYPES, with its type-byte andPARAMETER_PINSfingerprint. 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-secretfor this case refuses (fail closed). - The KeyCape secret format (48 bytes, URL-safe, confirmed by key-cape for confidential clients) applies
unchanged to
client_credentialsclients.
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_ttlandtoken_explicit_max_ttlall 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: openbaoand notoken_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
- role
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)
- role
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
- net-kingdom: memo r2 with R3, R4, R5 and R7, plus the R2 wording.
- Founder: acknowledge the type (r2), then accept the case.
- railiance-platform: broker admission for
oidc-service-client@<version>after acknowledgment (R1). This needs a workplan task here. - Founder: approve CCR-2026-0060 and CCR-2026-0061 and confirm their bindings (R6).