Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/common-go/grpc/grpc.go
2498 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package grpc
6
7
import (
8
"context"
9
"crypto/tls"
10
"crypto/x509"
11
"os"
12
"path/filepath"
13
"runtime/debug"
14
"time"
15
16
"github.com/gitpod-io/gitpod/common-go/log"
17
18
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
19
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
20
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
21
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
22
"github.com/opentracing/opentracing-go"
23
"github.com/prometheus/client_golang/prometheus"
24
"github.com/sirupsen/logrus"
25
"golang.org/x/xerrors"
26
"google.golang.org/grpc"
27
"google.golang.org/grpc/backoff"
28
"google.golang.org/grpc/codes"
29
"google.golang.org/grpc/grpclog"
30
"google.golang.org/grpc/keepalive"
31
"google.golang.org/grpc/status"
32
)
33
34
// maxMsgSize use 16MB as the default message size limit.
35
// grpc library default is 4MB
36
const maxMsgSize = 1024 * 1024 * 16
37
38
var defaultClientOptionsConfig struct {
39
Metrics *grpc_prometheus.ClientMetrics
40
}
41
42
// ClientMetrics produces client-side gRPC metrics
43
func ClientMetrics() prometheus.Collector {
44
res := grpc_prometheus.NewClientMetrics()
45
defaultClientOptionsConfig.Metrics = res
46
return res
47
}
48
49
// DefaultClientOptions returns the default grpc client connection options
50
func DefaultClientOptions() []grpc.DialOption {
51
bfConf := backoff.DefaultConfig
52
bfConf.MaxDelay = 5 * time.Second
53
54
var (
55
unaryInterceptor = []grpc.UnaryClientInterceptor{
56
grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer())),
57
}
58
streamInterceptor = []grpc.StreamClientInterceptor{
59
grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer())),
60
}
61
)
62
if defaultClientOptionsConfig.Metrics != nil {
63
unaryInterceptor = append(unaryInterceptor, defaultClientOptionsConfig.Metrics.UnaryClientInterceptor())
64
streamInterceptor = append(streamInterceptor, defaultClientOptionsConfig.Metrics.StreamClientInterceptor())
65
}
66
67
res := []grpc.DialOption{
68
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptor...)),
69
grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(streamInterceptor...)),
70
grpc.WithBlock(),
71
grpc.WithConnectParams(grpc.ConnectParams{
72
Backoff: bfConf,
73
}),
74
grpc.WithKeepaliveParams(keepalive.ClientParameters{
75
Time: 10 * time.Second,
76
Timeout: time.Second,
77
PermitWithoutStream: true,
78
}),
79
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
80
}
81
82
return res
83
}
84
85
// DefaultServerOptions returns the default ServerOption sets options for internal components
86
func DefaultServerOptions() []grpc.ServerOption {
87
return ServerOptionsWithInterceptors([]grpc.StreamServerInterceptor{}, []grpc.UnaryServerInterceptor{})
88
}
89
90
// ServerOptionsWithInterceptors returns the default ServerOption sets options for internal components with additional interceptors.
91
// By default, Interceptors for OpenTracing (grpc_opentracing) are added as the last one.
92
func ServerOptionsWithInterceptors(stream []grpc.StreamServerInterceptor, unary []grpc.UnaryServerInterceptor) []grpc.ServerOption {
93
tracingFilterFunc := grpc_opentracing.WithFilterFunc(func(ctx context.Context, fullMethodName string) bool {
94
return fullMethodName != "/grpc.health.v1.Health/Check"
95
})
96
97
stream = append(stream,
98
grpc_opentracing.StreamServerInterceptor(tracingFilterFunc),
99
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
100
)
101
unary = append(unary,
102
grpc_opentracing.UnaryServerInterceptor(tracingFilterFunc),
103
grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(
104
func(ctx context.Context, p interface{}) error {
105
log.WithField("stack", string(debug.Stack())).Errorf("[PANIC] %s", p)
106
return status.Errorf(codes.Internal, "%s", p)
107
},
108
)), // must be last, to be executed first after the rpc handler, we want upstream interceptors to have a meaningful response to work with
109
)
110
111
return []grpc.ServerOption{
112
// terminate the connection if the client pings more than once every 2 seconds
113
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
114
MinTime: 10 * time.Second,
115
PermitWithoutStream: true,
116
}),
117
grpc.MaxRecvMsgSize(maxMsgSize),
118
// We don't know how good our cients are at closing connections. If they don't close them properly
119
// we'll be leaking goroutines left and right. Closing Idle connections should prevent that.
120
grpc.KeepaliveParams(keepalive.ServerParameters{
121
MaxConnectionIdle: 30 * time.Minute,
122
}),
123
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream...)),
124
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary...)),
125
}
126
}
127
128
func SetupLogging() {
129
grpclog.SetLoggerV2(grpclog.NewLoggerV2(
130
log.WithField("component", "grpc").WriterLevel(logrus.DebugLevel),
131
log.WithField("component", "grpc").WriterLevel(logrus.WarnLevel),
132
log.WithField("component", "grpc").WriterLevel(logrus.ErrorLevel),
133
))
134
}
135
136
// TLSConfigOption configures a TLSConfig
137
type TLSConfigOption func(*tlsConfigOptions) error
138
139
type tlsConfigOptions struct {
140
ClientAuth tls.ClientAuthType
141
ServerName string
142
143
RootCAs bool
144
ClientCAs bool
145
}
146
147
// WithClientAuth configures a policy for TLS Client Authentication
148
func WithClientAuth(authType tls.ClientAuthType) TLSConfigOption {
149
return func(ico *tlsConfigOptions) error {
150
ico.ClientAuth = authType
151
return nil
152
}
153
}
154
155
// WithServerName configures thge ServerName used to verify the hostname
156
func WithServerName(serverName string) TLSConfigOption {
157
return func(ico *tlsConfigOptions) error {
158
ico.ServerName = serverName
159
return nil
160
}
161
}
162
163
func WithSetRootCAs(rootCAs bool) TLSConfigOption {
164
return func(ico *tlsConfigOptions) error {
165
ico.RootCAs = rootCAs
166
return nil
167
}
168
}
169
170
func WithSetClientCAs(clientCAs bool) TLSConfigOption {
171
return func(ico *tlsConfigOptions) error {
172
ico.ClientCAs = clientCAs
173
return nil
174
}
175
}
176
177
func ClientAuthTLSConfig(authority, certificate, privateKey string, opts ...TLSConfigOption) (*tls.Config, error) {
178
// Telepresence (used for debugging only) requires special paths to load files from
179
if root := os.Getenv("TELEPRESENCE_ROOT"); root != "" {
180
authority = filepath.Join(root, authority)
181
certificate = filepath.Join(root, certificate)
182
privateKey = filepath.Join(root, privateKey)
183
}
184
185
// Load certs
186
tlsCertificate, err := tls.LoadX509KeyPair(certificate, privateKey)
187
if err != nil {
188
return nil, xerrors.Errorf("cannot load TLS certificate: %w", err)
189
}
190
191
// Create a certificate pool from the certificate authority
192
certPool := x509.NewCertPool()
193
ca, err := os.ReadFile(authority)
194
if err != nil {
195
return nil, xerrors.Errorf("cannot not read ca certificate: %w", err)
196
}
197
198
if ok := certPool.AppendCertsFromPEM(ca); !ok {
199
return nil, xerrors.Errorf("failed to append ca certs")
200
}
201
202
options := tlsConfigOptions{}
203
for _, o := range opts {
204
err := o(&options)
205
if err != nil {
206
return nil, err
207
}
208
}
209
210
tlsConfig := &tls.Config{
211
Certificates: []tls.Certificate{tlsCertificate},
212
CipherSuites: []uint16{
213
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
214
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
215
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
216
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
217
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
218
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
219
},
220
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
221
MinVersion: tls.VersionTLS12,
222
MaxVersion: tls.VersionTLS12,
223
NextProtos: []string{"h2"},
224
}
225
226
tlsConfig.ServerName = options.ServerName
227
228
if options.ClientAuth != tls.NoClientCert {
229
log.WithField("clientAuth", options.ClientAuth).Info("enabling client authentication")
230
tlsConfig.ClientAuth = options.ClientAuth
231
}
232
233
if options.ClientCAs {
234
tlsConfig.ClientCAs = certPool
235
}
236
237
if options.RootCAs {
238
tlsConfig.RootCAs = certPool
239
}
240
241
return tlsConfig, nil
242
}
243
244