railiance-platform/docs/credential-lane-designs/oidc-service-client-review.md
codex 6858bef858
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
oidc-service-client 0.1 owner review; draft CCR-2026-0060/0061 (proposed)
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
2026-10-31 23:03:46 +01:00

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 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).