Prepare the two approval client-side reader admissions
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run

RPF-WP-0035-T06. Adds CCR-2026-0019 (secrets-engine) and CCR-2026-0020
(approval-engine-operator) with their exact-path read policies, reusing the
existing version-1 custody from the verifier activation. No reseed, rotation,
shared reader or verifier Secret reuse; both requests are in_flight and nothing
is applied.

The two shapes were decided by read-only survey rather than assumed.
secrets-engine consumes its client secret through an operator-run CLI reading a
protected file, and its namespace holds no workload, so reader 1 is an attended
operator-workstation OIDC lane rather than an ESO lane; its one missing input is
the operator group claim, which NetKingdom and KeyCape own. approval-engine is
not deployed and no owner source names who presents the operator client, so
reader 2 records the undetermined actor instead of guessing one for the widest
approval scope in the pair. Both declare openbao.auth missing rather than
carrying a placeholder binding.

T06 moves to wait on those two owner inputs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLUjpv3ssxNRAEPPgLFnEB

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1275505@bnt-lap001
Assistant-Session: 97265baa-f08f-4032-b290-a1e2965a69c5
This commit is contained in:
codex 2026-09-09 14:41:01 +02:00
parent 5507fac156
commit 32d5cf0211
7 changed files with 431 additions and 1 deletions

View file

@ -0,0 +1,158 @@
id: CCR-2026-0019
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: secrets-engine client-side read of its approval client secret
status: in_flight
created: '2026-09-09'
updated: '2026-09-09'
in_flight:
missing_fields:
- openbao.auth
blocking_reason: >-
The reader is an attended operator running the secrets-engine CLI on a
workstation, not an in-cluster identity. The auth binding is therefore
determined in shape but not bindable: method oidc on mount netkingdom, role
secrets-engine-approval-client-read, the three standard NetKingdom redirect
URIs, scopes openid/profile/email/groups, user_claim sub, groups_claim
groups, policy workload-kv-read-secrets-engine-approval-client, ttl 15m --
with the one missing input being the exact group claim for the authorized
operator, which NetKingdom/KeyCape own. A role cannot be created without it
and no placeholder is recorded here. Live inspection on 2026-09-09 found namespace
secrets-engine holding only ServiceAccount secrets-engine with no workload,
which confirms there is no in-cluster consumer to bind instead.
owner: railiance-platform
requester:
agent: claude
reason: >-
RPF-WP-0035-T06 residual from the completed verifier custody
(CCR-2026-0017). The verifier copy lets KeyCape authenticate the
secrets-engine-approval client; it does not let secrets-engine present that
client. secrets-engine implemented the consumer on 2026-09-09
(SECRETS-WP-0009-T03, repo revision 9eb07fd,
secrets-engine/docs/approval-service-auth.md) and reads the value from
SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE, an explicit protected file. This
request establishes the separate exact read that fills that file. It reuses
the existing version-1 custody and requests no reseed and no rotation.
review:
required: true
required_approvers:
- platform-operator
- secrets-engine-owner
comments:
- at: '2026-09-09'
reviewer: railiance-platform (claude)
decision: shape_determined_actor_pending
comment: >-
Reader shape determined from owner source rather than assumed: the
consumer is an operator-run CLI that exchanges the client secret for a
short-lived token immediately before each approval request, holds it only
in memory, and has no refresh token, renewal or fallback identity. That
makes this an attended operator-workstation lane on the OIDC mount, not an
External Secrets lane. The named operator group claim is the one input
neither this repo nor secrets-engine can supply; NetKingdom/KeyCape own it.
target:
domain: financials
tenant: platform
workload: secrets-engine
environment: production
purpose: >-
Let the authorized operator running the secrets-engine approval CLI fetch the
secrets-engine-approval client secret into a protected file, without granting
any in-cluster reader, any sibling path, or any write on the custody path.
openbao:
mount: platform
kv_path: platform/workloads/secrets-engine/approval-client
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-secrets-engine-approval-client
policy_file: openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl
access_frontdoor:
type: ops-warden
catalog_id: secrets-engine-approval-client
selector: secrets-engine approval client secret
command: warden access secrets-engine-approval-client --out FILE
resolvable: false
readiness: pending-review
delivery:
surface: operator-workstation
target: >-
Protected file consumed as SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE by the
secrets-engine approval CLI: owner-only mode, outside any Git work tree,
removed at the end of the session. No Kubernetes Secret, no ExternalSecret
and no reuse of the sso verifier Secret keycape-secrets-engine-approval-client.
risk:
classification: high
notes:
- >-
This is the presenting copy. Anyone holding it can obtain tokens as
service:secrets-engine for approval:read and approval:consume until KeyCape
disables the registration. The verifier copy admitted in CCR-2026-0017 does
not carry that power; the two must not be conflated because they hold the
same value.
- >-
A file-delivered credential outlives the process that read it. The lane
depends on operator hygiene for removal, which is weaker than an
ESO-delivered Secret bounded by a pod lifetime. That is a consequence of the
consumer being a workstation CLI and is recorded rather than engineered away.
- >-
approval:consume is a production-effect scope. This lane grants the ability
to present the client; it does not grant approval decisions, which remain
with access-engine, approval-engine and the existing consume gates.
- >-
No reseed and no rotation. Custody is the existing version 1 from the
verifier activation; a rotation is a distinct version-guarded operation that
also invalidates the KeyCape verifier copy and needs both owners.
verification:
positive:
- >-
The bound operator identity reads CLIENT_SECRET from the exact path into a
protected file with no value in chat, argv, shell history or logs.
- >-
The secrets-engine approval CLI exchanges it for a token with subject
service:secrets-engine, audience approval-engine, tenant tenant:platform and
the single requested scope, verified against live JWKS.
negative:
- >-
An identity outside the confirmed group claim cannot authenticate through
role secrets-engine-approval-client-read.
- >-
The role cannot read the approval-engine operator-client path, cannot list
any parent, and cannot write or patch the custody path.
- >-
No in-cluster ServiceAccount, including secrets-engine/secrets-engine, gains
a read through this lane.
- >-
The KeyCape verifier role cannot be substituted for this reader, and this
reader cannot read the verifier's delivery Secret.
activation_conditions:
- >-
NetKingdom/KeyCape confirm the exact group claim for the authorized operator
and it is recorded in openbao.auth.bound_claims with
bound_claims_confirmed true.
- >-
secrets-engine confirms the workstation procedure, file mode and removal step
for SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE.
- >-
Policy and OIDC role applied under attended authority
(openbao-platform-admin-login, founder_required) with metadata-only receipts.
- Positive and negative results recorded with non-secret request ids.
evidence: []
lifecycle:
deactivate: >-
Detach the policy from role secrets-engine-approval-client-read and disable
the ops-warden catalog entry. The KeyCape verifier lane and its delivery are
unaffected.
rotate: >-
Shared with CCR-2026-0017: the value is one client secret. Rotation is minted
by KeyCape and rewritten once under attended authority; both the verifier
delivery and this reader then see the new version. No independent rotation
exists for this lane alone.
compromised: >-
KeyCape disables the secrets-engine-approval registration first, since that
is what stops token issuance, then the value is rotated and this role's
policy detached pending review.
state_hub:
workplan_id: RPF-WP-0035
task_id: RPF-WP-0035-T06
related_request: CCR-2026-0017

