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
This commit is contained in:
parent
7bdf1996f0
commit
6858bef858
5 changed files with 417 additions and 0 deletions
162
docs/credential-lane-designs/oidc-service-client-review.md
Normal file
162
docs/credential-lane-designs/oidc-service-client-review.md
Normal 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue