mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
243 lines
8.0 KiB
Go
243 lines
8.0 KiB
Go
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/log"
|
|
|
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
|
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
|
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
|
|
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
|
"github.com/opentracing/opentracing-go"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/xerrors"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/backoff"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/grpclog"
|
|
"google.golang.org/grpc/keepalive"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// maxMsgSize use 16MB as the default message size limit.
|
|
// grpc library default is 4MB
|
|
const maxMsgSize = 1024 * 1024 * 16
|
|
|
|
var defaultClientOptionsConfig struct {
|
|
Metrics *grpc_prometheus.ClientMetrics
|
|
}
|
|
|
|
// ClientMetrics produces client-side gRPC metrics
|
|
func ClientMetrics() prometheus.Collector {
|
|
res := grpc_prometheus.NewClientMetrics()
|
|
defaultClientOptionsConfig.Metrics = res
|
|
return res
|
|
}
|
|
|
|
// DefaultClientOptions returns the default grpc client connection options
|
|
func DefaultClientOptions() []grpc.DialOption {
|
|
bfConf := backoff.DefaultConfig
|
|
bfConf.MaxDelay = 5 * time.Second
|
|
|
|
var (
|
|
unaryInterceptor = []grpc.UnaryClientInterceptor{
|
|
grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer())),
|
|
}
|
|
streamInterceptor = []grpc.StreamClientInterceptor{
|
|
grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer())),
|
|
}
|
|
)
|
|
if defaultClientOptionsConfig.Metrics != nil {
|
|
unaryInterceptor = append(unaryInterceptor, defaultClientOptionsConfig.Metrics.UnaryClientInterceptor())
|
|
streamInterceptor = append(streamInterceptor, defaultClientOptionsConfig.Metrics.StreamClientInterceptor())
|
|
}
|
|
|
|
res := []grpc.DialOption{
|
|
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptor...)),
|
|
grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(streamInterceptor...)),
|
|
grpc.WithBlock(),
|
|
grpc.WithConnectParams(grpc.ConnectParams{
|
|
Backoff: bfConf,
|
|
}),
|
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
|
Time: 10 * time.Second,
|
|
Timeout: time.Second,
|
|
PermitWithoutStream: true,
|
|
}),
|
|
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// DefaultServerOptions returns the default ServerOption sets options for internal components
|
|
func DefaultServerOptions() []grpc.ServerOption {
|
|
return ServerOptionsWithInterceptors([]grpc.StreamServerInterceptor{}, []grpc.UnaryServerInterceptor{})
|
|
}
|
|
|
|
// ServerOptionsWithInterceptors returns the default ServerOption sets options for internal components with additional interceptors.
|
|
// By default, Interceptors for OpenTracing (grpc_opentracing) are added as the last one.
|
|
func ServerOptionsWithInterceptors(stream []grpc.StreamServerInterceptor, unary []grpc.UnaryServerInterceptor) []grpc.ServerOption {
|
|
tracingFilterFunc := grpc_opentracing.WithFilterFunc(func(ctx context.Context, fullMethodName string) bool {
|
|
return fullMethodName != "/grpc.health.v1.Health/Check"
|
|
})
|
|
|
|
stream = append(stream,
|
|
grpc_opentracing.StreamServerInterceptor(tracingFilterFunc),
|
|
grpc_recovery.StreamServerInterceptor(), // must be last, to be executed first after the rpc handler, we want upstream interceptors to have a meaningful response to work with
|
|
)
|
|
unary = append(unary,
|
|
grpc_opentracing.UnaryServerInterceptor(tracingFilterFunc),
|
|
grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(
|
|
func(ctx context.Context, p interface{}) error {
|
|
log.WithField("stack", string(debug.Stack())).Errorf("[PANIC] %s", p)
|
|
return status.Errorf(codes.Internal, "%s", p)
|
|
},
|
|
)), // must be last, to be executed first after the rpc handler, we want upstream interceptors to have a meaningful response to work with
|
|
)
|
|
|
|
return []grpc.ServerOption{
|
|
// terminate the connection if the client pings more than once every 2 seconds
|
|
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
|
MinTime: 10 * time.Second,
|
|
PermitWithoutStream: true,
|
|
}),
|
|
grpc.MaxRecvMsgSize(maxMsgSize),
|
|
// We don't know how good our cients are at closing connections. If they don't close them properly
|
|
// we'll be leaking goroutines left and right. Closing Idle connections should prevent that.
|
|
grpc.KeepaliveParams(keepalive.ServerParameters{
|
|
MaxConnectionIdle: 30 * time.Minute,
|
|
}),
|
|
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream...)),
|
|
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary...)),
|
|
}
|
|
}
|
|
|
|
func SetupLogging() {
|
|
grpclog.SetLoggerV2(grpclog.NewLoggerV2(
|
|
log.WithField("component", "grpc").WriterLevel(logrus.DebugLevel),
|
|
log.WithField("component", "grpc").WriterLevel(logrus.WarnLevel),
|
|
log.WithField("component", "grpc").WriterLevel(logrus.ErrorLevel),
|
|
))
|
|
}
|
|
|
|
// TLSConfigOption configures a TLSConfig
|
|
type TLSConfigOption func(*tlsConfigOptions) error
|
|
|
|
type tlsConfigOptions struct {
|
|
ClientAuth tls.ClientAuthType
|
|
ServerName string
|
|
|
|
RootCAs bool
|
|
ClientCAs bool
|
|
}
|
|
|
|
// WithClientAuth configures a policy for TLS Client Authentication
|
|
func WithClientAuth(authType tls.ClientAuthType) TLSConfigOption {
|
|
return func(ico *tlsConfigOptions) error {
|
|
ico.ClientAuth = authType
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithServerName configures thge ServerName used to verify the hostname
|
|
func WithServerName(serverName string) TLSConfigOption {
|
|
return func(ico *tlsConfigOptions) error {
|
|
ico.ServerName = serverName
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func WithSetRootCAs(rootCAs bool) TLSConfigOption {
|
|
return func(ico *tlsConfigOptions) error {
|
|
ico.RootCAs = rootCAs
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func WithSetClientCAs(clientCAs bool) TLSConfigOption {
|
|
return func(ico *tlsConfigOptions) error {
|
|
ico.ClientCAs = clientCAs
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func ClientAuthTLSConfig(authority, certificate, privateKey string, opts ...TLSConfigOption) (*tls.Config, error) {
|
|
// Telepresence (used for debugging only) requires special paths to load files from
|
|
if root := os.Getenv("TELEPRESENCE_ROOT"); root != "" {
|
|
authority = filepath.Join(root, authority)
|
|
certificate = filepath.Join(root, certificate)
|
|
privateKey = filepath.Join(root, privateKey)
|
|
}
|
|
|
|
// Load certs
|
|
tlsCertificate, err := tls.LoadX509KeyPair(certificate, privateKey)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("cannot load TLS certificate: %w", err)
|
|
}
|
|
|
|
// Create a certificate pool from the certificate authority
|
|
certPool := x509.NewCertPool()
|
|
ca, err := os.ReadFile(authority)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("cannot not read ca certificate: %w", err)
|
|
}
|
|
|
|
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
|
return nil, xerrors.Errorf("failed to append ca certs")
|
|
}
|
|
|
|
options := tlsConfigOptions{}
|
|
for _, o := range opts {
|
|
err := o(&options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{tlsCertificate},
|
|
CipherSuites: []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
},
|
|
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
|
|
MinVersion: tls.VersionTLS12,
|
|
MaxVersion: tls.VersionTLS12,
|
|
NextProtos: []string{"h2"},
|
|
}
|
|
|
|
tlsConfig.ServerName = options.ServerName
|
|
|
|
if options.ClientAuth != tls.NoClientCert {
|
|
log.WithField("clientAuth", options.ClientAuth).Info("enabling client authentication")
|
|
tlsConfig.ClientAuth = options.ClientAuth
|
|
}
|
|
|
|
if options.ClientCAs {
|
|
tlsConfig.ClientCAs = certPool
|
|
}
|
|
|
|
if options.RootCAs {
|
|
tlsConfig.RootCAs = certPool
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|