2026-09-09 07:06:05 +02:00
|
|
|
# Approval consumer identity
|
|
|
|
|
|
|
|
|
|
Implemented for SECRETS-WP-0009-T03 on 2026-09-09. KeyCape's live verifier
|
|
|
|
|
registration is accepted under KEY-WP-0013-T02 and CCR-2026-0017; this consumer
|
|
|
|
|
implementation does not grant retrieval of its confidential client secret.
|
|
|
|
|
|
|
|
|
|
The CLI exchanges a separately supplied `secrets-engine-approval` client secret
|
|
|
|
|
for a fresh access token immediately before each approval HTTP request. Claim
|
|
|
|
|
requests ask for `approval:read`; consume requests ask for `approval:consume`.
|
|
|
|
|
The token exists only in memory for that request. There is no refresh token,
|
|
|
|
|
access-token file, background renewal, retry loop, or fallback identity.
|
|
|
|
|
|
|
|
|
|
| Input | Value |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE` | Explicit, protected temporary file outside Git, no group/other access |
|
|
|
|
|
| `SECRETS_ENGINE_KEYCAPE_ISSUER` | `https://kc.coulomb.social` |
|
|
|
|
|
| `SECRETS_ENGINE_KEYCAPE_TOKEN_URL` | `https://kc.coulomb.social/token` |
|
|
|
|
|
| `SECRETS_ENGINE_APPROVAL_URL` | Admitted HTTPS endpoint or literal loopback owner tunnel |
|
|
|
|
|
| `SECRETS_ENGINE_APPROVAL_TOKEN_FILE` | Unset when using the client-secret provider |
|
|
|
|
|
|
|
|
|
|
The existing token-file provider remains explicitly selectable by configuring
|
|
|
|
|
only `SECRETS_ENGINE_APPROVAL_TOKEN_FILE`. Configuring both providers fails
|
|
|
|
|
before either credential is read. An exchange failure never reads that file,
|
|
|
|
|
`BAO_TOKEN`, or `SECRETS_ENGINE_KEYCAPE_CLIENT_SECRET_FILE` (the different
|
|
|
|
|
OpenBao identity's credential).
|
|
|
|
|
|
|
|
|
|
The approval profile fixes client `secrets-engine-approval`, audience
|
|
|
|
|
`approval-engine`, subject `service:secrets-engine`, tenant `tenant:platform`,
|
|
|
|
|
role `secrets-engine`, service principal, empty groups and one requested scope.
|
|
|
|
|
It enforces RS256 shape, at most 900 seconds of life, strict expiry and at most
|
|
|
|
|
30 seconds of future issue time. The KeyCape assurance shape is
|
|
|
|
|
`level: aal1`, `methods: [client_secret]`, `mfa: false`, `source: key-cape`.
|
|
|
|
|
The same wire-shape correction applies to the existing OpenBao exchange; its
|
|
|
|
|
client, audience, tenant and authorization remain separate.
|
|
|
|
|
|
|
|
|
|
Claim parsing is a preflight, not signature verification or authorization.
|
|
|
|
|
Approval Engine verifies signature, issuer, audience and claims against JWKS.
|
|
|
|
|
The existing exact-action claim, access-engine decision and atomic consume gates
|
|
|
|
|
still precede a production OpenBao operation.
|
|
|
|
|
|
|
|
|
|
Credentials are sent through a non-redirecting transport. The KeyCape endpoint
|
|
|
|
|
must be the configured HTTPS issuer's `/token`. The new consumer refuses
|
|
|
|
|
cluster Service DNS from this workstation and plaintext non-loopback approval
|
|
|
|
|
endpoints. A literal loopback address alone cannot identify the responder: the
|
|
|
|
|
operator must establish a `kubectl port-forward` for the admitted cluster and
|
|
|
|
|
named Approval Engine pod. No tunnel or cluster deployment is created here.
|
|
|
|
|
|
|
|
|
|
## Reproducible component exercise
|
|
|
|
|
|
|
|
|
|
Run from this repository with Docker available and the owner source checkouts:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
uv run --with 'PyJWT[crypto]>=2.7,<3' python tools/exercise_approval_identity.py \
|
|
|
|
|
--keycape-source /home/worsch/key-cape \
|
|
|
|
|
--approval-engine-source /home/worsch/approval-engine \
|
|
|
|
|
--receipt /tmp/<new-metadata-receipt>.json
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The exercise starts the immutable admitted KeyCape image behind local HTTPS,
|
|
|
|
|
uses synthetic signing keys/client secrets, and serves the real Approval Engine
|
|
|
|
|
source with its JWT/JWKS verifier and SQLite store. It drives the actual Secrets
|
|
|
|
|
Engine production gate, with only the PDP represented by a sequencing double.
|
|
|
|
|
Operator create/approve, consumer claim/consume, same-digest retry, different
|
|
|
|
|
digest, spent claim, wrong action, wrong secret and operator-scope denial are
|
|
|
|
|
checked. All temporary processes and synthetic credentials are removed.
|
|
|
|
|
|
|
|
|
|
This exercise caught the previous fixture-only `aal`/`method` assumption:
|
|
|
|
|
the real issuer has always emitted `level`/`methods`. The regression suite now
|
|
|
|
|
refuses that obsolete shape. See
|
|
|
|
|
[the component receipt](evidence/2026-09-09-approval-identity-exercise.json).
|
|
|
|
|
|
|
|
|
|
## Live handoff
|
|
|
|
|
|
|
|
|
|
RPF-WP-0035-T06 owns the separate client-side read admission for the existing
|
|
|
|
|
`platform/workloads/secrets-engine/approval-client`, field `CLIENT_SECRET`.
|
|
|
|
|
The admission must name the actual consumer/placement, bounded reader, protected
|
|
|
|
|
temporary delivery and cleanup, refusal/revocation checks and lifecycle owner.
|
|
|
|
|
Do not use the verifier's `sso` Secret or reseed version-1 custody.
|
|
|
|
|
|
|
|
|
|
Approval Engine's operator is a separate identity and read lane. Audit receiver
|
|
|
|
|
and sender custody remains AUDIT-WP-0009-T09 / APPROVAL-WP-0002-T01. The live
|
|
|
|
|
Approval Engine deployment and native OpenBao delivery remain open. Synthetic
|
|
|
|
|
acceptance grants no production access, action approval or model spending.
|
2026-09-09 07:15:22 +02:00
|
|
|
|
|
|
|
|
The newly consumed FLEX-DEC-2026-012 return remains SECRETS-WP-0008-T02:
|
|
|
|
|
adopt submitted_request_digest and the evaluator-to-evaluator approval digest
|
|
|
|
|
join before live activation. The current PDP double does not validate it.
|