Switch gRPC admin API to Unix socket only, add client package

- Remove TCP listener support from gRPC server; Unix socket is now the
  only transport for the admin API (access controlled via filesystem
  permissions)
- Add standard gRPC health check service (grpc.health.v1.Health)
- Implement MCPROXY_* environment variable overrides for config
- Create client/mcproxy package with full API coverage and tests
- Update ARCHITECTURE.md and dev config (srv/mc-proxy.toml)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 07:48:11 -07:00
parent b25e1b0e79
commit f24fa2a2b0
9 changed files with 810 additions and 137 deletions

View File

@@ -2,8 +2,6 @@ package grpcserver
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log/slog"
"net"
@@ -14,7 +12,8 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -34,8 +33,16 @@ type AdminServer struct {
logger *slog.Logger
}
// New creates a gRPC server. For Unix sockets, no TLS is used. For TCP
// addresses, TLS is required with optional mTLS.
// NewAdminServer creates an AdminServer for use in testing or custom setups.
func NewAdminServer(srv *server.Server, store *db.Store, logger *slog.Logger) *AdminServer {
return &AdminServer{
srv: srv,
store: store,
logger: logger,
}
}
// New creates a gRPC server listening on a Unix socket.
func New(cfg config.GRPC, srv *server.Server, store *db.Store, logger *slog.Logger) (*grpc.Server, net.Listener, error) {
admin := &AdminServer{
srv: srv,
@@ -43,13 +50,6 @@ func New(cfg config.GRPC, srv *server.Server, store *db.Store, logger *slog.Logg
logger: logger,
}
if cfg.IsUnixSocket() {
return newUnixServer(cfg, admin)
}
return newTCPServer(cfg, admin)
}
func newUnixServer(cfg config.GRPC, admin *AdminServer) (*grpc.Server, net.Listener, error) {
path := cfg.SocketPath()
// Remove stale socket file from a previous run.
@@ -67,41 +67,12 @@ func newUnixServer(cfg config.GRPC, admin *AdminServer) (*grpc.Server, net.Liste
grpcServer := grpc.NewServer()
pb.RegisterProxyAdminServiceServer(grpcServer, admin)
return grpcServer, ln, nil
}
func newTCPServer(cfg config.GRPC, admin *AdminServer) (*grpc.Server, net.Listener, error) {
cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey)
if err != nil {
return nil, nil, fmt.Errorf("loading TLS keypair: %w", err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
if cfg.ClientCA != "" {
caCert, err := os.ReadFile(cfg.ClientCA)
if err != nil {
return nil, nil, fmt.Errorf("reading client CA: %w", err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCert) {
return nil, nil, fmt.Errorf("failed to parse client CA certificate")
}
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = pool
}
creds := credentials.NewTLS(tlsConfig)
grpcServer := grpc.NewServer(grpc.Creds(creds))
pb.RegisterProxyAdminServiceServer(grpcServer, admin)
ln, err := net.Listen("tcp", cfg.Addr)
if err != nil {
return nil, nil, fmt.Errorf("listening on %s: %w", cfg.Addr, err)
}
// Register standard gRPC health check service.
healthServer := health.NewServer()
healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthServer.SetServingStatus("mc_proxy.v1.ProxyAdminService", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(grpcServer, healthServer)
return grpcServer, ln, nil
}