// Package grpcserver implements the gRPC server for Metacrypt. package grpcserver import ( "context" "fmt" "log/slog" "strings" "sync" "google.golang.org/grpc" "google.golang.org/grpc/metadata" pb "git.wntrmute.dev/mc/metacrypt/gen/metacrypt/v2" "git.wntrmute.dev/mc/mcdsl/grpcserver" internacme "git.wntrmute.dev/mc/metacrypt/internal/acme" "git.wntrmute.dev/mc/metacrypt/internal/audit" "git.wntrmute.dev/mc/metacrypt/internal/auth" "git.wntrmute.dev/mc/metacrypt/internal/config" "git.wntrmute.dev/mc/metacrypt/internal/engine" "git.wntrmute.dev/mc/metacrypt/internal/policy" "git.wntrmute.dev/mc/metacrypt/internal/seal" ) // GRPCServer wraps the mcdsl gRPC server and all service implementations. type GRPCServer struct { cfg *config.Config sealMgr *seal.Manager auth *auth.Authenticator policy *policy.Engine engines *engine.Registry audit *audit.Logger logger *slog.Logger srv *grpcserver.Server acmeHandlers map[string]*internacme.Handler mu sync.Mutex } // New creates a new GRPCServer. func New(cfg *config.Config, sealMgr *seal.Manager, authenticator *auth.Authenticator, policyEngine *policy.Engine, engineRegistry *engine.Registry, auditLog *audit.Logger, logger *slog.Logger) *GRPCServer { return &GRPCServer{ cfg: cfg, sealMgr: sealMgr, auth: authenticator, policy: policyEngine, engines: engineRegistry, audit: auditLog, logger: logger, acmeHandlers: make(map[string]*internacme.Handler), } } // Start starts the gRPC server on cfg.Server.GRPCAddr. func (s *GRPCServer) Start() error { if s.cfg.Server.GRPCAddr == "" { s.logger.Info("grpc_addr not configured, gRPC server disabled") return nil } opts := &grpcserver.Options{ PreInterceptors: []grpc.UnaryServerInterceptor{sealInterceptor(s.sealMgr, s.logger, sealRequiredMethods())}, PostInterceptors: []grpc.UnaryServerInterceptor{auditInterceptor(s.audit)}, } srv, err := grpcserver.New( s.cfg.Server.TLSCert, s.cfg.Server.TLSKey, s.auth, methodMap(), s.logger, opts, ) if err != nil { return fmt.Errorf("grpc: create server: %w", err) } s.srv = srv pb.RegisterSystemServiceServer(srv.GRPCServer, &systemServer{s: s}) pb.RegisterAuthServiceServer(srv.GRPCServer, &authServer{s: s}) pb.RegisterEngineServiceServer(srv.GRPCServer, &engineServer{s: s}) pb.RegisterPKIServiceServer(srv.GRPCServer, &pkiServer{s: s}) pb.RegisterCAServiceServer(srv.GRPCServer, &caServer{s: s}) pb.RegisterPolicyServiceServer(srv.GRPCServer, &policyServer{s: s}) pb.RegisterBarrierServiceServer(srv.GRPCServer, &barrierServer{s: s}) pb.RegisterUserServiceServer(srv.GRPCServer, &userServer{s: s}) pb.RegisterACMEServiceServer(srv.GRPCServer, &acmeServer{s: s}) pb.RegisterSSHCAServiceServer(srv.GRPCServer, &sshcaServer{s: s}) pb.RegisterTransitServiceServer(srv.GRPCServer, &transitServer{s: s}) return srv.Serve(s.cfg.Server.GRPCAddr) } // Shutdown gracefully stops the gRPC server. func (s *GRPCServer) Shutdown() { if s.srv != nil { s.srv.Stop() } } // extractToken extracts the bearer token from gRPC metadata. Used by the // auth service Logout handler which needs the raw token. func extractToken(ctx context.Context) string { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "" } vals := md.Get("authorization") if len(vals) == 0 { return "" } v := vals[0] if strings.HasPrefix(v, "Bearer ") { return strings.TrimPrefix(v, "Bearer ") } return v } // callerUsername returns the username from the token info in the context, // or empty string if no token info is present. func callerUsername(ctx context.Context) string { ti := auth.TokenInfoFromContext(ctx) if ti == nil { return "" } return ti.Username } // methodMap builds the mcdsl grpcserver.MethodMap for Metacrypt. // // Public methods require no authentication. Auth-required and admin-required // methods are enforced by the mcdsl auth interceptor. func methodMap() grpcserver.MethodMap { return grpcserver.MethodMap{ Public: publicMethods(), AuthRequired: authRequiredMethods(), AdminRequired: adminRequiredMethods(), } } // publicMethods returns methods that require no authentication. // These include system lifecycle RPCs (status, init, unseal), the login // endpoint, and read-only PKI/CA/SSH CA endpoints that serve public // certificates and keys. func publicMethods() map[string]bool { return map[string]bool{ // System lifecycle — always available. "/metacrypt.v2.SystemService/Status": true, "/metacrypt.v2.SystemService/Init": true, "/metacrypt.v2.SystemService/Unseal": true, // Auth — login requires no existing token. "/metacrypt.v2.AuthService/Login": true, // PKI read-only — public certificates. "/metacrypt.v2.PKIService/GetRootCert": true, "/metacrypt.v2.PKIService/GetChain": true, "/metacrypt.v2.PKIService/GetIssuerCert": true, // CA read-only — public certificates and chains. "/metacrypt.v2.CAService/GetRoot": true, "/metacrypt.v2.CAService/GetIssuer": true, "/metacrypt.v2.CAService/GetChain": true, // SSH CA — public key and key revocation list. "/metacrypt.v2.SSHCAService/GetCAPublicKey": true, "/metacrypt.v2.SSHCAService/GetKRL": true, } } // authRequiredMethods returns methods that require a valid MCIAS token but // not necessarily the admin role. func authRequiredMethods() map[string]bool { return map[string]bool{ "/metacrypt.v2.AuthService/Logout": true, "/metacrypt.v2.AuthService/TokenInfo": true, "/metacrypt.v2.EngineService/ListMounts": true, "/metacrypt.v2.CAService/ListIssuers": true, "/metacrypt.v2.CAService/IssueCert": true, "/metacrypt.v2.CAService/GetCert": true, "/metacrypt.v2.CAService/ListCerts": true, "/metacrypt.v2.CAService/RenewCert": true, "/metacrypt.v2.CAService/SignCSR": true, "/metacrypt.v2.UserService/Register": true, "/metacrypt.v2.UserService/GetPublicKey": true, "/metacrypt.v2.UserService/ListUsers": true, "/metacrypt.v2.UserService/Encrypt": true, "/metacrypt.v2.UserService/Decrypt": true, "/metacrypt.v2.UserService/ReEncrypt": true, "/metacrypt.v2.UserService/RotateKey": true, "/metacrypt.v2.ACMEService/CreateEAB": true, // SSH CA — sign and read operations. "/metacrypt.v2.SSHCAService/SignHost": true, "/metacrypt.v2.SSHCAService/SignUser": true, "/metacrypt.v2.SSHCAService/GetProfile": true, "/metacrypt.v2.SSHCAService/ListProfiles": true, "/metacrypt.v2.SSHCAService/GetCert": true, "/metacrypt.v2.SSHCAService/ListCerts": true, // Transit — data-plane operations. "/metacrypt.v2.TransitService/GetKey": true, "/metacrypt.v2.TransitService/ListKeys": true, "/metacrypt.v2.TransitService/Encrypt": true, "/metacrypt.v2.TransitService/Decrypt": true, "/metacrypt.v2.TransitService/Rewrap": true, "/metacrypt.v2.TransitService/BatchEncrypt": true, "/metacrypt.v2.TransitService/BatchDecrypt": true, "/metacrypt.v2.TransitService/BatchRewrap": true, "/metacrypt.v2.TransitService/Sign": true, "/metacrypt.v2.TransitService/Verify": true, "/metacrypt.v2.TransitService/Hmac": true, "/metacrypt.v2.TransitService/GetPublicKey": true, } } // adminRequiredMethods returns methods that require a valid MCIAS token // with the admin role. func adminRequiredMethods() map[string]bool { return map[string]bool{ "/metacrypt.v2.SystemService/Seal": true, "/metacrypt.v2.EngineService/Mount": true, "/metacrypt.v2.EngineService/Unmount": true, "/metacrypt.v2.CAService/ImportRoot": true, "/metacrypt.v2.CAService/CreateIssuer": true, "/metacrypt.v2.CAService/DeleteIssuer": true, "/metacrypt.v2.CAService/RevokeCert": true, "/metacrypt.v2.CAService/DeleteCert": true, "/metacrypt.v2.PolicyService/CreatePolicy": true, "/metacrypt.v2.PolicyService/ListPolicies": true, "/metacrypt.v2.PolicyService/GetPolicy": true, "/metacrypt.v2.PolicyService/DeletePolicy": true, // User. "/metacrypt.v2.UserService/Provision": true, "/metacrypt.v2.UserService/DeleteUser": true, "/metacrypt.v2.ACMEService/SetConfig": true, "/metacrypt.v2.ACMEService/ListAccounts": true, "/metacrypt.v2.ACMEService/ListOrders": true, "/metacrypt.v2.BarrierService/ListKeys": true, "/metacrypt.v2.BarrierService/RotateMEK": true, "/metacrypt.v2.BarrierService/RotateKey": true, "/metacrypt.v2.BarrierService/Migrate": true, // SSH CA. "/metacrypt.v2.SSHCAService/CreateProfile": true, "/metacrypt.v2.SSHCAService/UpdateProfile": true, "/metacrypt.v2.SSHCAService/DeleteProfile": true, "/metacrypt.v2.SSHCAService/RevokeCert": true, "/metacrypt.v2.SSHCAService/DeleteCert": true, // Transit. "/metacrypt.v2.TransitService/CreateKey": true, "/metacrypt.v2.TransitService/DeleteKey": true, "/metacrypt.v2.TransitService/RotateKey": true, "/metacrypt.v2.TransitService/UpdateKeyConfig": true, "/metacrypt.v2.TransitService/TrimKey": true, } } // sealRequiredMethods returns the set of RPC full names that require the vault // to be unsealed. This is used by the sealInterceptor PreInterceptor. func sealRequiredMethods() map[string]bool { return map[string]bool{ "/metacrypt.v2.AuthService/Login": true, "/metacrypt.v2.AuthService/Logout": true, "/metacrypt.v2.AuthService/TokenInfo": true, "/metacrypt.v2.EngineService/Mount": true, "/metacrypt.v2.EngineService/Unmount": true, "/metacrypt.v2.EngineService/ListMounts": true, "/metacrypt.v2.PKIService/GetRootCert": true, "/metacrypt.v2.PKIService/GetChain": true, "/metacrypt.v2.PKIService/GetIssuerCert": true, "/metacrypt.v2.CAService/ImportRoot": true, "/metacrypt.v2.CAService/GetRoot": true, "/metacrypt.v2.CAService/CreateIssuer": true, "/metacrypt.v2.CAService/DeleteIssuer": true, "/metacrypt.v2.CAService/ListIssuers": true, "/metacrypt.v2.CAService/GetIssuer": true, "/metacrypt.v2.CAService/GetChain": true, "/metacrypt.v2.CAService/IssueCert": true, "/metacrypt.v2.CAService/GetCert": true, "/metacrypt.v2.CAService/ListCerts": true, "/metacrypt.v2.CAService/RenewCert": true, "/metacrypt.v2.CAService/SignCSR": true, "/metacrypt.v2.CAService/RevokeCert": true, "/metacrypt.v2.CAService/DeleteCert": true, "/metacrypt.v2.PolicyService/CreatePolicy": true, "/metacrypt.v2.PolicyService/ListPolicies": true, "/metacrypt.v2.PolicyService/GetPolicy": true, "/metacrypt.v2.PolicyService/DeletePolicy": true, "/metacrypt.v2.UserService/Register": true, "/metacrypt.v2.UserService/Provision": true, "/metacrypt.v2.UserService/GetPublicKey": true, "/metacrypt.v2.UserService/ListUsers": true, "/metacrypt.v2.UserService/Encrypt": true, "/metacrypt.v2.UserService/Decrypt": true, "/metacrypt.v2.UserService/ReEncrypt": true, "/metacrypt.v2.UserService/RotateKey": true, "/metacrypt.v2.UserService/DeleteUser": true, "/metacrypt.v2.ACMEService/CreateEAB": true, "/metacrypt.v2.ACMEService/SetConfig": true, "/metacrypt.v2.ACMEService/ListAccounts": true, "/metacrypt.v2.ACMEService/ListOrders": true, "/metacrypt.v2.BarrierService/ListKeys": true, "/metacrypt.v2.BarrierService/RotateMEK": true, "/metacrypt.v2.BarrierService/RotateKey": true, "/metacrypt.v2.BarrierService/Migrate": true, // SSH CA. "/metacrypt.v2.SSHCAService/GetCAPublicKey": true, "/metacrypt.v2.SSHCAService/SignHost": true, "/metacrypt.v2.SSHCAService/SignUser": true, "/metacrypt.v2.SSHCAService/CreateProfile": true, "/metacrypt.v2.SSHCAService/UpdateProfile": true, "/metacrypt.v2.SSHCAService/GetProfile": true, "/metacrypt.v2.SSHCAService/ListProfiles": true, "/metacrypt.v2.SSHCAService/DeleteProfile": true, "/metacrypt.v2.SSHCAService/GetCert": true, "/metacrypt.v2.SSHCAService/ListCerts": true, "/metacrypt.v2.SSHCAService/RevokeCert": true, "/metacrypt.v2.SSHCAService/DeleteCert": true, "/metacrypt.v2.SSHCAService/GetKRL": true, // Transit. "/metacrypt.v2.TransitService/CreateKey": true, "/metacrypt.v2.TransitService/DeleteKey": true, "/metacrypt.v2.TransitService/GetKey": true, "/metacrypt.v2.TransitService/ListKeys": true, "/metacrypt.v2.TransitService/RotateKey": true, "/metacrypt.v2.TransitService/UpdateKeyConfig": true, "/metacrypt.v2.TransitService/TrimKey": true, "/metacrypt.v2.TransitService/Encrypt": true, "/metacrypt.v2.TransitService/Decrypt": true, "/metacrypt.v2.TransitService/Rewrap": true, "/metacrypt.v2.TransitService/BatchEncrypt": true, "/metacrypt.v2.TransitService/BatchDecrypt": true, "/metacrypt.v2.TransitService/BatchRewrap": true, "/metacrypt.v2.TransitService/Sign": true, "/metacrypt.v2.TransitService/Verify": true, "/metacrypt.v2.TransitService/Hmac": true, "/metacrypt.v2.TransitService/GetPublicKey": true, // Seal itself requires unsealed state. "/metacrypt.v2.SystemService/Seal": true, } }