flex-auth/docs/action-bound-authorization-contract.md
tegwick 58fbd46ff9
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Finish FLEX-WP-0017
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a05e2e-805b-7042-a750-71f473bceea2
2026-09-01 20:21:58 +02:00

98 lines
4.4 KiB
Markdown

# Action-bound authorization contract
Status: implemented for execute-time request binding; durable approval storage
and lifecycle require `approval-engine` support.
Date: 2026-08-23
## Ownership and endpoints
flex-auth owns authorization evaluation and the canonical request/decision
vocabulary. A protected system obtains a current decision from:
```text
POST /v1/check
```
The response now includes `binding`, the exact normalized subject, action,
resource, context, and full SHA-256 request digest evaluated by the policy.
Consumers must compare structured fields; `reason`, `diagnostics`, titles, and
other prose are never an authorization contract.
`approval-engine` owns the durable approval object, authenticated approval
evidence, storage, supersession, consumption, and availability. State Hub
decision records are coordination and provenance evidence; they are not the
runtime approval authority. Production consumers must not infer action binding
or dual control from State Hub prose or a free-form `decided_by` value.
## Durable object
[`../schemas/action_authorization.schema.json`](../schemas/action_authorization.schema.json)
defines the proposed `ActionAuthorization` storage and transport object. It
combines:
- a canonical UUID and explicit lifecycle status;
- one complete `CheckRequest` containing the actor, action, resource, exact
target attributes, stage, purpose, and other policy inputs;
- a bounded validity window;
- approval count and individual approval evidence;
- the flex-auth `DecisionEnvelope` whose structured binding and digest match
that request.
The schema is generic. For secrets-engine, use this profile:
| Requirement | Canonical field |
| --- | --- |
| catalog id | `request.resource.id` |
| stage | `request.resource.attributes.stage` |
| lifecycle operation | `request.action` |
| exact secret fields | `request.resource.attributes.fields` |
| policy/auth targets | `request.resource.attributes.policy_targets` / `auth_targets` |
| requesting actor | `request.subject` |
| purpose | `request.context.purpose` |
| execution window | `validity.not_before` / `validity.expires_at` |
| dual control | `approvals.required_count` and distinct `entries[].subject_id` |
Array values that express exact targets are sets: producers sort and de-duplicate
them before evaluation; consumers require exact set equality. Wildcards are
policy inputs and should be denied for destructive production actions.
## Required verification
A production consumer may execute only when all of the following hold:
1. The durable object resolves from `approval-engine`, its canonical authority;
an outage fails the action closed.
2. `status` is `approved`, `superseded_by` is absent, and the current time is
inside the validity window.
3. The caller's proposed action exactly matches `request`, including resource
id, system, stage, target sets, actor, and purpose.
4. `decision.effect` is `allow`; `decision.binding` matches the request and its
digest; the policy package/version are accepted for the deployment.
5. Approval entries contain at least `required_count` distinct authenticated
subjects. Repeated entries from one subject count once.
Local fixtures, CCR labels, workplan ids, prose, and a caller-supplied flex-auth
decision id are provenance only. They cannot unlock a production action.
## Outage and supersession semantics
- `approval-engine` unreachable: privileged live
action denied; read-only inspection and dry-run may continue with a clear
non-authoritative status.
- flex-auth unavailable when a fresh decision is required: privileged live
action denied. A previously stored decision is usable only inside its bounded
validity window and only if the authoritative lifecycle object can still be
checked for supersession or revocation.
- `superseded`, `expired`, `revoked`, `denied`, or unknown status: deny.
- `superseded_by` present regardless of status: deny and resolve the replacement
explicitly; never follow it silently during execution.
## Remaining external work
`approval-engine` needs a structured object/endpoint capable of storing this
schema or equivalent fields, authenticated approval entries, atomic
supersession, and single-consumption semantics. Until that lands and the
consumer proof passes, secrets-engine is correct to keep live destructive
actions disabled. The standalone evaluator and the Topaz, relationship, rule,
and Keycloak delegated adapter boundaries now all populate the same binding.