Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02e47-6aac-7ee1-914d-0584c75d3c81
103 lines
4.4 KiB
Markdown
103 lines
4.4 KiB
Markdown
# Action-bound authorization contract
|
|
|
|
Status: implemented for execute-time request binding; durable approval storage
|
|
requires State Hub 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.
|
|
|
|
State Hub owns durable organizational-decision identity, resolution,
|
|
supersession, and availability. Its current endpoint is:
|
|
|
|
```text
|
|
GET /decisions/{uuid}
|
|
```
|
|
|
|
That object is not yet sufficient for privileged action execution. As of
|
|
2026-08-23 it has lifecycle fields and `superseded_by`, but action details live
|
|
only in prose and it records only one free-form `decided_by` value. Production
|
|
consumers must not infer action binding or dual control from it.
|
|
|
|
## 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 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
|
|
|
|
- State Hub or future authorization-object store 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
|
|
|
|
State Hub needs a structured object/endpoint capable of storing this schema or
|
|
equivalent fields, authenticated approval entries, and atomic supersession.
|
|
Until that lands, 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.
|