Admit KeyCape approval-engine client custody paths and delivery
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Answers KEY-WP-0013-T02. Both proposed KV paths are confirmed unchanged; the
field name is corrected to CLIENT_SECRET for the platform uppercase convention
and the CCR validator. Kubernetes delivery references are confirmed against the
live sso namespace. Attended authority is the governed openbao-platform-admin-login
lane, and the rollout is one attended window ordered after the Authelia issuer
precondition.

Adds CCR-2026-0017/0018, two exact-path read policies, two namespace-limited
ClusterSecretStores with Kubernetes auth, two ExternalSecrets, and RPF-WP-0035-T05.
Nothing is applied and no value exists: both CCRs remain proposed.

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-08 14:53:32 +02:00
parent 805e0e5a2b
commit f3ba7ca882
10 changed files with 651 additions and 3 deletions

View file

@ -0,0 +1,48 @@
# CCR-2026-0017 / CCR-2026-0018 (KEY-WP-0013-T02, RPF-WP-0035-T05).
# KeyCape-side delivery of the two approval-engine confidential client secrets.
# The Kubernetes Secret key stays client-secret to match the live
# KEYCAPE_RAPP_QONTO_CLIENT_SECRET secretKeyRef shape; the OpenBao field is
# CLIENT_SECRET because KV field names are uppercase by platform convention.
#
# Apply only after the stores sync and inside the agreed attended rollout window;
# the KeyCape image that reads both environment names rolls out after these sync.
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycape-secrets-engine-approval-client
namespace: sso
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-keycape-secrets-engine-approval
target:
name: keycape-secrets-engine-approval-client
creationPolicy: Owner
deletionPolicy: Retain
data:
- secretKey: client-secret
remoteRef:
key: workloads/secrets-engine/approval-client
property: CLIENT_SECRET
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycape-approval-engine-operator-client
namespace: sso
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-keycape-approval-engine-operator
target:
name: keycape-approval-engine-operator-client
creationPolicy: Owner
deletionPolicy: Retain
data:
- secretKey: client-secret
remoteRef:
key: workloads/approval-engine/operator-client
property: CLIENT_SECRET

View file

@ -0,0 +1,61 @@
# CCR-2026-0017 / CCR-2026-0018 (KEY-WP-0013-T02, RPF-WP-0035-T05).
# Two railiance01-local Kubernetes-auth stores for the KeyCape verifier copies of
# the approval-engine confidential client secrets. Deliberately separate stores,
# roles and policies so either client can be revoked without disturbing the other;
# their scope sets differ and a shared identity would erase that boundary.
#
# Do not apply before both CCRs are approved and the exact-path policies and
# Kubernetes auth roles exist. Both stores are limited to namespace sso and grant
# no access to any other platform workload path.
#
# The existing sso Secret keycape-rapp-qonto-client is hand-created, not managed
# here; adopting it is a separate migration.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: openbao-keycape-secrets-engine-approval
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
app.kubernetes.io/name: keycape
spec:
provider:
vault:
server: http://openbao.openbao.svc:8200
path: platform
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets-keycape-secrets-engine-approval
serviceAccountRef:
name: external-secrets
namespace: external-secrets
conditions:
- namespaces:
- sso
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: openbao-keycape-approval-engine-operator
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
app.kubernetes.io/name: keycape
spec:
provider:
vault:
server: http://openbao.openbao.svc:8200
path: platform
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets-keycape-approval-engine-operator
serviceAccountRef:
name: external-secrets
namespace: external-secrets
conditions:
- namespaces:
- sso

View file

