2026-09-02 00:52:04 +02:00
|
|
|
# Caller authentication
|
|
|
|
|
|
|
|
|
|
Production accepts only RS256 JWTs verified against KeyCape JWKS with the exact
|
|
|
|
|
configured issuer and `approval-engine` audience. `exp`, `iat`, `sub`,
|
|
|
|
|
`principal_type`, `tenant`, `roles`, `scope`, and `assurance` are mandatory.
|
|
|
|
|
Missing or unverifiable credentials fail closed. The development static-token
|
|
|
|
|
mode is explicit, file-backed, and refused with `--production`.
|
|
|
|
|
The verified `tenant` must exactly match the service's configured store tenant;
|
|
|
|
|
cross-tenant reads and mutations are rejected before object lookup.
|
|
|
|
|
|
2026-09-10 07:56:28 +02:00
|
|
|
**This check is store isolation, and it is not a statement about the person.**
|
|
|
|
|
It answers "does this caller belong to the store this engine serves", not "is
|
|
|
|
|
this human a member of the platform zone". `GH-DEC-2026-013` rules that a
|
|
|
|
|
key-cape human tenant claim may be supplied by the client registration rather
|
|
|
|
|
than asserted by the directory, and forbids a consumer treating the two as
|
|
|
|
|
equivalent for a decision turning on a fact about the person. Exact string
|
|
|
|
|
equality cannot see that difference — a string that matches exactly matches
|
|
|
|
|
whoever asserted it — so the difference is stated here instead.
|
|
|
|
|
|
|
|
|
|
Consequences this engine accepts deliberately:
|
|
|
|
|
|
|
|
|
|
- A registration-supplied tenant **is** admissible for admission to this store.
|
|
|
|
|
Admission means "arrived through a channel the platform registered", the
|
|
|
|
|
approver client is static and deployment-owned, and dynamic client
|
|
|
|
|
registration is excluded from key-cape by design.
|
|
|
|
|
- A registration-supplied tenant is **not** admissible for any doctrine that
|
|
|
|
|
turns on the approver's own membership — "an approver must be a member of the
|
|
|
|
|
platform zone" is a fact about the person, and this claim cannot carry it.
|
|
|
|
|
No such doctrine exists today; if `gate-house` issues one, it needs a
|
|
|
|
|
directory-sourced claim and this check does not become that claim by matching.
|
|
|
|
|
- When key-cape emits provenance alongside the tenant, this engine records it on
|
|
|
|
|
the approver entry the way schema v4 records `principal_type` — an evidence
|
|
|
|
|
reader should not have to infer provenance from a string that cannot carry it.
|
|
|
|
|
|
2026-09-02 00:52:04 +02:00
|
|
|
| Route | Required scope |
|
|
|
|
|
|---|---|
|
|
|
|
|
| create approval | `approval:create` |
|
|
|
|
|
| get approval or claim | `approval:read` |
|
|
|
|
|
| add approval entry | `approval:approve` |
|
|
|
|
|
| revoke | `approval:revoke` |
|
|
|
|
|
| supersede | `approval:supersede` |
|
|
|
|
|
| consume | `approval:consume` and service/agent principal |
|
|
|
|
|
| cadence, outbox, storage | `approval:observe` |
|
|
|
|
|
| explicit heartbeat | `approval:emit` |
|
|
|
|
|
|
|
|
|
|
Create additionally requires `binding.actor == sub`. Approval-entry subject,
|
2026-09-09 14:09:54 +02:00
|
|
|
assurance, evidence reference, and **principal type** are derived from the
|
|
|
|
|
verified JWT, never the request body.
|
|
|
|
|
|
2026-09-10 19:26:12 +02:00
|
|
|
`principal_type` is recorded from verified identity (since schema v4). Schema v5
|
|
|
|
|
implements GH-DEC-2026-016: when an approval declares `human_control: true`,
|
|
|
|
|
`/entries` refuses a service, agent or unknown principal with 403 before inserting
|
|
|
|
|
an entry or emitting issuance. The request body cannot supply the approver type
|
|
|
|
|
or downgrade the declaration. Undeclared approvals retain service-to-service use;
|
|
|
|
|
a human entry does not retroactively declare a human control.
|
|
|
|
|
|
|
|
|
|
**Requesting and binding are separate operations.** `POST /v1/approvals` creates
|
|
|
|
|
an unapproved request and records the strict boolean declaration. A service or
|
|
|
|
|
agent with `approval:create` may draft that request. The principal contributing
|
|
|
|
|
judgment is known only at `POST /entries`; this is the bind/issue boundary where
|
|
|
|
|
GH-DEC-2026-016's non-human refusal applies. A draft is never a valid approval.
|
|
|
|
|
No statement about the approver is inferred from `binding.principal` or the
|
|
|
|
|
requesting actor's type. Dual control also needs its declared `required_count`;
|
|
|
|
|
human control alone does not imply two approvers or decide which acts need it.
|
|
|
|
|
|
|
|
|
|
**Identity provenance:** reviewed KeyCape source
|
|
|
|
|
`f9812ab3b2bfe8f0817185f44071e612264ec3ee:src/internal/server/oidc/token.go`
|
|
|
|
|
sets `principal_type=human` only after consuming a client/redirect-bound PKCE
|
|
|
|
|
session and looking up the current user. Its separate client-credentials path
|
|
|
|
|
sets `service`; no registration field supplies a human principal type. This is
|
|
|
|
|
different from registration-supplied tenant routing. Production acceptance must
|
|
|
|
|
pin and prove that issuer behavior with a real human flow; signed fixtures here
|
|
|
|
|
prove engine enforcement, not native identity admission. If an issuer introduces
|
|
|
|
|
a registration-supplied route to `human`, GH-DEC-2026-016 §5 requires independent
|
|
|
|
|
provenance and refusal of that route before admitting it. A consumer must not
|
|
|
|
|
relax the identity contract simply because the JWT verifies.
|
|
|
|
|
|
|
|
|
|
Entries written before v4 stay null; pre-v5 objects have `human_control: false`.
|
|
|
|
|
KeyCape owns client registration and scope grants; approval-engine verifies and
|
|
|
|
|
enforces them. Requested registrations are:
|
2026-09-02 00:52:04 +02:00
|
|
|
|
|
|
|
|
- audience/resource server `approval-engine` with the scopes above;
|
|
|
|
|
- the secrets-engine PEP service client with `approval:read` and
|
|
|
|
|
`approval:consume`;
|
|
|
|
|
- separately governed lifecycle/operator clients with only their needed
|
|
|
|
|
mutation or observation scopes.
|
|
|
|
|
|
|
|
|
|
Client credentials belong in OpenBao/operator custody and must not be placed in
|
|
|
|
|
manifests, logs, State Hub, or this repository.
|
2026-09-02 15:46:06 +02:00
|
|
|
|
|
|
|
|
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`.
|