Files
mcr/internal/policy/engine_test.go
Kyle Isom f5e67bd4aa Phase 4: policy engine with deny-wins, default-deny evaluation
internal/policy/:
Priority-based policy engine per ARCHITECTURE.md §4. Stateless
Evaluate() sorts rules by priority, collects all matches, deny-wins
over allow, default-deny if no match. Rule matching: all populated
fields ANDed, empty fields are wildcards, repository glob via
path.Match. Built-in defaults: admin wildcard (all actions), human
user content access (pull/push/delete/catalog), version check
(always accessible). Engine wrapper with sync.RWMutex-protected
cache, SetRules merges with defaults, Reload loads from RuleStore.

internal/db/:
LoadEnabledPolicyRules() parses rule_json column from policy_rules
table into []policy.Rule, filtered by enabled=1, ordered by priority.

internal/server/:
RequirePolicy middleware extracts claims from context, repo from chi
URL param, evaluates policy, returns OCI DENIED (403) on deny with
optional audit callback.

69 tests passing across all packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:05:28 -07:00

163 lines
3.6 KiB
Go

package policy
import "testing"
// fakeRuleStore implements RuleStore for testing.
type fakeRuleStore struct {
rules []Rule
err error
}
func (f *fakeRuleStore) LoadEnabledPolicyRules() ([]Rule, error) {
return f.rules, f.err
}
func TestEngineDefaultsOnly(t *testing.T) {
e := NewEngine()
// Admin should be allowed.
effect, _ := e.Evaluate(PolicyInput{
Subject: "admin-uuid",
AccountType: "human",
Roles: []string{"admin"},
Action: ActionPush,
Repository: "myapp",
})
if effect != Allow {
t.Fatalf("admin push with defaults: got %s, want allow", effect)
}
// System account should be denied.
effect, _ = e.Evaluate(PolicyInput{
Subject: "system-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Deny {
t.Fatalf("system pull with defaults: got %s, want deny", effect)
}
}
func TestEngineWithCustomRules(t *testing.T) {
e := NewEngine()
e.SetRules([]Rule{
{
ID: 1,
Priority: 50,
Effect: Allow,
SubjectUUID: "ci-uuid",
Actions: []Action{ActionPull, ActionPush},
},
})
// System account with matching rule should be allowed.
effect, _ := e.Evaluate(PolicyInput{
Subject: "ci-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Allow {
t.Fatalf("ci pull with custom rule: got %s, want allow", effect)
}
// Different subject should still be denied.
effect, _ = e.Evaluate(PolicyInput{
Subject: "other-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Deny {
t.Fatalf("other pull: got %s, want deny", effect)
}
}
func TestEngineReload(t *testing.T) {
e := NewEngine()
store := &fakeRuleStore{
rules: []Rule{
{
ID: 1,
Priority: 50,
Effect: Allow,
SubjectUUID: "ci-uuid",
Actions: []Action{ActionPull},
},
},
}
if err := e.Reload(store); err != nil {
t.Fatalf("Reload: %v", err)
}
// ci-uuid should now be allowed.
effect, _ := e.Evaluate(PolicyInput{
Subject: "ci-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Allow {
t.Fatalf("ci pull after reload: got %s, want allow", effect)
}
// Reload again with new rules (simulating DB change).
store.rules = []Rule{
{
ID: 2,
Priority: 50,
Effect: Allow,
SubjectUUID: "deploy-uuid",
Actions: []Action{ActionPull},
},
}
if err := e.Reload(store); err != nil {
t.Fatalf("Reload (second): %v", err)
}
// ci-uuid should now be denied (old rule gone).
effect, _ = e.Evaluate(PolicyInput{
Subject: "ci-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Deny {
t.Fatalf("ci pull after second reload: got %s, want deny", effect)
}
// deploy-uuid should now be allowed.
effect, _ = e.Evaluate(PolicyInput{
Subject: "deploy-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Allow {
t.Fatalf("deploy pull after reload: got %s, want allow", effect)
}
}
func TestEngineReloadDisabledExcluded(t *testing.T) {
e := NewEngine()
// Store returns no rules (all disabled or none exist).
store := &fakeRuleStore{rules: nil}
if err := e.Reload(store); err != nil {
t.Fatalf("Reload: %v", err)
}
// No custom rules, so system account should be denied.
effect, _ := e.Evaluate(PolicyInput{
Subject: "ci-uuid",
AccountType: "system",
Action: ActionPull,
Repository: "myapp",
})
if effect != Deny {
t.Fatalf("system pull with no custom rules: got %s, want deny", effect)
}
}