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

@ -2,13 +2,16 @@
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
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 |
| --- | --- | --- | --- |
| [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 |
| [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
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.