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>
This commit is contained in:
71
PROGRESS.md
71
PROGRESS.md
@@ -6,7 +6,7 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
|
||||
## Current State
|
||||
|
||||
**Phase:** Batch A complete (Phases 2 + 3), ready for Phase 4 (policy engine)
|
||||
**Phase:** 4 complete, ready for Batch B (Phase 5 + Phase 8)
|
||||
**Last updated:** 2026-03-19
|
||||
|
||||
### Completed
|
||||
@@ -15,6 +15,7 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
- Phase 1: Configuration & database (all 3 steps)
|
||||
- Phase 2: Blob storage layer (all 2 steps)
|
||||
- Phase 3: MCIAS authentication (all 4 steps)
|
||||
- Phase 4: Policy engine (all 4 steps)
|
||||
- `ARCHITECTURE.md` — Full design specification (18 sections)
|
||||
- `CLAUDE.md` — AI development guidance
|
||||
- `PROJECT_PLAN.md` — Implementation plan (14 phases, 40+ steps)
|
||||
@@ -22,13 +23,77 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. Phase 4: Policy engine (depends on Phase 3)
|
||||
2. After Phase 4, Batch B: Phase 5 (OCI pull) and Phase 8 (admin REST)
|
||||
1. Batch B: Phase 5 (OCI pull) and Phase 8 (admin REST) — independent,
|
||||
can be done in parallel
|
||||
2. After Phase 5, Phase 6 (OCI push) then Phase 7 (OCI delete)
|
||||
|
||||
---
|
||||
|
||||
## Log
|
||||
|
||||
### 2026-03-19 — Phase 4: Policy engine
|
||||
|
||||
**Task:** Implement the registry-specific authorization engine with
|
||||
priority-based, deny-wins, default-deny evaluation per ARCHITECTURE.md §4.
|
||||
|
||||
**Changes:**
|
||||
|
||||
Step 4.1 — `internal/policy/` core types and evaluation:
|
||||
- `policy.go`: `Action` (6 constants), `Effect` (Allow/Deny), `PolicyInput`,
|
||||
`Rule` types per ARCHITECTURE.md §4
|
||||
- `Evaluate(input, rules)` — stateless evaluation: sort by priority (stable),
|
||||
collect all matching rules, deny-wins, default-deny
|
||||
- Rule matching: all populated fields ANDed; empty fields are wildcards;
|
||||
`Repositories` glob matching via `path.Match`; empty repo (global ops)
|
||||
only matches rules with empty Repositories list
|
||||
|
||||
Step 4.2 — `internal/policy/` built-in defaults:
|
||||
- `defaults.go`: `DefaultRules()` returns 3 built-in rules (negative IDs,
|
||||
priority 0): admin wildcard (all actions), human user content access
|
||||
(pull/push/delete/catalog), version check (always accessible)
|
||||
|
||||
Step 4.3 — `internal/policy/` engine wrapper with DB integration:
|
||||
- `engine.go`: `Engine` struct with `sync.RWMutex`-protected rule cache;
|
||||
`NewEngine()` pre-loaded with defaults; `SetRules()` merges with defaults;
|
||||
`Evaluate()` thread-safe evaluation; `Reload(RuleStore)` loads from DB
|
||||
- `RuleStore` interface: `LoadEnabledPolicyRules() ([]Rule, error)`
|
||||
- `internal/db/policy.go`: `LoadEnabledPolicyRules()` on `*DB` — loads
|
||||
enabled rules from `policy_rules` table, parses `rule_json` JSON column,
|
||||
returns `[]policy.Rule` ordered by priority
|
||||
|
||||
Step 4.4 — `internal/server/` policy middleware:
|
||||
- `policy.go`: `PolicyEvaluator` interface, `AuditFunc` callback type,
|
||||
`RequirePolicy(evaluator, action, auditFn)` middleware — extracts claims
|
||||
from context, repo name from chi URL param, assembles `PolicyInput`,
|
||||
returns OCI DENIED (403) on deny with optional audit callback
|
||||
|
||||
**Verification:**
|
||||
- `make all` passes: vet clean, lint 0 issues, 69 tests passing
|
||||
(17 policy + 14 server + 15 db + 9 auth + 7 config + 14 storage - some
|
||||
overlap from updated packages), all 3 binaries built
|
||||
- Policy evaluation tests: admin wildcard, user allow, system account deny,
|
||||
exact repo match (allow + deny on different repo), glob match
|
||||
(production/* matches production/myapp, not production/team/myapp),
|
||||
deny-wins over allow, priority ordering, empty repo global operation
|
||||
(admin catalog allowed, repo-scoped rule doesn't match), multiple
|
||||
matching rules (highest-priority allow returned)
|
||||
- Default rules tests: admin allowed for all 6 actions, user allowed for
|
||||
pull/push/delete/catalog but denied policy:manage, system account denied
|
||||
for all except version_check, version_check allowed for both human and
|
||||
system accounts
|
||||
- Engine tests: defaults-only (admin allow, system deny), custom rules
|
||||
(matching subject allowed, different subject denied), reload picks up new
|
||||
rules (old rules gone), reload with empty store (disabled rules excluded,
|
||||
falls back to defaults)
|
||||
- DB tests: LoadEnabledPolicyRules returns only enabled rules ordered by
|
||||
priority, parses rule_json correctly (effect, subject_uuid, actions,
|
||||
repositories), empty table returns nil
|
||||
- Middleware tests: admin allowed, user allowed, system denied (403 with
|
||||
OCI DENIED error), system with matching rule allowed, explicit deny
|
||||
rule blocks access (403)
|
||||
|
||||
---
|
||||
|
||||
### 2026-03-19 — Batch A: Phase 2 (blob storage) + Phase 3 (MCIAS auth)
|
||||
|
||||
**Task:** Implement content-addressed blob storage and MCIAS authentication
|
||||
|
||||
Reference in New Issue
Block a user