@ -0,0 +1,171 @@
id: CCR-2026-0017
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: KeyCape verifier custody for the secrets-engine-approval confidential client
status: proposed
created: '2026-09-08'
updated: '2026-09-08'
requester:
agent: claude
reason: >-
KEY-WP-0013-T02 (State Hub message 278a3ebe-b529-49f6-bd1a-e3ebcf318260) asks
railiance-platform to admit custody for two confidential client_credentials
registrations. This CCR covers the first: client secrets-engine-approval,
subject service:secrets-engine, audience approval-engine, tenant:platform per
resolved decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6, 15m token lifetime.
KeyCape owns the registration and issuance; this request establishes only the
custody path and the KeyCape-side delivery of the client secret it must verify
presented credentials against. It does not admit the client-side lane by which
secrets-engine would read its own copy — that is a separate request with a
separate consumer gate.
review:
required: true
required_approvers:
- platform-operator
- key-cape-owner
comments:
- at: '2026-09-08'
reviewer: railiance-platform (codex/claude)
decision: paths_confirmed_field_corrected
comment: >-
KV path platform/workloads/secrets-engine/approval-client is confirmed
unchanged: it matches the platform/workloads/<workload>/<lane> convention
enforced by scripts/credential-change.py and used by every admitted platform
lane. The proposed field name client_secret is corrected to CLIENT_SECRET;
KV field names are uppercase by convention (PROVISIONER_TOKEN,
CORE_HUB_API_TOKEN) and lowercase names fail FIELD_NAME_RE validation. The
Kubernetes Secret key client-secret and the KeyCape environment name are
confirmed as proposed.
target:
domain: financials
tenant: platform
workload: secrets-engine
environment: production
purpose: >-
Hold the secrets-engine-approval confidential client secret in platform custody
and project it into the KeyCape runtime so KeyCape can verify presented
client_credentials without the value living in Git, a chart value, or a
hand-created Kubernetes Secret.
openbao:
mount: platform
kv_path: platform/workloads/secrets-engine/approval-client
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-keycape-secrets-engine-approval
policy_file: openbao/policies/workload-kv-read-keycape-secrets-engine-approval.hcl
auth:
method: kubernetes
mount: kubernetes
role: external-secrets-keycape-secrets-engine-approval
bound_claims:
service_account_names:
- external-secrets
service_account_namespaces:
- external-secrets
bound_claims_confirmed: true
policies:
- workload-kv-read-keycape-secrets-engine-approval
ttl: 15m
access_frontdoor:
type: ops-warden
catalog_id: keycape-secrets-engine-approval-client
selector: KeyCape secrets-engine-approval confidential client secret
command: warden access keycape-secrets-engine-approval-client --fetch CLIENT_SECRET
resolvable: false
readiness: pending-review
delivery:
surface: external-secrets
target: >-
ClusterSecretStore openbao-keycape-secrets-engine-approval, limited to
namespace sso, to ExternalSecret sso/keycape-secrets-engine-approval-client and
Secret sso/keycape-secrets-engine-approval-client with key client-secret.
KeyCape resolves it as KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET through a
secretKeyRef, matching the live KEYCAPE_RAPP_QONTO_CLIENT_SECRET shape observed
on image main-153258b. Manifests:
argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml
and keycape-approval-clients.externalsecrets.yaml.
risk:
classification: high
notes:
- >-
This is a verifier-side copy. KeyCape holding the client secret lets it
authenticate the client; it does not let KeyCape act as the client, but a leak
of this value allows anyone to present as secrets-engine for approval:read and
approval:consume until the registration is disabled.
- >-
Custody path is under the client's own workload prefix, not KeyCape's, because
the credential belongs to the secrets-engine client identity. The reader
admitted here is the External Secrets Operator on behalf of the sso namespace
only; a future secrets-engine-side read is a separate lane and separate policy.
- >-
The existing keycape-rapp-qonto-client Secret is hand-created and not
ESO-managed. Do not extend this store or ExternalSecret to adopt it; that is a
distinct migration with its own owner.
- >-
The two approval clients are deliberately kept on separate policies, roles and
stores so the operator client can be revoked without disturbing this one. Their
scope sets differ; a shared policy would erase that boundary.
- >-
Compromise response is KeyCape disabling the client registration plus rotation
of this KV version. An OpenBao token expiry does not invalidate an already
issued client secret.
verification:
positive:
- >-
The ExternalSecret in namespace sso syncs CLIENT_SECRET to Secret key
client-secret without printing the value.
- >-
The KeyCape build that reads KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
starts and issues a token for subject service:secrets-engine with audience
approval-engine, tenant:platform, 15m lifetime, verified against live JWKS
signature.
- Exact claim bindings are asserted by KeyCape without any value or token logged.
negative:
- >-
A namespace outside the approved ClusterSecretStore condition cannot use this
store to read the path.
- >-
A service account outside external-secrets/external-secrets cannot authenticate
through role external-secrets-keycape-secrets-engine-approval.
- >-
The role cannot read the sibling approval-engine operator-client path, any
parent listing, or any other platform workload path.
- >-
The secrets-engine-approval client is denied scopes outside approval:read and
approval:consume, and human identities are denied approval:consume.
activation_conditions:
- >-
KeyCape and railiance-platform agree the single attended rollout window in
docs/credential-lane-designs/keycape-approval-clients.md; the KeyCape image that
reads both environment names is built and pinned but not yet deployed.
- >-
Attended first provision runs only through the governed
openbao-platform-admin-login lane (founder_required, attended OIDC via
netkingdom role=platform-admin), with a unique receipt path and no value in
chat, command arguments or shell history.
- >-
Policy, Kubernetes auth role and ClusterSecretStore applied before the
ExternalSecret; sync confirmed before the KeyCape image is rolled out.
- >-
Positive and negative results recorded with non-secret request ids or
timestamps.
evidence: []
lifecycle:
deactivate: >-
KeyCape disables the secrets-engine-approval registration; platform detaches
the policy from role external-secrets-keycape-secrets-engine-approval and
removes the ExternalSecret. The KV version is retained until KeyCape confirms
disablement.
rotate: >-
KeyCape mints a replacement client secret; platform writes the new KV version
under the same attended authority; ESO refresh delivers it and KeyCape restarts
or re-reads. Rotation is independent of the operator client.
compromised: >-
Disable the registration at KeyCape first (that is what stops token issuance),
then rotate the KV version and confirm no other namespace consumed the store.
state_hub:
workplan_id: RPF-WP-0035
task_id: RPF-WP-0035-T05
related_message: 278a3ebe-b529-49f6-bd1a-e3ebcf318260
related_decision: 5ed3fb35-eca9-413a-82b9-95171ba85bf6

