# Federal-mode reference SKELETON: parent-deny-is-absolute (NIST AC-6), # read-ACL only. # # Like access.rego this models the read cascade ONLY and is NOT a complete # authorization policy — it does not implement per-verb (write/create/delete/ # admin), WORM, roles, inherit:false fences, or config-edit. It is therefore # FAIL-CLOSED: every non-read action is denied. This variant deliberately has # NO admin bypass either — under AC-6 least-privilege the default posture is # deny, and an operator who needs a write path must add the per-verb (and, if # desired, admin-escape) semantics themselves. As shipped it authorizes reads # only. # # The ONE modelled difference from access.rego: any deny anywhere on the chain # is absolute — a leaf-level allow does NOT override an ancestor's deny. # Required by NIST AC-6: a central admin's root deny must be unbypassable by # a tenant who controls a subtree's .zddc. # access.rego (commercial): leaf allow CAN override an ancestor deny. # access_federal.rego: ancestor deny is absolute. # # The internal Go evaluator implements neither these federal semantics nor a # tested mirror of this file; federal-mode is reachable only by running OPA # with this policy and pointing ZDDC_OPA_URL at it. See federal_parity_test.go # for the modelled read-cascade divergence fixtures. # # Input shape: identical to access.rego — see that file's docstring. # acl.permissions maps principal patterns to verb strings; an empty # verb string is an explicit deny. package zddc.access_federal import future.keywords.if import future.keywords.in default allow := false # Read-ACL only: every grant rule is gated on a read action; any write/ # create/delete/admin falls through to the default-deny above (fail-closed). # Empty/absent action == read. (No admin bypass in federal mode — see header.) is_read_action if { not input.action } is_read_action if { input.action == "" } is_read_action if { input.action == "read" } # Read allowed when no .zddc files exist anywhere AND no rule matches. # Same default-allow case as commercial; preserves the empty-tree # behaviour. (zddc-server's --insecure check at startup makes this # unreachable in any non-deliberately-public deployment.) allow if { is_read_action not input.policy_chain.has_any_file not any_deny_match not any_allow_match } # Read allowed when files exist, no level (any depth) denies, and at least # one level allows. The "any level" check is what makes parent denies # absolute — there is no "deepest match wins" rule here. allow if { is_read_action input.policy_chain.has_any_file not any_deny_match any_allow_match } # Any explicit-deny permission entry at ANY level matches the email. any_deny_match if { some level in input.policy_chain.levels some pattern, verbs in level.acl.permissions verbs == "" email_matches(pattern, input.user.email) } # Any grant permission entry (non-empty verbs) at ANY level matches. any_allow_match if { some level in input.policy_chain.levels some pattern, verbs in level.acl.permissions verbs != "" email_matches(pattern, input.user.email) } # email_matches: identical to access.rego — see that file for the # rationale on the four cases. Duplicated rather than imported so this # file is self-contained for operators who copy it as a starting point. email_matches(pattern, email) if { pattern == email } email_matches(pattern, email) if { pattern == "*" email != "" } email_matches(pattern, email) if { contains(pattern, "*") contains(pattern, "@") glob.match(pattern, ["@"], email) } email_matches(pattern, email) if { contains(pattern, "*") not contains(pattern, "@") pattern != "*" glob.match(pattern, [], email) }