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
8.7 KiB
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: activeonce verification passesworkload_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}, fieldCLIENT_SECRETdelegation: {mode: native, intended_owner: key-cape}rotationsteps referencing the type's mint-secret and verification, withautomatable: falseuntil 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.pycompares everyPOSTbearer against onePROVISIONER_SERVICE_TOKEN- that token is user-engine's runtime secret
(
platform/workloads/user-engine/runtime, propertyPROVISIONER_TOKEN, delivered bysso-mfa/k8s/user-engine/openbao-runtime.yaml) - NetworkPolicy
sso/identity-provisioneradmits port 8080 only from user-engine pods, plus traefik for the/setup/passwordIngress
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:
- Attended, in the existing session (recommended). Run
groups ensurein the attended session that already runsregister-client, through the same contained kubectl (an exec into the provisioner pod, or a contained one-shot job), withwarden_executes: false,attended: trueand lanekey-cape-oidc-login. This adds no attended session, because the type already needs one, and nothing new has to be built. - 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-groupsbecomes a real caller-proxy lane andwarden_executes: trueholds. 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.