ops-warden/history/2026-10-08-oidc-rp-follow-through-review.md
tegwick 6258d73732
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
WARDEN-WP-0044-T01: R8, ensure-groups has no caller-identity path
The provisioner accepts only user-engine's runtime token and admits only
user-engine pods, so identity-provisioner-groups cannot be cataloged as a
caller proxy. Asked net-kingdom to move ensure-groups into the existing
attended session or add an operator credential; asked railiance-platform
for a generate-in-custody entry point and the platform/suppliers/ allowlist.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 427672@bnt-lap001
Assistant-Session: a0a2367e-4697-440c-a4e5-9713c009f6d8
2026-10-09 10:58:16 +02:00

8.7 KiB
Raw Blame History

Review — oidc-browser-relying-party 1.0 follow_through (WARDEN-WP-0044-T01)

Reviewed: net-kingdom security-case-types/oidc-browser-relying-party/type.yaml (1.0, proposed), against canon security-case-types_v0.1 and ops-warden ADR-0001, ADR-0002, ADR-0004, ADR-0005, ADR-0008, and ADR-0010. Reviewer: ops-warden, 2026-10-08. Feeds net-kingdom NK-WP-0048-T05.

Verdict: the step order, the attended grouping, and the rollback are sound. Before ops-warden can plan a run of this type (WARDEN-WP-0044-T02), the schema needs the changes R1–R3. R4–R7 are refinements.

Required

R1 — Bind each step to a catalog lane. Add a lane: <catalog-id> field to every follow-through step. warden case plan resolves each step through the catalog, and it refuses a step whose lane is missing or not reviewed. That is the T02 acceptance test. Today the steps name their auth only in prose, so ops-warden cannot tell which lane applies. Two lanes do not exist yet:

Step Lane needed Owner
ensure-groups identity-provisioner caller credential for POST /v1/groups/ensure / revoke net-kingdom
mint-secret broker grant that generates a value in custody at {secret_ref} railiance-platform

key-cape-oidc-login already covers the attended operator session.

R2 — Define warden_executes: true narrowly. It should mean: ops-warden invokes the owner's declared entry point, through the existing caller proxy and under the caller's identity. It must not mean that ops-warden holds a credential. For ensure-groups, the "identity-provisioner service token … via the ops-warden proxy" has to be a token the caller obtains through its own lane. If ops-warden held a standing service token, it would be a broker, which ADR-0002 rules out. Please write this definition into the canon field description, not only into this type.

R3 — Generated secrets must never pass through the run. mint-secret has to generate the value inside the broker and custody. It must never be generated in the run and written to custody afterwards. Add value_handling: none | generated_in_custody per step, and refuse any step that would put a value on ops-warden's stdout, argv, or receipt (ADR-0004, ADR-0008). custody-allowed-to-consumer remains a capabilities-only check. That is correct.

Refinements

R4 — Per-case catalog entries. The catalog-entry materialization item (oidc-client-{client_id}) creates one route per case. WARDEN-WP-0044-T04 and ADR-0001 point the other way: the type contributes one reviewed, parameterized entry, and each case adds values, not routes. Proposal: drop catalog-entry from materialization, and let the type declare a single oidc-client lane whose path template uses {client_id}. The existing hand-built rapp-qonto-keycape-client entry would later become a case of the type.

R5 — Make ESO delivery a gate. bind-env needs the delivered Secret to exist, and KeyCape fails closed at start when it does not. Add a per-step ready_check (or a verification item) after deliver-secret: ExternalSecret sso/keycape-{client_id}-client reports Ready=True. This checks status only and never reads the value. Without it, the attended session can start before the Secret has been delivered.

R6 — Membership comes only from the accepted record. In ensure-groups, the case controls the members (initial_members), and it runs unattended. The run must pass exactly the memberships in the accepted case record and refuse anything else. Group membership is an access grant.

R7 — Missing verifications. Add mfa-pinned (when mfa_required == "true", the KeyCape registration carries the MFA pin, checked through the non-secret client view) and secret-delivered (R5). Every item keeps reads_secret_value: false.

Not in scope for ops-warden

