secrets-engine/docs/approval-service-auth.md

150 lines
8.8 KiB
Markdown
Raw Normal View History

# 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 \
--flex-auth-source /home/worsch/flex-auth \
--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 through the actual locally compiled Flex Auth evaluator.
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.
### CCR-2026-0019 workstation procedure — consumer return, 2026-09-10
This source review answers Platform's procedure request
`fbf434cf-3f68-4d11-ae98-bb3798e2c5a5`. The current consumer accepts the following
attended file lifetime. It does not confirm an operator group or admit a reader:
CCR-2026-0019 remains in flight, its front door unresolvable, and no value was read.
1. After the exact group, role and reader admission are accepted, use an
operator-owned runtime directory outside every Git worktree, with canonical
non-symlink parents and mode 0700. Prefer the workstation's private runtime
tmpfs; stop if it is unavailable rather than silently choosing a shared or
backed-up location. Set `umask 077`. Create a unique session subdirectory and
a new regular `client-secret` file owned by that operator, mode 0600. Check
ownership/mode/location without displaying file contents.
2. Install the session wrapper's EXIT cleanup before delivery. Its INT and TERM
handlers must exit through that cleanup. The admitted delivery owner fills
only this file from `platform/workloads/secrets-engine/approval-client`, field
`CLIENT_SECRET`, existing version 1. Use the reviewed front door after it is
resolvable; do not replace its pending admission with a direct raw vault read,
the verifier's Kubernetes Secret, a sibling identity or an interactive paste.
3. Supply only the absolute file path as
`SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE` to the intended Secrets Engine
CLI. Keep the file for both claim and consume exchanges in that one attended
operation. Unset `SECRETS_ENGINE_APPROVAL_TOKEN_FILE`; configure the existing
issuer/token endpoint and the admitted Approval Engine endpoint or its
owner-bound tunnel. Disable shell tracing and credential-bearing HTTP debug
logs. The file value belongs in neither argv nor an environment variable.
4. On success, refusal, exception or interruption, cleanup unsets the path,
removes that exact file, then removes the now-empty session directory and
verifies both are absent. Record only cleanup status and non-secret request
IDs. Do not recursively remove a shared directory. SIGKILL, host failure or a
failed trap requires attended residual-file inspection/removal before reuse;
automatic cleanup is not provided by the current consumer. Unlink is not a
claim of secure erasure or token revocation. Tmpfs avoids a persistent copy
but does not replace host custody controls.
The implemented `read_strict_token_file` rejects group/other-accessible files,
files inside Git worktrees and empty inputs. It does not establish parent-path
ownership, create or delete this file, or prove session cleanup. Those are the
explicit delivery/operator responsibilities above, retained in the CCR's risk
section. `KeyCapeServiceAuthProvider.exchange` rereads the file before each
request and holds the resulting token in memory. Removing it prevents a later
exchange; already-issued tokens retain their bounded lifetime. Compromise uses
KeyCape registration disablement and the CCR's coordinated lifecycle procedure.
Required live evidence remains: exact authorized identity and mode/location
checks, one scoped exchange/claim/consume result, wrong-group and sibling-path
denials, and cleanup after both success and refusal. Existing synthetic component
evidence verifies the consumer behavior; it is not that live reader receipt.
Real human approval follows INFD-WP-0001-T07/T08's reviewed PKCE client; a future
service requester needs its own narrow registration. The unpresented operator
reader CCR-2026-0020 was cancelled on owner withdrawal and is not a dependency.
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.
SECRETS-WP-0008-T02 now supplies the implemented FLEX-DEC-2026-012 replay
and approval join. Verify the deployed PDP implements it before live activation.
### 2026-09-09 replay correction accepted locally
The updated exercise requires `--flex-auth-source` and compiles its actual Go
evaluator. It no longer substitutes a decision double. Thirteen checks cover
signed issuer/claim/consume, registry precedence, changed submission replay,
wrong action, same/different consume digest and actual dual-control policy.
See `docs/evidence/2026-09-09-replay-contract-exercise.json`. Earlier nine-check
PDP-double evidence remains historical. Live custody/deployment gates still apply.