View file

@ -0,0 +1,145 @@
id: CCR-2026-0020
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: Client-side read of the approval-engine-operator client secret
status: in_flight
created: '2026-09-09'
updated: '2026-09-09'
in_flight:
missing_fields:
- openbao.auth
blocking_reason: >-
The presenting actor is not identified by any owner source. approval-engine
verifies this client's tokens but never presents them, and no other repo
claims the approver surface that would. Live inspection on 2026-09-09 found
no approval-engine namespace, workload or Service in the cluster; the owner
deploy manifest targets namespace approval-engine and remains unapplied. An
auth method cannot be chosen before it is known whether the presenter is an
attended operator, an in-cluster component, or an agent surface, and the
three imply different roles, policies and delivery.
owner: railiance-platform
requester:
agent: claude
reason: >-
RPF-WP-0035-T06 residual from the completed verifier custody
(CCR-2026-0018). The verifier copy lets KeyCape authenticate the
approval-engine-operator client; something must also present it. This request
holds the client-side reader open with the determined parts recorded and the
undetermined parts named, rather than guessing a reader for the widest scope
set in the pair. It reuses existing version-1 custody; no reseed, no rotation.
review:
required: true
required_approvers:
- platform-operator
- approval-engine-owner
comments:
- at: '2026-09-09'
reviewer: railiance-platform (claude)
decision: actor_not_determined
comment: >-
Determined from owner source: the approval-engine-operator registration
holds approval:create, read, approve, revoke, supersede, observe and emit,
and explicitly not consume; approval-engine itself only verifies presented
tokens (approval_engine/api.py identity check) and does not hold this
client. approval-engine's own records note the engine does not restrict
/entries by principal type, so this client can supply approver evidence
today, and whether it should is approval doctrine owned by gate-house. The
reader therefore cannot be named here without an owner decision, and
naming one anyway would hand the widest approval scope to a guessed
identity. Held in_flight deliberately.
target:
domain: financials
tenant: platform
workload: approval-engine
environment: production
purpose: >-
Establish the separate exact read by which the eventual approval-engine
operator surface obtains its client secret, once that surface is named by its
owner.
openbao:
mount: platform
kv_path: platform/workloads/approval-engine/operator-client
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-approval-engine-operator-client
policy_file: openbao/policies/workload-kv-read-approval-engine-operator-client.hcl
access_frontdoor:
type: ops-warden
catalog_id: approval-engine-operator-client
selector: approval-engine operator client secret
resolvable: false
readiness: pending-review
delivery:
surface: undetermined
target: >-
Not determined. The delivery surface follows the actor: an attended operator
implies a protected workstation file, an in-cluster component implies an
ExternalSecret into the eventual approval-engine namespace, and an agent
surface implies neither. No surface is admitted until the owner names one.
risk:
classification: high
notes:
- >-
This is the widest scope set in the pair: create, approve, revoke and
supersede. A holder can forge approval lifecycle actions, and because
approval-engine records principal_type rather than restricting it, a
non-human holder can supply approver evidence. That is why the actor is left
unnamed rather than defaulted.
- >-
approval:consume is absent by design and stays absent. Adding it is a new
lane decision, not an edit to this request.
- >-
The exact-path read policy is written and committed so the eventual reader
inherits a bounded grant rather than a broad one drafted under time pressure.
Writing the policy is not admitting a reader.
- >-
No reseed and no rotation; custody is the existing version 1 from the
verifier activation.
verification:
positive:
- >-
Once the actor is named, the bound identity reads CLIENT_SECRET from the
exact path into its admitted delivery surface with no value in chat, argv,
shell history or logs.
- >-
The named surface obtains a token with subject service:approval-engine-operator,
audience approval-engine and tenant tenant:platform, verified against live JWKS.
negative:
- >-
An identity outside the confirmed binding cannot authenticate through the
eventual role.
- >-
The role cannot read the secrets-engine approval-client path, cannot list any
parent, and cannot write or patch the custody path.
- The reader is denied approval:consume, and denial is observed rather than assumed.
- >-
The KeyCape verifier role cannot be substituted for this reader, and this
reader cannot read the verifier's delivery Secret.
activation_conditions:
- >-
The approval-engine owner names the presenting actor and its placement, or
records that no client-side reader is wanted and this request is cancelled.
- >-
Given that actor, the auth method, mount, role, bound claims and delivery
surface are completed and reviewed as a concrete request.
- >-
Policy and role applied under attended authority
(openbao-platform-admin-login, founder_required) with metadata-only receipts.
- Positive and negative results recorded with non-secret request ids.
evidence: []
lifecycle:
deactivate: >-
Detach the policy from the eventual role and disable the ops-warden catalog
entry. The KeyCape verifier lane and its delivery are unaffected.
rotate: >-
Shared with CCR-2026-0018: one client secret, minted by KeyCape and rewritten
once under attended authority. No independent rotation for this lane alone.
compromised: >-
KeyCape disables the approval-engine-operator registration first, then the
value is rotated; emitted approval actions are referred to approval-engine
for audit review, since rotation does not retract them.
state_hub:
workplan_id: RPF-WP-0035
task_id: RPF-WP-0035-T06
related_request: CCR-2026-0018