bind-env and register-client are attended, and their owner is net-kingdom. The justification is accepted: the live registry is a Kubernetes Secret. ops-warden groups those steps into one key-cape-oidc-login session and does not execute them. hand-over belongs to the consumer.

Addendum 2026-10-09: R4 reconciled with ops-mason item 5

net-kingdom pointed out that R4 conflicts with MASON-WP-0010-T01 item 5 (per-case {consuming_service}-keycape-client). On reflection, R4 was wrong as written. R4 is withdrawn and replaced with R4′.

A single parameterized entry would have to carry workload_ref: not-applicable, as the generic openbao-api-key template does. That would drop the consumer's workload reference, and ADR-0009 needs that reference to select the zone and PEP failure mode for this exact client. A per-case entry carries real per-consumer data. It is not a second copy of a procedure, so ADR-0001 is not engaged.

R4′: keep one catalog entry per case, named {consuming_service}-keycape-client (ops-mason's item 5). Template its body in the type, as item 3 now requires for policies, so that cases add values and never route design (WARDEN-WP-0044-T04). The body should contain:

  • owner_repo: key-cape, warden_executes: false, risk: high, status: active once verification passes
  • workload_ref: {applicability: applicable, name: {consuming_service}, declaration_ref: <consumer declaration>}. If the consumer has not published a declaration, the field takes the explicit unknown form, never an inferred one.
  • path_template: platform/workloads/keycape/{client_id}, field CLIENT_SECRET
  • delegation: {mode: native, intended_owner: key-cape}
  • rotation steps referencing the type's mint-secret and verification, with automatable: false until a run has been proven

Correction to R4: the existing rapp-qonto-keycape-client is a client-credentials workload client at a different path. It is not a browser relying party, so it would not become a case of this type. Only the naming precedent carries over.

Re-check 2026-10-09: revised 1.0 against R1–R7

warden case check-type run on the revised type: schema_ok=True. R1 (lane), R2 (canon definition of warden_executes), R3 (value_handling), R4′ (templated catalog-entry body), R5 (ready_check on deliver-secret), R6 (exact memberships in ensure-groups) and R7 (secret-delivered, mfa-pinned) are all present. Before T02 can plan a run, two lanes must become reviewed catalog entries: identity-provisioner-groups (owner net-kingdom) and keycape-client-secret-generate (owner railiance-platform). ops-warden has no further required changes to the type.

Addendum 2026-10-09: R8, ensure-groups has no caller-identity path

NK-WP-0048-T08 handed the ops-warden route for the provisioner's service token to WARDEN-WP-0044-T01. That route cannot be written as the type declares it.

The ensure-groups step says warden_executes: true, auth "the caller's own identity-provisioner credential through its lane; ops-warden invokes it via the caller proxy and holds no token". The deployed provisioner has no per-caller credential:

  • identity-provisioner/server.py compares every POST bearer against one PROVISIONER_SERVICE_TOKEN
  • that token is user-engine's runtime secret (platform/workloads/user-engine/runtime, property PROVISIONER_TOKEN, delivered by sso-mfa/k8s/user-engine/openbao-runtime.yaml)
  • NetworkPolicy sso/identity-provisioner admits port 8080 only from user-engine pods, plus traefik for the /setup/password Ingress

A catalog lane over this would have to fetch user-engine's workload secret and reach the service from outside its admitted peers. The operator would be acting as user-engine, not as themselves. That breaks canon §3's narrow warden_executes (caller identity, no standing credential) and ADR-0002. So ops-warden adds no identity-provisioner-groups entry in this form.

R8: net-kingdom chooses one of these:

  1. Attended, in the existing session (recommended). Run groups ensure in the attended session that already runs register-client, through the same contained kubectl (an exec into the provisioner pod, or a contained one-shot job), with warden_executes: false, attended: true and lane key-cape-oidc-login. This adds no attended session, because the type already needs one, and nothing new has to be built.
  2. Give the provisioner a caller credential. Add an operator-scoped credential separate from user-engine's, such as an OIDC caller token verified against KeyCape or an OpenBao-issued per-operator token, plus an admitted ingress path for it. Then identity-provisioner-groups becomes a real caller-proxy lane and warden_executes: true holds. This is more work, and it is the shape the type describes today.

Until one of these lands, warden case check-type keeps reporting the lane missing, which is correct.