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 |
| [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 |
| [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
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
id: RPF-WP-0035-T06
status: todo
status: wait
priority: high
needs_human: false
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,
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
`secrets-engine-approval` exchange from a protected temporary client-secret file,
with exact approval-engine / tenant:platform / read-or-consume scope, no token