View file

@ -12,6 +12,7 @@ KeyCape approval-client admission below, which carries executable CCRs
| [Fluid-telegram operator KV](fluid-telegram-operator-kv.md) | RPF-WP-0033 (finished) / RPF-WP-0035-T03 | MASON-WP-0005; FT-WP-0002 | Tenant acceptance, actual OIDC group, write-capable CCR support | | [Fluid-telegram operator KV](fluid-telegram-operator-kv.md) | RPF-WP-0033 (finished) / RPF-WP-0035-T03 | MASON-WP-0005; FT-WP-0002 | Tenant acceptance, actual OIDC group, write-capable CCR support |
| [State Hub preflight signing](state-hub-preflight-signing.md) | RPF-WP-0034 (finished) / RPF-WP-0035-T04 | FLEX-WP-0020-T05; STATE-WP-0085-T09 delivered | Deployment binding, owner-approved custody and rotation window | | [State Hub preflight signing](state-hub-preflight-signing.md) | RPF-WP-0034 (finished) / RPF-WP-0035-T04 | FLEX-WP-0020-T05; STATE-WP-0085-T09 delivered | Deployment binding, owner-approved custody and rotation window |
| [KeyCape approval-engine clients](keycape-approval-clients.md) | RPF-WP-0035-T05 | KEY-WP-0013-T02; GLAS-WP-0015 | Owner approval of CCR-2026-0017/0018, attended founder window, KeyCape reading build | | [KeyCape approval-engine clients](keycape-approval-clients.md) | RPF-WP-0035-T05 | KEY-WP-0013-T02; GLAS-WP-0015 | Owner approval of CCR-2026-0017/0018, attended founder window, KeyCape reading build |
| [Approval client-side readers](keycape-approval-client-side-readers.md) | RPF-WP-0035-T06 | SECRETS-WP-0009-T03; APPROVAL-WP-0002 | Reader 1: exact operator group claim; Reader 2: the presenting actor is unnamed by any owner source |
The completed design workplans are archived. RPF-WP-0035 is the canonical The completed design workplans are archived. RPF-WP-0035 is the canonical
queue for owner review, implementation, and live acceptance still required. queue for owner review, implementation, and live acceptance still required.

