package grpc import ( "crypto/tls" "net" "os" "os/signal" "sync" "syscall" "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/telemetry/metrics" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" ) // NewServer creates a new gRPC serve. // It is the callers responsibility to close the resturned server. func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync.WaitGroup) (*grpc.Server, error) { if opt == nil { opt = defaultServerOptions } else { opt.applyServerDefaults() } ln, err := net.Listen("tcp", opt.Addr) if err != nil { return nil, err } grpcOpts := []grpc.ServerOption{ grpc.StatsHandler(metrics.NewGRPCServerStatsHandler(opt.ServiceName)), grpc.KeepaliveParams(opt.KeepaliveParams), } if opt.TLSCertificate != nil { log.Debug().Str("addr", opt.Addr).Msg("internal/grpc: serving over TLS") cert := credentials.NewServerTLSFromCert(opt.TLSCertificate) grpcOpts = append(grpcOpts, grpc.Creds(cert)) } else { log.Warn().Str("addr", opt.Addr).Msg("internal/grpc: serving without TLS") } srv := grpc.NewServer(grpcOpts...) registrationFn(srv) log.Info().Interface("grpc-service-info", srv.GetServiceInfo()).Msg("internal/grpc: registered") wg.Add(1) go func() { defer wg.Done() if err := srv.Serve(ln); err != grpc.ErrServerStopped { log.Error().Str("addr", opt.Addr).Err(err).Msg("internal/grpc: unexpected shutdown") } }() return srv, nil } // ServerOptions contains the configurations settings for a gRPC server. type ServerOptions struct { // Addr specifies the host and port on which the server should serve // gRPC requests. If empty, ":443" is used. Addr string // TLS certificates to use, if any. TLSCertificate *tls.Certificate // InsecureServer when enabled disables all transport security. // In this mode, Pomerium is susceptible to man-in-the-middle attacks. // This should be used only for testing. InsecureServer bool // KeepaliveParams sets GRPC keepalive.ServerParameters KeepaliveParams keepalive.ServerParameters // ServiceName specifies the service name for telemetry exposition ServiceName string } var defaultServerOptions = &ServerOptions{ Addr: ":443", } func (o *ServerOptions) applyServerDefaults() { if o.Addr == "" { o.Addr = defaultServerOptions.Addr } } // Shutdown attempts to shut down the server when a os interrupt or sigterm // signal are received without interrupting any // active connections. Shutdown stops the server from // accepting new connections and RPCs and blocks until all the pending RPCs are // finished. func Shutdown(srv *grpc.Server) { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) signal.Notify(sigint, syscall.SIGTERM) rec := <-sigint log.Info().Str("signal", rec.String()).Msg("internal/grpc: shutting down servers") srv.GracefulStop() log.Info().Str("signal", rec.String()).Msg("internal/grpc: shut down servers") }