Files
mcr/internal/grpcserver/server.go
Kyle Isom 758aa91bfc Migrate gRPC server to mcdsl grpcserver package
Replace MCR's custom auth, admin, and logging interceptors with the
shared mcdsl grpcserver package. This eliminates ~110 lines of
interceptor code and uses the same method-map auth pattern used by
metacrypt.

Key changes:
- server.go: delegate to mcdslgrpc.New() for TLS, logging, and auth
- interceptors.go: replaced with MethodMap definition (public, auth-required, admin-required)
- Handler files: switch from auth.ClaimsFromContext to mcdslauth.TokenInfoFromContext
- auth/client.go: add Authenticator() accessor for the underlying mcdsl authenticator
- Tests: use mock MCIAS HTTP server instead of fakeValidator interface
- Vendor: add mcdsl/grpcserver to vendor directory

ListRepositories and GetRepository are now explicitly auth-required
(not admin-required), matching the REST API. Previously they were
implicitly auth-required by not being in the bypass or admin maps.

Security: method map uses default-deny -- unmapped RPCs are rejected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:46:03 -07:00

116 lines
3.3 KiB
Go

// Package grpcserver implements the MCR gRPC admin API server.
//
// It delegates TLS, logging, and auth/admin interceptors to the mcdsl
// grpcserver package. MCR-specific business logic lives in the service
// handler files (registry.go, policy.go, audit.go, admin.go).
package grpcserver
import (
"log"
"log/slog"
"net"
"sync"
mcdslauth "git.wntrmute.dev/kyle/mcdsl/auth"
mcdslgrpc "git.wntrmute.dev/kyle/mcdsl/grpcserver"
pb "git.wntrmute.dev/kyle/mcr/gen/mcr/v1"
"git.wntrmute.dev/kyle/mcr/internal/db"
"git.wntrmute.dev/kyle/mcr/internal/gc"
"git.wntrmute.dev/kyle/mcr/internal/policy"
)
// AuditFunc is a callback for recording audit events. It follows the same
// signature as db.WriteAuditEvent but without an error return -- audit
// failures should not block request processing.
type AuditFunc func(eventType, actorID, repository, digest, ip string, details map[string]string)
// Deps holds the dependencies injected into the gRPC server.
type Deps struct {
DB *db.DB
Authenticator *mcdslauth.Authenticator
Engine PolicyReloader
AuditFn AuditFunc
Collector *gc.Collector
}
// PolicyReloader can reload policy rules from a store.
type PolicyReloader interface {
Reload(store policy.RuleStore) error
}
// GCStatus tracks the current state of garbage collection for the gRPC server.
type GCStatus struct {
mu sync.Mutex
running bool
lastRun *gcLastRun
}
type gcLastRun struct {
StartedAt string
CompletedAt string
BlobsRemoved int
BytesFreed int64
}
// Server wraps a mcdsl grpcserver.Server with MCR-specific configuration.
type Server struct {
srv *mcdslgrpc.Server
deps Deps
gcStatus *GCStatus
}
// New creates a configured gRPC server that delegates TLS setup, logging,
// and auth/admin interceptors to the mcdsl grpcserver package.
//
// If certFile or keyFile is empty, TLS is skipped (for testing only).
func New(certFile, keyFile string, deps Deps, logger *slog.Logger) (*Server, error) {
srv, err := mcdslgrpc.New(certFile, keyFile, deps.Authenticator, methodMap(), logger)
if err != nil {
return nil, err
}
// The JSON codec is registered globally via init() in gen/mcr/v1/codec.go.
// The client must use grpc.ForceCodecV2(mcrv1.JSONCodec{}) to match.
_ = pb.JSONCodec{} // ensure the gen/mcr/v1 init() runs (codec registration)
gcStatus := &GCStatus{}
s := &Server{srv: srv, deps: deps, gcStatus: gcStatus}
// Register all services.
pb.RegisterRegistryServiceServer(srv.GRPCServer, &registryService{
db: deps.DB,
collector: deps.Collector,
gcStatus: gcStatus,
auditFn: deps.AuditFn,
})
pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyService{
db: deps.DB,
engine: deps.Engine,
auditFn: deps.AuditFn,
})
pb.RegisterAuditServiceServer(srv.GRPCServer, &auditService{
db: deps.DB,
})
pb.RegisterAdminServiceServer(srv.GRPCServer, &adminService{})
return s, nil
}
// Serve starts the gRPC server on the given listener.
func (s *Server) Serve(lis net.Listener) error {
log.Printf("grpc server listening on %s", lis.Addr())
return s.srv.GRPCServer.Serve(lis)
}
// GracefulStop gracefully stops the gRPC server.
func (s *Server) GracefulStop() {
s.srv.Stop()
}
// GRPCServer returns the underlying grpc.Server for testing.
func (s *Server) GRPCServer() *mcdslgrpc.Server {
return s.srv
}