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
This commit is contained in:
parent
805e0e5a2b
commit
f3ba7ca882
10 changed files with 651 additions and 3 deletions
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
158
docs/credential-lane-designs/keycape-approval-clients.md
Normal file
158
docs/credential-lane-designs/keycape-approval-clients.md
Normal 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 60–90 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.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
path "platform/data/workloads/approval-engine/operator-client" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "platform/metadata/workloads/approval-engine/operator-client" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
path "platform/data/workloads/secrets-engine/approval-client" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "platform/metadata/workloads/secrets-engine/approval-client" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
|
@ -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. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue