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>
13 KiB
MCR Development Progress
Reverse-chronological log of development work. Most recent entries first.
See PROJECT_PLAN.md for the implementation roadmap and
ARCHITECTURE.md for the full design specification.
Current State
Phase: 4 complete, ready for Batch B (Phase 5 + Phase 8) Last updated: 2026-03-19
Completed
- Phase 0: Project scaffolding (all 4 steps)
- 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 guidancePROJECT_PLAN.md— Implementation plan (14 phases, 40+ steps)PROGRESS.md— This file
Next Steps
- Batch B: Phase 5 (OCI pull) and Phase 8 (admin REST) — independent, can be done in parallel
- 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,Ruletypes per ARCHITECTURE.md §4Evaluate(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;
Repositoriesglob matching viapath.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:Enginestruct withsync.RWMutex-protected rule cache;NewEngine()pre-loaded with defaults;SetRules()merges with defaults;Evaluate()thread-safe evaluation;Reload(RuleStore)loads from DBRuleStoreinterface:LoadEnabledPolicyRules() ([]Rule, error)internal/db/policy.go:LoadEnabledPolicyRules()on*DB— loads enabled rules frompolicy_rulestable, parsesrule_jsonJSON column, returns[]policy.Ruleordered by priority
Step 4.4 — internal/server/ policy middleware:
policy.go:PolicyEvaluatorinterface,AuditFunccallback type,RequirePolicy(evaluator, action, auditFn)middleware — extracts claims from context, repo name from chi URL param, assemblesPolicyInput, returns OCI DENIED (403) on deny with optional audit callback
Verification:
make allpasses: 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 with OCI token endpoint and auth middleware.
Changes:
Phase 2 — internal/storage/ (Steps 2.1 + 2.2):
storage.go:Storestruct withlayersPath/uploadsPath,New()constructor, digest validation (^sha256:[a-f0-9]{64}$), content-addressed path layout:<layers>/sha256/<first-2-hex>/<full-64-hex>writer.go:BlobWriterwrapping*os.File+crypto/sha256running hash viaio.MultiWriter.StartUpload(uuid)creates temp file in uploads dir.Write()updates both file and hash.Commit(expectedDigest)finalizes hash, verifies digest,MkdirAllprefix dir,Renameatomically.Cancel()cleans up temp file.BytesWritten()returns offset.reader.go:Open(digest)returnsio.ReadCloser,Stat(digest)returns size,Delete(digest)removes blob + best-effort prefix dir cleanup,Exists(digest)returns bool. All validate digest format first.errors.go:ErrBlobNotFound,ErrDigestMismatch,ErrInvalidDigest- No new dependencies (stdlib only)
Phase 3 — internal/auth/ (Steps 3.1) + internal/server/ (Steps 3.2–3.4):
auth/client.go:ClientwithNewClient(serverURL, caCert, serviceName, tags), TLS 1.3 minimum, optional custom CA cert, 10s HTTP timeout.Login()POSTs to MCIAS/v1/auth/login.ValidateToken()with SHA-256 cache keying and 30s TTL.auth/claims.go:Claimsstruct (Subject, AccountType, Roles) with context helpersContextWithClaims/ClaimsFromContextauth/cache.go:validationCachewithsync.RWMutex, lazy eviction, injectablenowfunction for testingauth/errors.go:ErrUnauthorized,ErrMCIASUnavailableserver/middleware.go:TokenValidatorinterface,RequireAuthmiddleware (Bearer token extraction,WWW-Authenticateheader, OCI error format)server/token.go:LoginClientinterface,TokenHandler(Basic auth → bearer token exchange via MCIAS, RFC 3339issued_at)server/v2.go:V2Handlerreturning 200{}server/routes.go:NewRouterwith chi:/v2/token(no auth),/v2/(RequireAuth middleware)server/ocierror.go:writeOCIError()helper for OCI error JSON format- New dependency:
github.com/go-chi/chi/v5
Verification:
make allpasses: vet clean, lint 0 issues, 52 tests passing (7 config + 13 db/audit + 14 storage + 9 auth + 9 server), all 3 binaries built- Storage tests: new store, digest validation (3 valid + 9 invalid), path layout, write+commit, digest mismatch rejection (temp cleanup verified), cancel cleanup, bytes written tracking, concurrent writes to different UUIDs, open after write, stat, exists, delete (verify gone), open not found, invalid digest format (covers Open/Stat/Delete/Exists)
- Auth tests: cache put/get, TTL expiry with clock injection, concurrent cache access, login success/failure (httptest mock), validate success/revoked, cache hit (request counter), cache expiry (clock advance)
- Server tests: RequireAuth valid/missing/invalid token, token handler success/invalid creds/missing auth, routes integration (authenticated /v2/, unauthenticated /v2/ → 401, token endpoint bypasses auth)
2026-03-19 — Phase 1: Configuration & database
Task: Implement TOML config loading with env overrides and validation, SQLite database with migrations, and audit log helpers.
Changes:
Step 1.1 — internal/config/:
config.go:Configstruct matching ARCHITECTURE.md §10 (all 6 TOML sections: server, database, storage, mcias, web, log)- Parsed with
go-toml/v2; env overrides viaMCR_prefix using reflection-based struct walker - Startup validation: 6 required fields checked (listen_addr, tls_cert, tls_key, database.path, storage.layers_path, mcias.server_url)
- Same-filesystem check for layers_path/uploads_path via device ID comparison (walks to nearest existing parent if path doesn't exist yet)
- Default values: read_timeout=30s, write_timeout=0, idle_timeout=120s, shutdown_timeout=60s, uploads_path derived from layers_path, log.level=info
device_linux.go: Linux-specificextractDeviceIDusingsyscall.Stat_tdeploy/examples/mcr.toml: annotated example config
Step 1.2 — internal/db/:
db.go:Open(path)creates/opens SQLite viamodernc.org/sqlite, sets pragmas (WAL, foreign_keys, busy_timeout=5000), chmod 0600migrate.go: migration framework withschema_migrationstracking table;Migrate()applies pending migrations in transactions;SchemaVersion()reports current version- Migration 000001:
repositories,manifests,tags,blobs,manifest_blobs,uploads— all tables, constraints, and indexes per ARCHITECTURE.md §8 - Migration 000002:
policy_rules,audit_log— tables and indexes per §8
Step 1.3 — internal/db/:
audit.go:WriteAuditEvent(eventType, actorID, repository, digest, ip, details)with JSON-serialized details map;ListAuditEvents(AuditFilter)with filtering by event_type, actor_id, repository, time range, and offset/limit pagination (default 50, descending by event_time)AuditFilterstruct with all filter fieldsAuditEventstruct with JSON tags for API serialization
Lint fix:
.golangci.yaml: disabledfieldalignmentanalyzer in govet (micro- optimization that hurts struct readability; not a security/correctness concern)
Verification:
make allpasses: vet clean, lint 0 issues, 20 tests passing (7 config + 13 db/audit), all 3 binaries built- Config tests: valid load, defaults applied, uploads_path default, 5 missing-required-field cases, env override (string + duration), same-filesystem check
- DB tests: open+migrate, idempotent migrate, 9 tables verified, foreign key enforcement, tag cascade on manifest delete, manifest_blobs cascade (blob row preserved), WAL mode verified
- Audit tests: write+list, filter by type, filter by actor, filter by repository, pagination (3 pages), null fields handled
2026-03-19 — Phase 0: Project scaffolding
Task: Set up Go module, build system, linter config, and binary entry points with cobra subcommands.
Changes:
go.mod: modulegit.wntrmute.dev/kyle/mcr, Go 1.25, cobra dependency- Directory skeleton:
cmd/mcrsrv/,cmd/mcr-web/,cmd/mcrctl/,internal/,proto/mcr/v1/,gen/mcr/v1/,web/templates/,web/static/,deploy/docker/,deploy/examples/,deploy/scripts/,deploy/systemd/,docs/ .gitignore: binaries,srv/,*.db*, IDE/OS filesMakefile: standard targets (all,build,test,vet,lint,proto,proto-lint,clean,docker,devserver);allrunsvet → lint → test → mcrsrv mcr-web mcrctl;CGO_ENABLED=0on binary builds; version injection via-X main.version.golangci.yaml: golangci-lint v2 config matching mc-proxy conventions; linters: errcheck, govet, ineffassign, unused, errorlint, gosec, staticcheck, revive; formatters: gofmt, goimports; gosec G101 excluded in test filesbuf.yaml: protobuf linting (STANDARD) and breaking change detection (FILE)cmd/mcrsrv/main.go: root command withserver,init,snapshotsubcommands (stubs returning "not implemented")cmd/mcr-web/main.go: root command withserversubcommand (stub)cmd/mcrctl/main.go: root command withstatus,repo(list/delete),gc(trigger/status),policy(list/create/update/delete),audit(tail),snapshotsubcommands (stubs)- All binaries accept
--versionflag
Verification:
make allpasses: vet clean, lint 0 issues, test (no test files), all three binaries built successfully./mcrsrv --version→mcrsrv version 3695581./mcr-web --version→mcr-web version 3695581- All stubs return "not implemented" error as expected
make cleanremoves binaries
2026-03-19 — Project planning
Task: Create design documents and implementation plan.
Changes:
README.md: Existing one-line descriptionARCHITECTURE.md: Full design specification covering OCI Distribution Spec compliance, MCIAS authentication, policy engine, storage design, API surface (OCI + admin REST + gRPC), database schema, garbage collection, configuration, web UI, CLI tools, deployment, security modelCLAUDE.md: Development guidance for AI-assisted implementationPROJECT_PLAN.md: 14-phase implementation plan with discrete steps, acceptance criteria, dependency graph, and batchable work identificationPROGRESS.md: This progress tracker
Notes:
- No code written yet. All files are documentation/planning.
- ARCHITECTURE.md reviewed and corrected for: GC algorithm crash safety, policy glob semantics, tag FK cascade, OCI error format, API sync violations, timeout configuration, backup considerations, and other consistency issues.