flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after
secrets-engine found that a claim-bearing request's request_digest covers the
carried claim, so it can never equal a pdp_digest recorded before that claim
existed. A consumer obeying GH-DEC-2026-008 against request_digest would have
failed closed permanently on every claim rather than on a bad one.
The value recorded at issue was already correct, so no code changes. What was
wrong was this repo's description of the comparison target: a reader would
reach for request_digest and fail closed forever. The schema and
docs/approval-claim.md now name approval_binding_digest as the execute-time
target and state that request_digest is never it, while leaving the issue-time
description as it stood.
Separately, docs/keycape-service-registrations.md now records a live collision
in the deployment inputs: the manifest serves --tenant platform while the
requested registrations issue tenant:coulomb, and ApiApplication.identity
compares them with exact string equality before any object lookup, so those
tokens would be denied 403 on every non-health route. flex-auth's
tenant:platform is a PDP subject this engine never reads and cannot bridge the
two. The values are left as-is on purpose — resolving it needs an owner
statement on whether the two name the same layer, and guessing grants
cross-tenant access to the approval store. The doc's stale issuer is corrected
to the live https://kc.coulomb.social from 06544b0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715850@bnt-lap001
Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
106 lines
4.3 KiB
Markdown
106 lines
4.3 KiB
Markdown
# 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://kc.coulomb.social`) |
|
|
| JWKS | `GET /jwks` on the KeyCape service |
|
|
| Scopes | `approval:create`, `approval:read`, `approval:approve`, `approval:revoke`, `approval:supersede`, `approval:consume`, `approval:observe`, `approval:emit` |
|
|
|
|
## Tenant reconciliation — unresolved, blocks token issuance
|
|
|
|
**This is a live collision, not a naming preference.** `ApiApplication.identity`
|
|
compares the verified JWT `tenant` claim to the engine's configured store tenant
|
|
with exact string equality and raises `Forbidden` before any object lookup
|
|
(`approval_engine/api.py:66`). There is no mapping table, no normalisation, and
|
|
no prefix handling anywhere in this engine.
|
|
|
|
The three values currently in play:
|
|
|
|
| Value | Where it is set | Current content |
|
|
| --- | --- | --- |
|
|
| Store tenant | `deploy/approval-engine.yaml` `--tenant` (CLI default `platform`) | `platform` |
|
|
| JWT `tenant` claim | the client registrations below | `tenant:coulomb` |
|
|
| CheckRequest tenant | flex-auth policy subject | `tenant:platform` — **never read by this engine** |
|
|
|
|
`platform` != `tenant:coulomb`, so tokens issued under the registrations below
|
|
would be denied `403` on every non-health route. Spelling similarity between
|
|
`platform` and `tenant:platform` is not a mapping either; the flex-auth policy
|
|
subject is a PDP input this engine never inspects, so it cannot participate in
|
|
the comparison at all.
|
|
|
|
Denial evidence: `tests/test_auth.py::test_wrong_tenant_is_forbidden` — a
|
|
signature-valid token whose tenant differs from the store tenant is refused
|
|
without mutation.
|
|
|
|
**Resolution is an owner decision and is deliberately not taken here.** Either
|
|
KeyCape issues `tenant: platform` to match the store, or this repo's manifest
|
|
sets `--tenant tenant:coulomb` to match the registration. Which is correct
|
|
depends on whether `platform` and `tenant:coulomb` name the same layer — a
|
|
question this engine cannot answer, and answering it wrongly grants a token
|
|
cross-tenant access to the approval store. The values below are left as
|
|
requested until that mapping is stated by an owner, so the mismatch stays
|
|
visible rather than being silently resolved by whichever document was edited
|
|
last.
|
|
|
|
Tracked against `APPROVAL-WP-0002-T01`; related `KEY-WP-0013-T02`,
|
|
`SECRETS-WP-0009-T03`.
|
|
|
|
## 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`.
|