Engine/PIP declaration is now checkable (layer.yaml plus a Tooling-client scan). Writes persist a decision record or the published fail-closed stance, live-lookup freshness is published, events_for is tenant-scoped, and mutation evidence drains to audit-core from a local outbox without blocking the mutation. Sender registration is requested as AUDIT-IN-0002. Boundary-contract amendment is requested as NET-IN-0002. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
4.9 KiB
flex-auth Integration
tenant-engine gates every write, and every authorized read, through
flex-auth's POST /v1/check (flex_auth.FlexAuthCheckClient, wired in as
authz.FlexAuthWriteAuthorizer). Writes are PEP-shaped: no mutation
without a decision record or a recorded fail-closed stance
(pep-stance.yaml).
What tenant-engine sends
A CheckRequest per flex-auth/schemas/check_request.schema.json:
{
"id": "check:<uuid>",
"tenant": "tenant:friendly:binky",
"subject": {"id": "<actor>", "type": "service"},
"action": "tenant.create",
"resource": {"id": "<tenant_id>", "type": "tenant", "system": "tenant-engine"},
"context": {}
}
context is always empty. This engine never puts tenant_roles or any
other PIP claim on the check it sends. The check asks whether this
caller may use this admin/PIP surface, not what the tenant is allowed
to do on the platform.
Action → resource-type mapping (must match flex-auth's
FLEX-WP-0008-T01 vocabulary exactly — coordinate values, don't diverge):
| Action | Resource type | Surface |
|---|---|---|
tenant.create |
tenant |
write |
tenant.role.grant |
role-grant |
write |
tenant.role.revoke |
role-grant |
write |
tenant.plan.assign |
plan-assignment |
write |
tenant.update |
tenant |
write |
tenant.retire |
tenant |
write |
tenant.reactivate |
tenant |
write |
tenant.grouping.set |
tenant |
write |
tenant.guardrail.read |
guardrail |
read |
tenant.guardrail.set |
guardrail |
write |
tenant.read |
tenant |
read |
tenant.role.read |
role-grant |
read (cache-read) |
tenant.role.read.live |
role-grant |
read (live-lookup) |
What tenant-engine expects back
A DecisionEnvelope per flex-auth/schemas/decision_envelope.schema.json.
Only effect: "allow" authorizes the action. Every other effect, a
non-200 response, a malformed body, or a transport failure/timeout all
resolve to deny. FlexAuthCheckClient.check() never raises past its
own boundary; it returns a CheckResult that is either a decision or a
recorded application of the fail-closed stance.
The decision id, request digest, effect, and source are persisted on
authz_records for every attempt and on the mutation event payload for
every successful write. Verdicts are never cached: every call is a new
POST (pep-stance.yaml verdict_caching: none).
Live-lookup is not cyclic (TEN-WP-0011-T03)
GET /tenants/{id}/roles/live authorizes via tenant.role.read.live then
reads the store. That check does not re-enter tenant-engine:
- The CheckRequest this engine sends has empty
contextand does not carrytenant_roles. FlexAuthCheckClientPOSTs only/v1/check. Proven bytests/test_pip_claims.py::test_live_lookup_check_does_not_reenter_tenant_engine.- flex-auth's tenant-engine policy package
(
flex-auth/examples/tenant-engine/policy_package.md) matchessubject.idandactiononly. It does not consult tenant capability roles. The package's own scope note says those roles are tenant state a different protected system might consult via live-lookup; conflating the two would authorize the wrong thing. - flex-auth's live-roles adapter is built and unwired
(
FLEX-WP-0015,flex-auth/docs/tenancy-posture-review.md): no non-test caller, no current policy consumestenant_rolesfor a privileged path.
Together: GET /roles/live → POST /v1/check is a service-identity question. The PDP does not need tenant-engine claims to answer it, so it does not call back.
Note: tenant.role.read / tenant.role.read.live / tenant.read /
tenant.grouping.set are not yet in that policy package's valid_actions.
A live flex-auth will currently unknown_action those four. That is
itself evidence they are not evaluated via tenant-role claims. Adding
them as static service-identity rules (same shape as
tenant.guardrail.read) is flex-auth work, not a cycle to unwind here.
Unreachable-engine stance
Published in pep-stance.yaml. Every scope is fail_closed. DefaultDeny
(URL unset) and transport failure both apply that stance and record it.
Tests assert the file equals shipped behaviour.
Configuration
| Env var | Default | Meaning |
|---|---|---|
TENANT_ENGINE_FLEX_AUTH_URL |
unset | Base URL of a reachable flex-auth deployment. When unset, create_app() falls back to authz.DefaultDenyWriteAuthorizer. |
TENANT_ENGINE_FLEX_AUTH_TIMEOUT_SECONDS |
3 |
Bounded timeout on the synchronous path — no retries. |
TENANT_ENGINE_FLEX_AUTH_TOKEN_FILE |
unset | Rotating bearer token file, read on each check. |
Related
pep-stance.yaml— published fail-closed mappip-claims.yaml— input-class freshnessflex-auth/workplans/FLEX-WP-0008-tenant-engine-consumer-integration.mdkey-cape/workplans/KEY-WP-0005-iam-profile-core-claims.mdnet-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md