Batch A: blob storage layer, MCIAS auth, OCI token endpoint

Phase 2 — internal/storage/:
Content-addressed blob storage with atomic writes via rename.
BlobWriter stages data in uploads dir with running SHA-256 hash,
commits by verifying digest then renaming to layers/sha256/<prefix>/<hex>.
Reader provides Open, Stat, Delete, Exists with digest validation.

Phase 3 — internal/auth/ + internal/server/:
MCIAS client with Login and ValidateToken, 30s SHA-256-keyed cache
with lazy eviction and injectable clock for testing. TLS 1.3 minimum
with optional custom CA cert.
Chi router with RequireAuth middleware (Bearer token extraction,
WWW-Authenticate header, OCI error format), token endpoint (Basic
auth → bearer exchange via MCIAS), and /v2/ version check handler.

52 tests passing (14 storage + 9 auth + 9 server + 20 existing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 14:51:19 -07:00
parent fde66be9c1
commit 3314b7a618
25 changed files with 1696 additions and 6 deletions

View File

@@ -6,13 +6,15 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
## Current State
**Phase:** 1 complete, ready for Batch A (Phase 2 + Phase 3)
**Phase:** Batch A complete (Phases 2 + 3), ready for Phase 4 (policy engine)
**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)
- `ARCHITECTURE.md` — Full design specification (18 sections)
- `CLAUDE.md` — AI development guidance
- `PROJECT_PLAN.md` — Implementation plan (14 phases, 40+ steps)
@@ -20,14 +22,72 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
### Next Steps
1. Begin Batch A: Phase 2 (blob storage) and Phase 3 (MCIAS auth)
in parallel — these are independent
2. After both complete, proceed to Phase 4 (policy engine)
1. Phase 4: Policy engine (depends on Phase 3)
2. After Phase 4, Batch B: Phase 5 (OCI pull) and Phase 8 (admin REST)
---
## Log
### 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`: `Store` struct with `layersPath`/`uploadsPath`, `New()`
constructor, digest validation (`^sha256:[a-f0-9]{64}$`), content-addressed
path layout: `<layers>/sha256/<first-2-hex>/<full-64-hex>`
- `writer.go`: `BlobWriter` wrapping `*os.File` + `crypto/sha256` running hash
via `io.MultiWriter`. `StartUpload(uuid)` creates temp file in uploads dir.
`Write()` updates both file and hash. `Commit(expectedDigest)` finalizes hash,
verifies digest, `MkdirAll` prefix dir, `Rename` atomically. `Cancel()` cleans
up temp file. `BytesWritten()` returns offset.
- `reader.go`: `Open(digest)` returns `io.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.23.4):
- `auth/client.go`: `Client` with `NewClient(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`: `Claims` struct (Subject, AccountType, Roles) with context
helpers `ContextWithClaims`/`ClaimsFromContext`
- `auth/cache.go`: `validationCache` with `sync.RWMutex`, lazy eviction,
injectable `now` function for testing
- `auth/errors.go`: `ErrUnauthorized`, `ErrMCIASUnavailable`
- `server/middleware.go`: `TokenValidator` interface, `RequireAuth` middleware
(Bearer token extraction, `WWW-Authenticate` header, OCI error format)
- `server/token.go`: `LoginClient` interface, `TokenHandler` (Basic auth →
bearer token exchange via MCIAS, RFC 3339 `issued_at`)
- `server/v2.go`: `V2Handler` returning 200 `{}`
- `server/routes.go`: `NewRouter` with 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 all` passes: 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,