diff --git a/docs/delegated-mode-operations.md b/docs/delegated-mode-operations.md new file mode 100644 index 0000000..5e2c5b2 --- /dev/null +++ b/docs/delegated-mode-operations.md @@ -0,0 +1,133 @@ +# Delegated Mode Operations + +Status: implemented for FLEX-WP-0004 P4.6. + +## Purpose + +Delegated mode lets flex-auth coordinate external authorization systems +without changing the protected-system-facing API. Protected systems keep +using flex-auth check, batch, list, explanation, and audit contracts. +Topaz, relationship PDPs, rule PDPs, Keycloak Authorization Services, +and directory resolvers stay behind adapter boundaries. + +## Deployment Order + +1. Load protected-system manifests and resource manifests into the + flex-auth registry. +2. Load subject manifests or configure directory resolvers. +3. Import resources and relationships into the delegated backend. +4. Import policy artifacts or backend-native policy mirrors. +5. Run adapter health checks. +6. Start serving delegated checks only after the backend reports healthy + and the latest import has a recorded consistency token. + +For Topaz, the local reference topology is `examples/topaz`. For +Keycloak, register resources from flex-auth manifests before enabling +UMA permission checks. For OpenFGA/SpiceDB-style systems, import tuples +from the canonical registry snapshot. For OPA/Cedar-style systems, +import policy artifacts from validated flex-auth policy packages. + +## Health Checks + +Each delegated adapter exposes a health boundary. Runtime health should +be tracked separately for: + +- backend reachability +- policy import freshness +- directory or tuple import freshness +- resolver freshness +- last successful decision time + +Health failures should not silently fall back to stale allow decisions. +They should produce observable diagnostics and deny fail-closed unless a +deployment has explicitly accepted a narrower fail-open mode for a +non-sensitive action. + +## Fail-Closed Default + +The default behavior is fail-closed: + +| Condition | Decision effect | Typical reason | +| --- | --- | --- | +| backend unavailable | deny | `topaz_unavailable`, `relationship_backend_unavailable`, `rule_backend_unavailable`, `keycloak_unavailable` | +| stale directory or tuple data | deny | `topaz_directory_stale`, `relationship_data_stale` | +| stale policy | deny | `rule_policy_stale`, `keycloak_policy_stale` | +| partial backend result | deny | `*_partial_result` | +| request cannot be translated | deny | `*_request_incomplete` | + +Fail-open is only acceptable for explicitly classified low-risk +capabilities and must be recorded as a policy decision, not an adapter +default. Data, identity, policy, secret, audit, and commercial planes +should remain fail-closed. + +## Caching + +Recommended cache layers: + +- resource manifest snapshot cache +- subject/group resolver cache +- delegated backend import token cache +- decision-result cache for idempotent low-risk checks + +Cache entries must record source, retrieval time, max age, and expiry. +Directory resolver results already expose `Freshness`; tuple and +Topaz/Keycloak imports expose consistency metadata through the decision +provenance fields. Cache hits should still include provenance so audit +readers can tell whether evidence came from a fresh backend call or a +bounded cache. + +## Consistency + +flex-auth uses `DecisionEnvelope.provenance.directory_etag` as the +adapter-neutral consistency token field: + +- Topaz: relation etag when available +- OpenFGA/SpiceDB-style backends: model id, zedtoken, or tuple-store + freshness token +- directory resolvers: freshness metadata attached to subject metadata +- Keycloak/rule PDPs: policy version in `provenance.policy_version` + +Adapters should deny or mark stale when a request demands a newer token +than the backend can satisfy. Read-your-writes paths should import +registry data, record the returned token, and require that token for the +first protected-system checks after import. + +## Audit Behavior + +Delegated decisions must log the same canonical envelope as standalone +decisions: + +- subject and resource references +- effect, reason, matched rule, matched policy version +- obligations +- diagnostics +- evaluator and mode +- consistency token +- CARING descriptor, restrictions, exposure modes, derived + capabilities, exposure events, and conformance findings + +Backend-native traces can appear in diagnostics, but they must not +replace the canonical fields. Explanations should be generated from the +flex-auth envelope so switching backends does not change protected-system +or auditor vocabulary. + +## Adapter Notes + +- Topaz operations: `docs/topaz-adapter-operations.md` +- Relationship PDPs: `docs/relationship-pdp-adapter-boundary.md` +- Rule PDPs: `docs/rule-pdp-adapter-boundary.md` +- Keycloak Authorization Services: + `docs/keycloak-authz-adapter-path.md` +- Directory resolvers: `docs/directory-group-resolver-adapters.md` + +## Operational Checklist + +- Backend configured and reachable. +- Policy/resource/tuple import completed. +- Latest import token recorded. +- Directory resolver freshness thresholds configured. +- Fail-closed behavior tested for unavailable, stale, partial, and + invalid-request cases. +- Decision log captures delegated provenance and CARING metadata. +- Rollback path can switch a protected system back to standalone mode or + to another delegated adapter without changing the protected-system API. diff --git a/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md b/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md index e6d6593..09e71a4 100644 --- a/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md +++ b/workplans/FLEX-WP-0004-delegated-pdp-and-directory-adapters.md @@ -3,7 +3,7 @@ id: FLEX-WP-0004 type: workplan title: "Delegated PDP and Directory Adapters" domain: netkingdom -status: active +status: completed owner: flex-auth topic_slug: flex-auth planning_priority: P2 @@ -150,7 +150,7 @@ descriptor provenance remains inspectable. ```task id: FLEX-WP-0004-T006 -status: todo +status: done priority: medium state_hub_task_id: "491260f9-b4d7-46fe-8220-d358597db33a" ```