View file

@ -0,0 +1,79 @@
# Approval client-side readers
RPF-WP-0035-T06, the residual handed over by the completed verifier custody
(T05). Requests:
[CCR-2026-0019](../../credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml)
and [CCR-2026-0020](../../credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml).
Both are `in_flight`, neither is approved, and nothing is applied.
## Why this exists as separate requests
CCR-2026-0017/0018 admitted the **verifier** copies: KeyCape holds the two client
secrets so it can authenticate credentials presented to it. Presenting them is a
different power with a different holder, so it needs its own admission. The two
Warden fetch selectors that the verifier review left unresolved are exactly this
gap. Both readers use the **existing version-1 custody** — no reseed, no
rotation, no shared reader, no reuse of the `sso` verifier Secrets.
## Survey, 2026-09-09
Read-only, against owner source and the live cluster.
| Question | Finding |
| --- | --- |
| Is there an in-cluster secrets-engine consumer? | No. Namespace `secrets-engine` holds only ServiceAccount `secrets-engine` and no workload. `flex-auth/flex-auth-secrets-engine` is the flex-auth PDP for that consumer, not the service. |
| How does secrets-engine consume the client secret? | Operator-run CLI reading `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE`, exchanged for a fresh token before each request, held in memory only, no refresh token or fallback identity. Implemented 2026-09-09, SECRETS-WP-0009-T03, `secrets-engine` revision `9eb07fd`. |
| Is approval-engine deployed? | No. No namespace, workload or Service. The owner manifest targets namespace `approval-engine` with a digest-pinned image and is unapplied. |
| Who presents `approval-engine-operator`? | Not identified by any owner source. approval-engine verifies these tokens and never presents them. |
## Reader 1 — secrets-engine-approval (CCR-2026-0019)
Shape is determined; one input is missing.
| Item | Value |
| --- | --- |
| Path / field | `platform/workloads/secrets-engine/approval-client` / `CLIENT_SECRET` |
| Policy | `workload-kv-read-secrets-engine-approval-client` (exact data + metadata read, written and committed) |
| Auth | `oidc` on mount `netkingdom`, role `secrets-engine-approval-client-read`, standard three redirect URIs, scopes `openid profile email groups`, `user_claim: sub`, `groups_claim: groups`, ttl 15m |
| Front door | `warden access secrets-engine-approval-client --out FILE` |
| Delivery | Protected owner-only file outside any Git work tree, consumed as `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE`, removed at end of session |
**Missing:** the exact group claim for the authorized operator. NetKingdom and
KeyCape own it; this repo and secrets-engine cannot supply it. The role cannot
be created without it, so `openbao.auth` is declared missing rather than
recorded with a placeholder that would later be mistaken for a confirmed
binding.
This is an operator-workstation lane, not an External Secrets lane — because the
consumer is a CLI, not a pod. A file-delivered credential outlives the process
that read it, so removal depends on operator hygiene. That is weaker than the
pod-lifetime bound of the verifier delivery and is recorded as a consequence of
the consumer's shape rather than engineered away.
## Reader 2 — approval-engine-operator (CCR-2026-0020)
Held open deliberately. The exact-path read policy
`workload-kv-read-approval-engine-operator-client` is written and committed so
the eventual reader inherits a bounded grant rather than one drafted under time
pressure. Writing the policy is not admitting a reader.
**Missing:** the presenting actor and its placement. This client carries the
widest scope set in the pair — `approval:create`, `read`, `approve`, `revoke`,
`supersede`, `observe`, `emit`, and deliberately not `consume`. approval-engine
records `principal_type` on every entry rather than restricting who may supply
approver evidence, and its owner records that whether a non-human principal
*should* is approval doctrine belonging to `gate-house`. Naming a reader here
would hand the widest approval scope to a guessed identity, so the request names
what is undetermined instead.
The delivery surface follows the actor: an attended operator implies a protected
workstation file; an in-cluster component implies an ExternalSecret into the
eventual `approval-engine` namespace; an agent surface implies neither.
## Not in scope
- No reseed or rotation of either custody path. Rotation is one version-guarded
operation shared with the verifier copies and needs both owners.
- No `approval:consume` on the operator client, now or by later scope edit.
- No reuse of the `sso` verifier Secrets or the KeyCape verifier roles.
- No audit-sender custody; that stays with the existing owner records.

