railiance-platform/credential-change-requests/CCR-2026-0017-keycape-secrets-engine-approval-client.yaml
codex f3ba7ca882
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Admit KeyCape approval-engine client custody paths and delivery
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
2026-09-08 14:53:32 +02:00

171 lines
7.7 KiB
YAML

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