Harden the PEP harness and KeyCape registration request
Close remaining in-repo APPROVAL-WP-0002 gaps: drive GH-DEC-2026-003 against the real HTTP surface, fail closed on JWT/human-consume/static-token paths, treat audit 200 duplicates as drained, and ask KeyCape for the production audience and client grants. Assistant: grok Assistant-Session: 01a06253-e557-7971-93d9-4f4c2cfbf455
This commit is contained in:
parent
2bd2d19a98
commit
2370f69927
11 changed files with 588 additions and 46 deletions
|
|
@ -32,3 +32,8 @@ only verifies and enforces them. Requested registrations are:
|
|||
|
||||
Client credentials belong in OpenBao/operator custody and must not be placed in
|
||||
manifests, logs, State Hub, or this repository.
|
||||
|
||||
KeyCape's OpenBao service-auth contract currently emits `aud` as the OAuth
|
||||
`clientId`. That pattern must not be reused here. Tokens presented to this API
|
||||
MUST have resource-server audience `approval-engine`. Requested non-secret
|
||||
client fragments are in `docs/keycape-service-registrations.md`.
|
||||
|
|
|
|||
67
docs/keycape-service-registrations.md
Normal file
67
docs/keycape-service-registrations.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Requested KeyCape registrations
|
||||
|
||||
Status: requested by `APPROVAL-WP-0002-T01`. Non-secret. KeyCape owns issuance,
|
||||
client disablement, and the exact claim contract. This file is a consumer
|
||||
request, not a live registration.
|
||||
|
||||
Tokens presented to approval-engine MUST use resource-server audience
|
||||
`approval-engine`. Do not reuse the OpenBao service-auth pattern that sets
|
||||
`aud` to the OAuth `clientId`.
|
||||
|
||||
Required claims remain those in `docs/caller-authentication.md`: `iss`, `sub`,
|
||||
`aud`, `exp`, `iat`, `principal_type`, `tenant`, `roles`, `scope`, `assurance`.
|
||||
`principal_type` for consume callers must be `service` or `agent`.
|
||||
|
||||
## Resource server
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Audience | `approval-engine` |
|
||||
| Issuer | the deployed KeyCape issuer (manifest uses `https://auth.netkingdom.local`) |
|
||||
| JWKS | `GET /jwks` on the KeyCape service |
|
||||
| Scopes | `approval:create`, `approval:read`, `approval:approve`, `approval:revoke`, `approval:supersede`, `approval:consume`, `approval:observe`, `approval:emit` |
|
||||
|
||||
## Clients
|
||||
|
||||
Confidential client secrets stay in OpenBao/operator custody. `secretRef`
|
||||
names below are placeholders for that custody path.
|
||||
|
||||
```yaml
|
||||
clients:
|
||||
- clientId: secrets-engine-approval
|
||||
displayName: secrets-engine PEP consume client
|
||||
audience: approval-engine
|
||||
allowedScopes: [approval:read, approval:consume]
|
||||
grantTypes: [client_credentials]
|
||||
clientType: confidential
|
||||
secretRef: env:KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
|
||||
serviceSubject: service:secrets-engine
|
||||
principal_type: service
|
||||
tenant: tenant:coulomb
|
||||
roles: [secrets-engine]
|
||||
tokenLifetime: 15m
|
||||
|
||||
- clientId: approval-engine-operator
|
||||
displayName: approval-engine lifecycle operator
|
||||
audience: approval-engine
|
||||
allowedScopes:
|
||||
- approval:create
|
||||
- approval:read
|
||||
- approval:approve
|
||||
- approval:revoke
|
||||
- approval:supersede
|
||||
- approval:observe
|
||||
- approval:emit
|
||||
grantTypes: [client_credentials]
|
||||
clientType: confidential
|
||||
secretRef: env:KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET
|
||||
serviceSubject: service:approval-engine-operator
|
||||
principal_type: service
|
||||
tenant: tenant:coulomb
|
||||
roles: [approval-operator]
|
||||
tokenLifetime: 15m
|
||||
```
|
||||
|
||||
Human approvers use the existing KeyCape human flow with `approval:approve`
|
||||
only, still with `aud=approval-engine`. They must not receive
|
||||
`approval:consume`.
|
||||
|
|
@ -14,7 +14,15 @@ and consume conflicts all prevent the callback. A same-digest retry receives
|
|||
the engine's idempotent success. If the callback fails after consume, the
|
||||
approval stays spent; there is no unconsume.
|
||||
|
||||
The module rereads the mounted bearer-token file on each HTTP request. Its unit
|
||||
harness uses a dry-run callback and demonstrates the ordering, but live closure
|
||||
requires the secrets-engine-owned handler to prove that no OpenBao request is
|
||||
made in every failure case.
|
||||
The module rereads the mounted bearer-token file on each HTTP request. Conflict,
|
||||
unavailability, unauthorized, missing, DENY, and digest-mismatch paths all
|
||||
prevent the callback. Same-digest consume retries are idempotent; a later full
|
||||
sequence against a spent approval fails at the claim. The consume response is
|
||||
rejected if it carries decision-shaped keys (`effect` / `allow` / `deny`).
|
||||
|
||||
The live HTTP harness in `tests/test_pep.py` drives this sequence against the
|
||||
real WSGI surface without performing a protected action. secrets-engine owns
|
||||
the production OpenBao PEP (`src/secrets_engine/approval_consume.py`); that
|
||||
handler is implemented and proven in-repo for 409 / unreachable / missing
|
||||
binding. Live closure still needs this service deployed and a durable consume
|
||||
binding served (`SECRETS-WP-0007-T04` / `SECRETS-WP-0008-T02`).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue