ZDDC/zddc/internal/policy/rego_failclosed_test.go
ZDDC 6d132572d3 chore(server): drop the federal reference Rego (bring-your-own-policy)
Decision: external OPA is a bring-your-own-policy escape hatch, not a
supported turnkey mode — so stop shipping access_federal.rego. A verb-blind
read-ACL policy under NIST AC-6 branding is a liability to hand a federal
evaluator, and (like access.rego before the fail-close) it over-granted writes
and ignored WORM. The HTTPDecider + Decider interface stay: operators who want
an AC-6 ancestor-deny-absolute posture write their own Rego.

- Delete rego/access_federal.rego, FederalRego, --print-rego=federal, and
  federal_parity_test.go; trim the federal cases from rego_failclosed_test.go.
- Reframe every doc reference (rego.go, main.go, file.go, ARCHITECTURE.md,
  README.md) to "operators write their own Rego"; rewrite the README
  "Reference Rego policy" section to describe the single fail-closed read-ACL
  skeleton accurately (it also still carried the now-removed "mirrors exactly"
  parity claim).

Out of scope (flagged): the broader federal-readiness narrative
(FedRAMP/FIPS/IdP) and the separate website page federal.html still discuss
federal posture — the OPA bring-your-own-Rego path stays valid, but a
deliberate review with the federal go-to-market in mind is warranted.

go vet + full go test ./... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 08:45:21 -05:00

83 lines
2.7 KiB
Go

package policy
import (
"context"
"testing"
"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
"github.com/open-policy-agent/opa/rego"
)
// TestReferenceRego_FailClosedOnWrites pins the security contract of the
// bundled reference Rego skeleton: it models READ-ACL only, so any non-read
// action must be DENIED even when the read-ACL would grant — and the only
// write-capable principal is an elevated admin. This is the behavior that,
// untested, previously let a verb-blind policy ship claiming to "mirror the
// internal decider exactly." See rego/access.rego.
func TestReferenceRego_FailClosedOnWrites(t *testing.T) {
ctx := context.Background()
stdQ, err := rego.New(
rego.Query("data.zddc.access.allow"),
rego.Module("access.rego", ReferenceRego),
).PrepareForEval(ctx)
if err != nil {
t.Fatalf("compile access.rego: %v", err)
}
// A chain that GRANTS full rwcd to alice — so any denial below is the
// action gate, not a missing ACL.
grant := zddc.PolicyChain{
Levels: []zddc.ZddcFile{{ACL: zddc.ACLRules{Permissions: map[string]string{"*@example.com": "rwcd"}}}},
HasAnyFile: true,
}
evalAllow := func(q rego.PreparedEvalQuery, action string, admin bool) bool {
in := AllowInput{Path: "/p/", Action: action, PolicyChain: chainToSerializable(grant)}
in.User.Email = "alice@example.com"
in.User.IsActiveAdmin = admin
regoInput, err := canonicalInput(in)
if err != nil {
t.Fatalf("encode: %v", err)
}
rs, err := q.Eval(ctx, rego.EvalInput(regoInput))
if err != nil {
t.Fatalf("eval: %v", err)
}
if len(rs) == 0 {
t.Fatalf("no result")
}
v, ok := rs[0].Expressions[0].Value.(bool)
if !ok {
t.Fatalf("result not bool: %v", rs[0].Expressions[0].Value)
}
return v
}
cases := []struct {
name string
q rego.PreparedEvalQuery
action string
admin bool
wantAllow bool
}{
// Commercial: reads granted, every write verb denied (fail-closed).
{"access read allowed", stdQ, ActionRead, false, true},
{"access empty-action(read) allowed", stdQ, "", false, true},
{"access write denied", stdQ, ActionWrite, false, false},
{"access create denied", stdQ, ActionCreate, false, false},
{"access delete denied", stdQ, ActionDelete, false, false},
{"access admin-action denied", stdQ, ActionAdmin, false, false},
// An elevated admin is the one write-capable principal.
{"access write allowed for active admin", stdQ, ActionWrite, true, true},
{"access delete allowed for active admin", stdQ, ActionDelete, true, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := evalAllow(tc.q, tc.action, tc.admin); got != tc.wantAllow {
t.Errorf("allow=%v, want %v (action=%q active_admin=%v)", got, tc.wantAllow, tc.action, tc.admin)
}
})
}
}