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:
68
PROGRESS.md
68
PROGRESS.md
@@ -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.2–3.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,
|
||||
|
||||
Reference in New Issue
Block a user