View file

@ -0,0 +1,7 @@
path "platform/data/workloads/approval-engine/operator-client" {
capabilities = ["read"]
}
path "platform/metadata/workloads/approval-engine/operator-client" {
capabilities = ["read"]
}

View file

@ -0,0 +1,7 @@
path "platform/data/workloads/secrets-engine/approval-client" {
capabilities = ["read"]
}
path "platform/metadata/workloads/secrets-engine/approval-client" {
capabilities = ["read"]
}

View file

@ -246,7 +246,7 @@ Rotation is a distinct, version-guarded operation.
```task ```task
id: RPF-WP-0035-T06 id: RPF-WP-0035-T06
status: todo status: wait
priority: high priority: high
needs_human: false needs_human: false
intervention_note: "" intervention_note: ""
@ -265,6 +265,39 @@ consumer delivery, cleanup/revocation and rollback, then obtain the required
owner reviews for those concrete requests. No reseeding, rotation, shared reader, owner reviews for those concrete requests. No reseeding, rotation, shared reader,
reuse of the sso verifier Secret, or implicit operator consume grant. reuse of the sso verifier Secret, or implicit operator consume grant.
2026-09-09 preparation return: both exact-reader admissions are prepared as
`CCR-2026-0019` (secrets-engine) and `CCR-2026-0020` (approval-engine-operator),
with their exact-path read policies written and committed. Design:
`docs/credential-lane-designs/keycape-approval-client-side-readers.md`. Both
reuse existing version-1 custody; no reseed, rotation, shared reader or verifier
Secret reuse. Both are `in_flight` and unapproved; nothing is applied.
Read-only survey findings that decided the two shapes. Namespace
`secrets-engine` holds only ServiceAccount `secrets-engine` with no workload,
and `flex-auth/flex-auth-secrets-engine` is that consumer's PDP, not the
service; secrets-engine consumes the client secret through an operator-run CLI
reading `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE` (SECRETS-WP-0009-T03,
revision `9eb07fd`). So reader 1 is an attended operator-workstation OIDC lane,
not an ESO lane. approval-engine has no namespace, workload or Service; its
manifest is unapplied, and no owner source names who presents the operator
client — approval-engine verifies these tokens and never holds them.
**Unblock:** reader 1 needs the exact NetKingdom/KeyCape group claim for the
authorized operator, plus secrets-engine confirming the file mode and removal
step; `openbao.auth` is declared missing rather than filled with a placeholder
that would later read as a confirmed binding. Reader 2 needs its owner to name
the presenting actor and placement, or to record that no client-side reader is
wanted so the request is cancelled. Naming a reader for the widest approval
scope without that decision is the failure this task exists to avoid.
**Done when:** each reader has a named actor and placement, a completed and
reviewed auth binding, admitted delivery, and its owner approvals; positive and
negative checks pass, including cross-path, wrong-identity and parent-list
denial and operator `consume` denial; or the request is explicitly cancelled by
its owner. Rotation stays a distinct version-guarded operation shared with
CCR-2026-0017/0018.
Consumer input supplied 2026-09-09: secrets-engine now implements per-request Consumer input supplied 2026-09-09: secrets-engine now implements per-request
`secrets-engine-approval` exchange from a protected temporary client-secret file, `secrets-engine-approval` exchange from a protected temporary client-secret file,
with exact approval-engine / tenant:platform / read-or-consume scope, no token with exact approval-engine / tenant:platform / read-or-consume scope, no token