Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
183 lines
9.9 KiB
YAML
183 lines
9.9 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: verified
|
|
created: '2026-09-08'
|
|
updated: '2026-09-09'
|
|
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 \u2014 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.'
|
|
- at: '2026-09-08T23:05:19+00:00'
|
|
reviewer: User (platform-operator; explicit session approval)
|
|
decision: approved
|
|
comment: User answered "I approve, go on." to the explicit two-CCR approval question
|
|
naming both required roles. Scope remains verifier-side custody and its attended
|
|
rollout, as reviewed in docs/credential-lane-designs/keycape-approval-clients-review.md.
|
|
No client-side read or factory spending grant.
|
|
- at: '2026-09-08T23:05:19+00:00'
|
|
reviewer: User (key-cape-owner; explicit session approval)
|
|
decision: approved
|
|
comment: User answered "I approve, go on." to the explicit two-CCR approval question
|
|
naming both required roles. Scope remains verifier-side custody and its attended
|
|
rollout, as reviewed in docs/credential-lane-designs/keycape-approval-clients-review.md.
|
|
No client-side read or factory spending grant.
|
|
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:
|
|
- at: '2026-09-09T00:14:27+00:00'
|
|
actor: the-custodian (codex)
|
|
kind: verifier_custody_and_rollout
|
|
result: passed
|
|
details:
|
|
- Named user approval recorded for platform-operator and key-cape-owner on 2026-09-09.
|
|
- Initial CAS=0 custody created version 1. Verified rollback/resume retained the
|
|
same values and versions.
|
|
- Both stores Valid and ExternalSecrets SecretSynced; exact native read, sibling/listing
|
|
and wrong service account/namespace/store-use denials passed; reader revocation
|
|
and coding-agent deny precedence passed.
|
|
- Pinned image 7ff54c54e63e has one ready replica; protected previous config/image
|
|
retained and signing key unchanged.
|
|
- Both clients passed live JWKS and exact audience/subject/tenant/role/scope/900-second
|
|
lifetime checks, excess-scope and wrong-secret denials; human client consume
|
|
denied.
|
|
- Native verifier from pinned image ran in the attended owner process with memory-only
|
|
credentials. Pod network policy remains unchanged. Future iat bound is the existing
|
|
30-second contract; expiry has zero leeway.
|
|
- Fresh post-rollout OpenBao OIDC login succeeded and self-revoked. No client-side
|
|
fetch front door or factory spending was admitted.
|
|
- 'Receipt: docs/evidence/2026-09-09-keycape-verifier-admission.json'
|
|
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:
|
|
decision_id: b533a271-b704-4c5c-98a2-9a5951aadfb6
|
|
decision_api_url: http://127.0.0.1:8000/decisions/b533a271-b704-4c5c-98a2-9a5951aadfb6
|
|
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
|