View file

@ -0,0 +1,148 @@
id: CCR-2026-0018
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: KeyCape verifier custody for the approval-engine-operator confidential client
status: proposed
created: '2026-09-08'
updated: '2026-09-08'
requester:
agent: claude
reason: >-
Second of the two registrations in KEY-WP-0013-T02 (State Hub message
278a3ebe-b529-49f6-bd1a-e3ebcf318260): client approval-engine-operator, subject
service:approval-engine-operator, audience approval-engine, tenant:platform per
resolved decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6, 15m token lifetime.
Scopes are approval:create, read, approve, revoke, supersede, observe and emit,
explicitly without approval:consume. KeyCape owns registration and issuance;
this request establishes only custody and the KeyCape-side delivery.
review:
required: true
required_approvers:
- platform-operator
- key-cape-owner
comments:
- at: '2026-09-08'
reviewer: railiance-platform (codex/claude)
decision: paths_confirmed_field_corrected
comment: >-
KV path platform/workloads/approval-engine/operator-client is confirmed
unchanged and conforms to the platform/workloads/<workload>/<lane>
convention. Field client_secret is corrected to CLIENT_SECRET for the same
uppercase convention and validator constraint as CCR-2026-0017. Kubernetes
Secret sso/keycape-approval-engine-operator-client with key client-secret and
the KeyCape environment name are confirmed as proposed.
target:
domain: financials
tenant: platform
workload: approval-engine
environment: production
purpose: >-
Hold the approval-engine-operator confidential client secret in platform
custody and project it into the KeyCape runtime so KeyCape can verify presented
client_credentials for the operator client.
openbao:
mount: platform
kv_path: platform/workloads/approval-engine/operator-client
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-keycape-approval-engine-operator
policy_file: openbao/policies/workload-kv-read-keycape-approval-engine-operator.hcl
auth:
method: kubernetes
mount: kubernetes
role: external-secrets-keycape-approval-engine-operator
bound_claims:
service_account_names:
- external-secrets
service_account_namespaces:
- external-secrets
bound_claims_confirmed: true
policies:
- workload-kv-read-keycape-approval-engine-operator
ttl: 15m
access_frontdoor:
type: ops-warden
catalog_id: keycape-approval-engine-operator-client
selector: KeyCape approval-engine-operator confidential client secret
command: warden access keycape-approval-engine-operator-client --fetch CLIENT_SECRET
resolvable: false
readiness: pending-review
delivery:
surface: external-secrets
target: >-
ClusterSecretStore openbao-keycape-approval-engine-operator, limited to
namespace sso, to ExternalSecret sso/keycape-approval-engine-operator-client and
Secret sso/keycape-approval-engine-operator-client with key client-secret.
KeyCape resolves it as KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET through a
secretKeyRef. Manifests:
argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml
and keycape-approval-clients.externalsecrets.yaml.
risk:
classification: high
notes:
- >-
This client carries the widest approval scope set in the pair — create,
approve, revoke and supersede. A leak allows forging approval lifecycle actions,
which is a stronger outcome than the read/consume client in CCR-2026-0017.
- >-
The absence of approval:consume is a deliberate separation of duties. Any later
request to add consume to this client is a new lane decision, not a scope edit.
- >-
Kept on its own policy, role and store so it can be revoked independently of the
secrets-engine-approval client.
- >-
Compromise response is KeyCape disabling the registration plus rotation of this
KV version; approval actions already emitted are not retracted by rotation and
need approval-engine's own audit review.
verification:
positive:
- >-
The ExternalSecret in namespace sso syncs CLIENT_SECRET to Secret key
client-secret without printing the value.
- >-
The KeyCape build issues a token for subject service:approval-engine-operator
with audience approval-engine, tenant:platform and 15m lifetime, verified
against live JWKS signature.
negative:
- >-
A namespace outside the approved ClusterSecretStore condition cannot use this
store to read the path.
- >-
A service account outside external-secrets/external-secrets cannot authenticate
through role external-secrets-keycape-approval-engine-operator.
- >-
The role cannot read the sibling secrets-engine approval-client path, any parent
listing, or any other platform workload path.
- >-
The operator client is denied approval:consume, and denial is observed rather
than assumed.
activation_conditions:
- >-
Same single attended rollout window and prepared-but-undeployed KeyCape image as
CCR-2026-0017; see docs/credential-lane-designs/keycape-approval-clients.md.
- >-
Attended first provision only through openbao-platform-admin-login
(founder_required, attended OIDC via netkingdom role=platform-admin) with a
unique receipt path.
- >-
Policy, Kubernetes auth role and ClusterSecretStore applied before the
ExternalSecret; sync confirmed before the KeyCape rollout.
- Positive and negative results recorded with non-secret request ids or timestamps.
evidence: []
lifecycle:
deactivate: >-
KeyCape disables the approval-engine-operator registration; platform detaches the
policy from role external-secrets-keycape-approval-engine-operator and removes
the ExternalSecret.
rotate: >-
KeyCape mints a replacement client secret; platform writes the new KV version
under the same attended authority. Rotation is independent of CCR-2026-0017.
compromised: >-
Disable the registration at KeyCape, rotate the KV version, and refer emitted
approval actions to approval-engine for audit review.
state_hub:
workplan_id: RPF-WP-0035
task_id: RPF-WP-0035-T05
related_message: 278a3ebe-b529-49f6-bd1a-e3ebcf318260
related_decision: 5ed3fb35-eca9-413a-82b9-95171ba85bf6

View file

@ -2,13 +2,16 @@
Reviewed against local owner source on 2026-09-05. These are proposed designs, Reviewed against local owner source on 2026-09-05. These are proposed designs,
not approvals or executable CCRs. No live credentials or OpenBao objects were not approvals or executable CCRs. No live credentials or OpenBao objects were
created. Files here are deliberately outside the production CCR/policy scan. created. Files here are deliberately outside the production CCR/policy scan, except the
KeyCape approval-client admission below, which carries executable CCRs
(CCR-2026-0017/0018) that remain `proposed` and unapplied.
| Design | Design workplan / current implementation task | Consumer dependency | Main unresolved input | | Design | Design workplan / current implementation task | Consumer dependency | Main unresolved input |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [Secrets-engine service JWT](secrets-engine-service-jwt.md) | RPF-WP-0032 (finished) / RPF-WP-0035-T02 | SECRETS-WP-0008-T06; SECRETS-WP-0007-T04 | Actual issuer/JWKS, live registration and scoped execution authority | | [Secrets-engine service JWT](secrets-engine-service-jwt.md) | RPF-WP-0032 (finished) / RPF-WP-0035-T02 | SECRETS-WP-0008-T06; SECRETS-WP-0007-T04 | Actual issuer/JWKS, live registration and scoped execution authority |
| [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 |
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,158 @@
# KeyCape approval-engine client custody admission
Answer to KEY-WP-0013-T02 (State Hub message
`278a3ebe-b529-49f6-bd1a-e3ebcf318260`, KeyCape packet
`key-cape: docs/approval-engine-provisioning-request.yaml`). Tracked here as
RPF-WP-0035-T05. Requests: [CCR-2026-0017](../../credential-change-requests/CCR-2026-0017-keycape-secrets-engine-approval-client.yaml),
[CCR-2026-0018](../../credential-change-requests/CCR-2026-0018-keycape-approval-engine-operator-client.yaml).
Nothing below is an activation. Both CCRs are `proposed`; no value has been
generated, no KV version written, no manifest applied. Source preparation is not
live completion.
## a) Custody paths and field names
| Item | Proposed by KeyCape | Admitted | Change |
| --- | --- | --- | --- |
| Client 1 path | `platform/workloads/secrets-engine/approval-client` | same | confirmed |
| Client 2 path | `platform/workloads/approval-engine/operator-client` | same | confirmed |
| Field | `client_secret` | `CLIENT_SECRET` | **corrected** |
Both paths are correct as proposed. They match the
`platform/workloads/<workload>/<lane>` shape that `scripts/credential-change.py`
enforces for the `platform` mount and that every admitted platform lane uses
(`platform/workloads/core-hub/runtime`, `platform/workloads/user-engine/runtime`,
`platform/workloads/telemetry/grafana-admin`). Naming each path under the client's
own workload prefix rather than under KeyCape is also right: the credential
belongs to the client identity; KeyCape is its verifier, not its owner.
The field name is the one correction. KV field names on this mount are uppercase
(`PROVISIONER_TOKEN`, `CORE_HUB_API_TOKEN`, `NPM_AUTH_TOKEN`), and the CCR
validator rejects anything that does not match `^[A-Z][A-Z0-9_]*$`. So the
OpenBao field is `CLIENT_SECRET`. This does not change the Kubernetes Secret key
or the environment name — see below.
## b) Kubernetes delivery references
Confirmed as proposed, with the mapping made explicit:
| | Client 1 | Client 2 |
| --- | --- | --- |
| Secret | `sso/keycape-secrets-engine-approval-client` | `sso/keycape-approval-engine-operator-client` |
| Secret key | `client-secret` | `client-secret` |
| KeyCape env | `KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET` | `KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET` |
| ClusterSecretStore | `openbao-keycape-secrets-engine-approval` | `openbao-keycape-approval-engine-operator` |
| OpenBao policy | `workload-kv-read-keycape-secrets-engine-approval` | `workload-kv-read-keycape-approval-engine-operator` |
| Kubernetes auth role | `external-secrets-keycape-secrets-engine-approval` | `external-secrets-keycape-approval-engine-operator` |
`OpenBao CLIENT_SECRET``ExternalSecret secretKey client-secret`
`secretKeyRef` → env. That shape is the one already running in `sso`: the live
`keycape` pod on `main-153258b` resolves
`KEYCAPE_RAPP_QONTO_CLIENT_SECRET` from `secretKeyRef{name:
keycape-rapp-qonto-client, key: client-secret}`. Read-only observation
2026-09-08, and it confirms KeyCape's own statement that the image carries only
that one client reference.
Manifests are written and client-validated but unapplied:
`argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml`
and `keycape-approval-clients.externalsecrets.yaml`.
Three deliberate choices, so they are not read as accidents:
- **Two stores, two roles, two policies.** The scope sets differ — the operator
client can create, approve, revoke and supersede; the secrets-engine client can
read and consume. Separate identities mean either can be revoked without
touching the other. A shared store would erase that boundary.
- **Kubernetes auth, not AppRole.** The neighbouring `sso` store
(`openbao-sso-user-engine-runtime`) and `openbao-rapp-qonto` use AppRole for
cutover reasons. New same-cluster lanes use Kubernetes auth against
`external-secrets/external-secrets`, as `openbao-core-hub-runtime` does, so
there is no AppRole secret to hold in custody.
- **Verifier side only.** This admits the copy KeyCape needs to verify presented
credentials. It does **not** admit the client-side lane by which secrets-engine
or the approval-engine operator would read its own copy. That is a separate
request with its own consumer gate, and no part of it is implied here.
`creationPolicy: Owner` means the ExternalSecret creates the Secret. Do not
pre-create either Secret by hand.
## c) Authority for the attended first provision
Your reading is right, and your refusal to act on the `warden plan` result was
also right: a generic database-lane match is not authorization for this path.
The authority is the governed **`openbao-platform-admin-login`** lane —
`founder_required`, attended OIDC through `bao login -method=oidc -path=netkingdom
role=platform-admin`, invoked as:
```bash
warden access openbao-platform-admin-login --exec -- <command>
```
with a unique `--receipt` path. Nothing else in this repo carries write authority
against `platform/workloads/`. Two constraints that come with it:
- It is a **founder-attended** session. No agent, including this one, can execute
it unattended, and neither KeyCape nor railiance-platform can self-authorize it.
- The owner command is never run directly, only through the `warden access
--exec` envelope, so output containment applies (RPF-WP-0017). No value in chat,
command arguments, shell history or logs; receipts carry metadata only.
Custody writes are seeded with compare-and-set zero on first version.
## d) Rollout window
Agreed in shape: **one attended session**, everything below in order, no partial
landing. Registrations and the reading build deploy together because a KeyCape
image that reads an absent env fails closed, and a delivered secret with no build
to read it is custody without a consumer.
Prerequisites before the window opens:
1. Both CCRs approved by platform-operator and the KeyCape owner.
2. KeyCape image that reads both environment names is **built and pinned, not
deployed**. `main-153258b` is not that image.
3. Founder available for the attended OpenBao session.
4. **The Authelia issuer precondition from KeyCape message
`c8b1ad10-dae8-48fb-a0ea-7e2a101c54bf` is settled first.** The same rollout
that lands these clients also lands upstream ID-token verification that fails
closed on issuer mismatch. Confirm the `iss` value and pin `authelia.issuer`
in the KeyCape config secret before, not during, this window. A broken human
login and a broken client registration arriving together would be very hard to
tell apart.
In-window order:
1. Apply the two policies and the two Kubernetes auth roles (attended, via the
authority in (c)).
2. Seed both KV paths, CAS=0, with the client secrets KeyCape mints. Record
metadata-only receipts.
3. Apply the two ClusterSecretStores; confirm both reach `Valid`.
4. Apply the two ExternalSecrets; confirm `SecretSynced` and that both Secrets
exist with key `client-secret`. No value is printed at any point.
5. Roll out the KeyCape image.
6. KeyCape runs its verification: live JWKS signature, exact claim bindings
(subject, tenant `tenant:platform`, audience `approval-engine`, 15m lifetime),
and denial of excess scopes — operator `consume` and human `consume` — without
logging any value or token.
7. Platform runs its negative checks: cross-path read denied, wrong service
account denied, out-of-namespace store use denied, parent listing denied.
8. Both sides record evidence into the CCRs and a receipt into the workplan.
Rollback: revert the KeyCape image to `main-153258b`, delete the two
ExternalSecrets, detach the policies from the roles. The KV versions are retained
until KeyCape confirms whether the registrations stay; if they are abandoned,
KeyCape disables the registrations first and platform then destroys the versions.
**Date is not set here.** It depends on the founder's availability, which is not
mine to schedule. Propose a slot from 2026-09-10 and I will confirm the
platform side; the window needs roughly 6090 minutes with both owners present.
## What is not admitted
- No client-side (secrets-engine / approval-engine) read lane.
- No adoption of the hand-created `sso/keycape-rapp-qonto-client` Secret into ESO.
- No change to the separate `codex-railiance-platform` JWT issuance contract
(KEY-WP-0009-T04) or to RPF-WP-0035-T02's service-JWT lane.
- No approval for `approval:consume` on the operator client, now or by later
scope edit. Adding it is a new lane decision.

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

@ -11,7 +11,7 @@ plans is not a count of missing implementations or independent incidents.
| [RPF-WP-0029](RPF-WP-0029-backup-credential-default-removal.md) | Backup cutover and full offsite application recovery complete; old share invalidation receipt remains | S3 retains custody acceptance; S1 and forge own their backup execution. | | [RPF-WP-0029](RPF-WP-0029-backup-credential-default-removal.md) | Backup cutover and full offsite application recovery complete; old share invalidation receipt remains | S3 retains custody acceptance; S1 and forge own their backup execution. |
| [RPF-WP-0025](RPF-WP-0025-openbao-operator-only-access.md) | Private OpenBao access; fresh attended callback/login then guarded retraction | Coordinate package, issuer, tunnel and DNS owners; keep the window separate. | | [RPF-WP-0025](RPF-WP-0025-openbao-operator-only-access.md) | Private OpenBao access; fresh attended callback/login then guarded retraction | Coordinate package, issuer, tunnel and DNS owners; keep the window separate. |
| [RPF-WP-0015](RPF-WP-0015-audit-core-custody-and-recovery-coordination.md) | Two prepared recovery exercises; registered load driver exists; fresh sender/window/abort approvals and custody readiness remain | S3 contributes lease/ESO and snapshot/unseal proof; S1/S2 and audit-core execute their parts. | | [RPF-WP-0015](RPF-WP-0015-audit-core-custody-and-recovery-coordination.md) | Two prepared recovery exercises; registered load driver exists; fresh sender/window/abort approvals and custody readiness remain | S3 contributes lease/ESO and snapshot/unseal proof; S1/S2 and audit-core execute their parts. |
| [RPF-WP-0035](RPF-WP-0035-credential-lane-implementation.md) | Two remaining lanes: secrets-engine JWT and Fluid operator KV | Signing T04 is complete; JWT and Fluid retain separate issuer/consumer gates. | | [RPF-WP-0035](RPF-WP-0035-credential-lane-implementation.md) | Three remaining lanes: secrets-engine JWT, Fluid operator KV, KeyCape approval clients | Signing T04 is complete; T05 admission answered and awaiting owner approval plus a founder-attended window. |
| [RPF-WP-0036](RPF-WP-0036-platform-service-assurance.md) | Implemented local assurance/admission; waits for recurring restore evidence, Q2 reception and owner handoff | Run the assurance commands; live acceptance and external ownership remain gated. | | [RPF-WP-0036](RPF-WP-0036-platform-service-assurance.md) | Implemented local assurance/admission; waits for recurring restore evidence, Q2 reception and owner handoff | Run the assurance commands; live acceptance and external ownership remain gated. |
| [RPF-WP-0038](RPF-WP-0038-forgejo-scaleway-primary-coverage.md) | Native backup, full Scaleway archive recovery and 273 MiB Nextcloud essentials recovery verified; scheduled tier cutover remains | Bind recurring caller/dependencies, verified inventory, quota checks and separate owner retention. | | [RPF-WP-0038](RPF-WP-0038-forgejo-scaleway-primary-coverage.md) | Native backup, full Scaleway archive recovery and 273 MiB Nextcloud essentials recovery verified; scheduled tier cutover remains | Bind recurring caller/dependencies, verified inventory, quota checks and separate owner retention. |

View file

@ -7,7 +7,7 @@ repo: railiance-platform
status: blocked status: blocked
owner: codex owner: codex
created: "2026-09-05" created: "2026-09-05"
updated: "2026-09-06" updated: "2026-09-08"
related: related:
- RPF-WP-0032 - RPF-WP-0032
- RPF-WP-0033 - RPF-WP-0033
@ -141,6 +141,51 @@ chart commit `49e3182`, Helm revision 59. No repository rename executed.
Evidence: `docs/evidence/RPF-WP-0035-T04-signing-activation-2026-09-05.json`; Evidence: `docs/evidence/RPF-WP-0035-T04-signing-activation-2026-09-05.json`;
closure: `history/2026-09-05-preflight-signing-activation-complete.md`. closure: `history/2026-09-05-preflight-signing-activation-complete.md`.
## Admit KeyCape approval-engine client custody and delivery
```task
id: RPF-WP-0035-T05
status: wait
priority: high
```
Answers KEY-WP-0013-T02 (State Hub message
`278a3ebe-b529-49f6-bd1a-e3ebcf318260`). Admission:
`docs/credential-lane-designs/keycape-approval-clients.md`; requests
`CCR-2026-0017` (secrets-engine-approval) and `CCR-2026-0018`
(approval-engine-operator).
Platform owns the two custody paths, exact-path read policies, Kubernetes auth
roles and the KeyCape-side ESO delivery. KeyCape owns the client registrations,
issuance, claim set and disablement; approval-engine owns approval semantics and
audit of emitted actions.
2026-09-08 admission response: both proposed KV paths confirmed unchanged; field
`client_secret` corrected to `CLIENT_SECRET` for the uppercase KV convention and
CCR validator. Kubernetes delivery references confirmed against the live `sso`
namespace, which already resolves `KEYCAPE_RAPP_QONTO_CLIENT_SECRET` through the
same `secretKeyRef{key: client-secret}` shape on image `main-153258b`. Attended
authority named as the governed `openbao-platform-admin-login` lane
(`founder_required`); KeyCape's refusal to treat its generic `warden plan`
database match as authorization was correct. Rollout agreed as one attended
session with the KeyCape reading build pinned but undeployed, ordered after the
Authelia issuer precondition in message `c8b1ad10-dae8-48fb-a0ea-7e2a101c54bf`.
Policies, ClusterSecretStores and ExternalSecrets are written and validated,
none applied.
**Unblock:** owner approval of both CCRs, a KeyCape image that reads both
environment names, the settled Authelia `iss` pin, and a founder-attended window
date. Verifier-side custody only — no client-side read lane is admitted, and no
scope beyond the two declared sets.
**Done when:** both CCRs are approved, the attended window seeds both KV paths
with CAS=0, policies/roles/stores/ExternalSecrets apply and sync, the KeyCape
build resolves both environment names, KeyCape evidences live JWKS signature and
exact claim bindings with excess scopes denied (operator `consume`, human
`consume`), platform evidences cross-path/wrong-identity/out-of-namespace denial,
and every receipt is metadata-only. If either registration is abandoned, KeyCape
disables it before the KV version is destroyed.
## Dependency review — 2026-09-06 ## Dependency review — 2026-09-06
SECRETS-WP-0008-T02 now records the local PIP claim/validation join implemented SECRETS-WP-0008-T02 now records the local PIP claim/validation join implemented