Phase 10: gRPC admin API with interceptor chain

Proto definitions for 4 services (RegistryService, PolicyService,
AuditService, AdminService) with hand-written Go stubs using JSON
codec until protobuf tooling is available.

Interceptor chain: logging (method, peer IP, duration, never logs
auth metadata) → auth (bearer token via MCIAS, Health bypasses) →
admin (role check for GC, policy, delete, audit RPCs).

All RPCs share business logic with REST handlers via internal/db
and internal/gc packages. TLS 1.3 minimum on gRPC listener.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 20:46:21 -07:00
parent 562b69e875
commit 185b68ff6d
30 changed files with 3616 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
## Current State
**Phase:** 9 complete, ready for Phase 10
**Phase:** 10 complete, ready for Phase 11
**Last updated:** 2026-03-19
### Completed
@@ -21,6 +21,7 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
- Phase 7: OCI delete path (all 2 steps)
- Phase 8: Admin REST API (all 5 steps)
- Phase 9: Garbage collection (all 2 steps)
- Phase 10: gRPC admin API (all 4 steps)
- `ARCHITECTURE.md` — Full design specification (18 sections)
- `CLAUDE.md` — AI development guidance
- `PROJECT_PLAN.md` — Implementation plan (14 phases, 40+ steps)
@@ -28,13 +29,100 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
### Next Steps
1. Phase 10 (gRPC admin API)
2. Phase 11 (CLI tool) and Phase 12 (web UI)
1. Phase 11 (CLI tool) and Phase 12 (web UI)
---
## Log
### 2026-03-19 — Phase 10: gRPC admin API
**Task:** Implement the gRPC admin API server with the same business
logic as the REST admin API, per ARCHITECTURE.md section 7.
**Changes:**
Step 10.1 — Proto definitions (`proto/mcr/v1/`):
- `common.proto`: `PaginationRequest` shared type
- `registry.proto`: `RegistryService` with `ListRepositories`,
`GetRepository`, `DeleteRepository`, `GarbageCollect`, `GetGCStatus` RPCs
- `policy.proto`: `PolicyService` with `ListPolicyRules`,
`CreatePolicyRule`, `GetPolicyRule`, `UpdatePolicyRule`,
`DeletePolicyRule` RPCs; `UpdatePolicyRuleRequest` includes field mask
- `audit.proto`: `AuditService` with `ListAuditEvents` RPC; filter
fields for event_type, actor_id, repository, time range
- `admin.proto`: `AdminService` with `Health` RPC
Step 10.2 — Generated code (`gen/mcr/v1/`):
- Hand-written message types and gRPC service descriptors matching
protoc-gen-go v1.36+ and protoc-gen-go-grpc v1.5+ output patterns
- `codec.go`: JSON codec implementing `encoding.CodecV2` via
`mem.BufferSlice`, registered globally via `init()` as stand-in
until protobuf code generation is available
- Handler functions properly delegate to interceptor chain (critical
for auth/admin enforcement; handlers that ignore the interceptor
parameter bypass security checks entirely)
- Client and server interfaces with `mustEmbedUnimplemented*Server()`
for forward compatibility
Step 10.3 — Interceptor chain (`internal/grpcserver/interceptors.go`):
- `loggingInterceptor`: logs method, peer IP, status code, duration;
never logs the authorization metadata value
- `authInterceptor`: extracts `authorization` metadata, validates
bearer token via `TokenValidator` interface, injects claims into
context; `Health` bypasses auth via `authBypassMethods` map
- `adminInterceptor`: requires admin role for GC, policy, delete,
audit RPCs via `adminRequiredMethods` map; returns
`codes.PermissionDenied` for insufficient role
- gRPC errors: `codes.Unauthenticated` for missing/invalid token,
`codes.PermissionDenied` for insufficient role
Step 10.4 — Server implementation (`internal/grpcserver/`):
- `server.go`: `New(certFile, keyFile, deps)` creates configured
gRPC server with TLS 1.3 minimum (skips TLS if paths empty for
testing); `Serve()`, `GracefulStop()`
- `registry.go`: `registryService` implementing all 5 RPCs with
same DB calls as REST handlers; GC runs asynchronously with its
own `GCStatus` tracking (separate from REST's `GCState` since
`GCState.mu` is unexported); shares `gc.Collector` for actual GC
- `policy.go`: `policyService` implementing all 5 RPCs with
validation (effect, actions, priority), field mask updates, engine
reload, audit events
- `audit.go`: `auditService` implementing `ListAuditEvents` with
pagination and filter pass-through to `db.ListAuditEvents`
- `admin.go`: `adminService` implementing `Health` (returns "ok")
**Dependencies added:**
- `google.golang.org/grpc` v1.79.3
- `google.golang.org/protobuf` v1.36.11
- Transitive: `golang.org/x/net`, `golang.org/x/text`,
`google.golang.org/genproto/googleapis/rpc`
**Verification:**
- `make all` passes: vet clean, lint 0 issues, all tests passing,
all 3 binaries built
- Interceptor tests (12 new): Health bypasses auth, no token rejected
(Unauthenticated), invalid token rejected (Unauthenticated), valid
token accepted, non-admin denied policy RPCs (PermissionDenied),
admin allowed policy RPCs, admin required methods completeness
check, auth bypass methods completeness check, delete repo requires
admin, GC requires admin, audit requires admin
- Registry service tests (7 new): list repositories empty, get repo
not found, get repo empty name (InvalidArgument), delete repo not
found, delete repo empty name, GC status initial (not running),
GC trigger returns ID
- Policy service tests (8 new): create policy rule (with engine
reload verification), create validation (5 subtests: zero priority,
empty description, invalid effect, no actions, invalid action), get
policy rule, get not found, list policy rules, delete + verify
gone + reload count, delete not found, update with field mask
- Audit service tests (3 new): list empty, list with data (verify
fields and details map), list with pagination
- Admin service tests (2 new): Health returns "ok", Health without
auth succeeds
---
### 2026-03-19 — Phase 9: Garbage collection
**Task:** Implement the two-phase GC algorithm for removing unreferenced