-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
tls.go
364 lines (323 loc) · 11.3 KB
/
tls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package rpc
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/errors"
)
type lazyHTTPClient struct {
sync.Once
httpClient http.Client
err error
}
type lazyCertificateManager struct {
sync.Once
cm *security.CertificateManager
err error
}
func wrapError(err error) error {
if !errors.HasType(err, (*security.Error)(nil)) {
return &security.Error{
Message: "problem using security settings",
Err: err,
}
}
return err
}
// SecurityContext is a wrapper providing transport security helpers such as
// the certificate manager.
type SecurityContext struct {
security.CertsLocator
config *base.Config
tenID roachpb.TenantID
lazy struct {
// The certificate manager. Must be accessed through GetCertificateManager.
certificateManager lazyCertificateManager
// httpClient uses the client TLS config. It is initialized lazily.
httpClient lazyHTTPClient
}
}
// MakeSecurityContext makes a SecurityContext.
//
// TODO(tbg): don't take a whole Config. This can be trimmed down significantly.
func MakeSecurityContext(cfg *base.Config, tenID roachpb.TenantID) SecurityContext {
return SecurityContext{
CertsLocator: security.MakeCertsLocator(cfg.SSLCertsDir),
config: cfg,
tenID: tenID,
}
}
// GetCertificateManager returns the certificate manager, initializing it
// on the first call. If certificates should be used but none are found,
// fails eagerly.
func (ctx *SecurityContext) GetCertificateManager() (*security.CertificateManager, error) {
ctx.lazy.certificateManager.Do(func() {
var opts []security.Option
if !roachpb.IsSystemTenantID(ctx.tenID.ToUint64()) {
opts = append(opts, security.ForTenant(ctx.tenID.String()))
}
ctx.lazy.certificateManager.cm, ctx.lazy.certificateManager.err =
security.NewCertificateManager(ctx.config.SSLCertsDir, opts...)
if ctx.lazy.certificateManager.err == nil && !ctx.config.Insecure {
infos, err := ctx.lazy.certificateManager.cm.ListCertificates()
if err != nil {
ctx.lazy.certificateManager.err = err
} else if len(infos) == 0 {
// If we know there should be certificates (we're in secure mode)
// but there aren't any, this likely indicates that the certs dir
// was misconfigured.
ctx.lazy.certificateManager.err = errors.New("no certificates found; does certs dir exist?")
}
}
})
return ctx.lazy.certificateManager.cm, ctx.lazy.certificateManager.err
}
// GetServerTLSConfig returns the server TLS config, initializing it if needed.
// If Insecure is true, return a nil config, otherwise ask the certificate
// manager for a server TLS config.
func (ctx *SecurityContext) GetServerTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetServerTLSConfig()
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// GetTenantServerTLSConfig returns the tenant server TLS config, initializing
// it if needed. If Insecure is true, return a nil config, otherwise asks the
// certificate manager for the tenant server TLS config.
func (ctx *SecurityContext) GetTenantServerTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetTenantServerTLSConfig()
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// GetClientTLSConfig returns the client TLS config, initializing it if needed.
// If Insecure is true, return a nil config, otherwise ask the certificate
// manager for a TLS config using certs for the config.User.
// This TLSConfig might **NOT** be suitable to talk to the Admin UI, use GetUIClientTLSConfig instead.
func (ctx *SecurityContext) GetClientTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetClientTLSConfig(ctx.config.User)
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// GetTenantClientTLSConfig returns the client TLS config for the tenant, provided
// the SecurityContext operates on behalf of a secondary tenant (i.e. not the
// system tenant).
//
// If Insecure is true, return a nil config, otherwise retrieves the client
// certificate for the configured tenant from the cert manager.
func (ctx *SecurityContext) GetTenantClientTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetTenantClientTLSConfig()
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// getUIClientTLSConfig returns the client TLS config for Admin UI clients, initializing it if needed.
// If Insecure is true, return a nil config, otherwise ask the certificate
// manager for a TLS config configured to talk to the Admin UI.
// This TLSConfig is **NOT** suitable to talk to the GRPC or SQL servers, use GetClientTLSConfig instead.
func (ctx *SecurityContext) getUIClientTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetUIClientTLSConfig()
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// GetUIServerTLSConfig returns the server TLS config for the Admin UI, initializing it if needed.
// If Insecure is true, return a nil config, otherwise ask the certificate
// manager for a server UI TLS config.
//
// TODO(peter): This method is only used by `server.NewServer` and
// `Server.Start`. Move it.
func (ctx *SecurityContext) GetUIServerTLSConfig() (*tls.Config, error) {
// Early out.
if ctx.config.Insecure || ctx.config.DisableTLSForHTTP {
return nil, nil
}
cm, err := ctx.GetCertificateManager()
if err != nil {
return nil, wrapError(err)
}
tlsCfg, err := cm.GetUIServerTLSConfig()
if err != nil {
return nil, wrapError(err)
}
return tlsCfg, nil
}
// GetHTTPClient returns the http client, initializing it
// if needed. It uses the client TLS config.
func (ctx *SecurityContext) GetHTTPClient() (http.Client, error) {
ctx.lazy.httpClient.Do(func() {
ctx.lazy.httpClient.httpClient.Timeout = 10 * time.Second
var transport http.Transport
ctx.lazy.httpClient.httpClient.Transport = &transport
transport.TLSClientConfig, ctx.lazy.httpClient.err = ctx.getUIClientTLSConfig()
})
return ctx.lazy.httpClient.httpClient, ctx.lazy.httpClient.err
}
// getClientCertPaths returns the paths to the client cert and key. This uses
// the node certs for the NodeUser, and the actual client certs for all others.
func (ctx *SecurityContext) getClientCertPaths(user string) (string, string) {
if user == security.NodeUser {
return ctx.NodeCertPath(), ctx.NodeKeyPath()
}
return ctx.ClientCertPath(user), ctx.ClientKeyPath(user)
}
// CheckCertificateAddrs validates the addresses inside the configured
// certificates to be compatible with the configured listen and
// advertise addresses. This is an advisory function (to inform/educate
// the user) and not a requirement for security.
// This must also be called after ValidateAddrs() and after
// the certificate manager was initialized.
func (ctx *SecurityContext) CheckCertificateAddrs(cctx context.Context) {
if ctx.config.Insecure {
return
}
// By now the certificate manager must be initialized.
cm, _ := ctx.GetCertificateManager()
// Verify that the listen and advertise addresses are compatible
// with the provided certificate.
certInfo := cm.NodeCert()
if certInfo.Error != nil {
log.Shoutf(cctx, log.Severity_ERROR,
"invalid node certificate: %v", certInfo.Error)
} else {
cert := certInfo.ParsedCertificates[0]
addrInfo := certAddrs(cert)
// Log the certificate details in any case. This will aid during troubleshooting.
log.Infof(cctx, "server certificate addresses: %s", addrInfo)
var msg bytes.Buffer
// Verify the compatibility. This requires that ValidateAddrs() has
// been called already.
host, _, err := net.SplitHostPort(ctx.config.AdvertiseAddr)
if err != nil {
panic("programming error: call ValidateAddrs() first")
}
if err := cert.VerifyHostname(host); err != nil {
fmt.Fprintf(&msg, "advertise address %q not in node certificate (%s)\n", host, addrInfo)
}
host, _, err = net.SplitHostPort(ctx.config.SQLAdvertiseAddr)
if err != nil {
panic("programming error: call ValidateAddrs() first")
}
if err := cert.VerifyHostname(host); err != nil {
fmt.Fprintf(&msg, "advertise SQL address %q not in node certificate (%s)\n", host, addrInfo)
}
if msg.Len() > 0 {
log.Shoutf(cctx, log.Severity_WARNING,
"%s"+
"Secure client connections are likely to fail.\n"+
"Consider extending the node certificate or tweak --listen-addr/--advertise-addr/--sql-addr/--advertise-sql-addr.",
msg.String())
}
}
// TODO(tbg): Verify that the tenant listen and advertise addresses are
// compatible with the provided certificate.
// Verify that the http listen and advertise addresses are
// compatible with the provided certificate.
certInfo = cm.UICert()
if certInfo == nil {
// A nil UI cert means use the node cert instead;
// see details in (*CertificateManager) getEmbeddedUIServerTLSConfig()
// and (*CertificateManager) getUICertLocked().
certInfo = cm.NodeCert()
}
if certInfo.Error != nil {
log.Shoutf(cctx, log.Severity_ERROR,
"invalid UI certificate: %v", certInfo.Error)
} else {
cert := certInfo.ParsedCertificates[0]
addrInfo := certAddrs(cert)
// Log the certificate details in any case. This will aid during
// troubleshooting.
log.Infof(cctx, "web UI certificate addresses: %s", addrInfo)
}
}
// HTTPRequestScheme returns "http" or "https" based on the value of
// Insecure and DisableTLSForHTTP.
func (ctx *SecurityContext) HTTPRequestScheme() string {
return ctx.config.HTTPRequestScheme()
}
// certAddrs formats the list of addresses included in a certificate for
// printing in an error message.
func certAddrs(cert *x509.Certificate) string {
// If an IP address was specified as listen/adv address, the
// hostname validation will only use the IPAddresses field. So this
// needs to be printed in all cases.
addrs := make([]string, len(cert.IPAddresses))
for i, ip := range cert.IPAddresses {
addrs[i] = ip.String()
}
// For names, the hostname validation will use DNSNames if
// the Subject Alt Name is present in the cert, otherwise
// it will use the common name. We can't parse the
// extensions here so we print both.
return fmt.Sprintf("IP=%s; DNS=%s; CN=%s",
strings.Join(addrs, ","),
strings.Join(cert.DNSNames, ","),
cert.Subject.CommonName)
}