forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
/
register.go
365 lines (320 loc) · 12.3 KB
/
register.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
365
/*
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"crypto/x509"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
)
// LocalRegister is used to generate host keys when a node or proxy is running
// within the same process as the Auth Server and as such, does not need to
// use provisioning tokens.
func LocalRegister(id IdentityID, authServer *AuthServer, additionalPrincipals, dnsNames []string, remoteAddr string) (*Identity, error) {
// If local registration is happening and no remote address was passed in
// (which means no advertise IP was set), use localhost.
if remoteAddr == "" {
remoteAddr = defaults.Localhost
}
keys, err := authServer.GenerateServerKeys(GenerateServerKeysRequest{
HostID: id.HostUUID,
NodeName: id.NodeName,
Roles: teleport.Roles{id.Role},
AdditionalPrincipals: additionalPrincipals,
RemoteAddr: remoteAddr,
DNSNames: dnsNames,
NoCache: true,
})
if err != nil {
return nil, trace.Wrap(err)
}
identity, err := ReadIdentityFromKeyPair(keys)
if err != nil {
return nil, trace.Wrap(err)
}
return identity, nil
}
// RegisterParams specifies parameters
// for first time register operation with auth server
type RegisterParams struct {
// DataDir is the data directory
// storing CA certificate
DataDir string
// Token is a secure token to join the cluster
Token string
// ID is identity ID
ID IdentityID
// Servers is a list of auth servers to dial
Servers []utils.NetAddr
// AdditionalPrincipals is a list of additional principals to dial
AdditionalPrincipals []string
// DNSNames is a list of DNS names to add to x509 certificate
DNSNames []string
// PrivateKey is a PEM encoded private key (not passed to auth servers)
PrivateKey []byte
// PublicTLSKey is a server's public key to sign
PublicTLSKey []byte
// PublicSSHKey is a server's public SSH key to sign
PublicSSHKey []byte
// CipherSuites is a list of cipher suites to use for TLS client connection
CipherSuites []uint16
// CAPin is the SKPI hash of the CA used to verify the Auth Server.
CAPin string
// CAPath is the path to the CA file.
CAPath string
// GetHostCredentials is a client that can fetch host credentials.
GetHostCredentials HostCredentials
}
// CredGetter is an interface for a client that can be used to get host
// credentials. This interface is needed because lib/client can not be imported
// in lib/auth due to circular imports.
type HostCredentials func(context.Context, string, bool, RegisterUsingTokenRequest) (*PackedKeys, error)
// Register is used to generate host keys when a node or proxy are running on
// different hosts than the auth server. This method requires provisioning
// tokens to prove a valid auth server was used to issue the joining request
// as well as a method for the node to validate the auth server.
func Register(params RegisterParams) (*Identity, error) {
// Read in the token. The token can either be passed in or come from a file
// on disk.
token, err := utils.ReadToken(params.Token)
if err != nil {
return nil, trace.Wrap(err)
}
// Attempt to register through the auth server, if it fails, try and
// register through the proxy server.
ident, err := registerThroughAuth(token, params)
if err != nil {
// If no params client was set this is a proxy and fail right away.
if params.GetHostCredentials == nil {
log.Debugf("Missing client, failing with error from Auth Server: %v.", err)
return nil, trace.Wrap(err)
}
ident, err = registerThroughProxy(token, params)
if err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("Successfully registered through proxy server.")
return ident, nil
}
log.Debugf("Successfully registered through auth server.")
return ident, nil
}
// registerThroughProxy is used to register through the proxy server.
func registerThroughProxy(token string, params RegisterParams) (*Identity, error) {
log.Debugf("Attempting to register through proxy server.")
if len(params.Servers) == 0 {
return nil, trace.BadParameter("no auth servers set")
}
keys, err := params.GetHostCredentials(context.Background(),
params.Servers[0].String(),
lib.IsInsecureDevMode(),
RegisterUsingTokenRequest{
Token: token,
HostID: params.ID.HostUUID,
NodeName: params.ID.NodeName,
Role: params.ID.Role,
AdditionalPrincipals: params.AdditionalPrincipals,
DNSNames: params.DNSNames,
PublicTLSKey: params.PublicTLSKey,
PublicSSHKey: params.PublicSSHKey,
})
if err != nil {
return nil, trace.Unwrap(err)
}
keys.Key = params.PrivateKey
return ReadIdentityFromKeyPair(keys)
}
// registerThroughAuth is used to register through the auth server.
func registerThroughAuth(token string, params RegisterParams) (*Identity, error) {
log.Debugf("Attempting to register through auth server.")
var client *Client
var err error
// Build a client to the Auth Server. If a CA pin is specified require the
// Auth Server is validated. Otherwise attempt to use the CA file on disk
// but if it's not available connect without validating the Auth Server CA.
switch {
case params.CAPin != "":
client, err = pinRegisterClient(params)
default:
client, err = insecureRegisterClient(params)
}
if err != nil {
return nil, trace.Wrap(err)
}
defer client.Close()
// Get the SSH and X509 certificates for a node.
keys, err := client.RegisterUsingToken(RegisterUsingTokenRequest{
Token: token,
HostID: params.ID.HostUUID,
NodeName: params.ID.NodeName,
Role: params.ID.Role,
AdditionalPrincipals: params.AdditionalPrincipals,
DNSNames: params.DNSNames,
PublicTLSKey: params.PublicTLSKey,
PublicSSHKey: params.PublicSSHKey,
})
if err != nil {
return nil, trace.Wrap(err)
}
keys.Key = params.PrivateKey
return ReadIdentityFromKeyPair(keys)
}
// insecureRegisterClient attempts to connects to the Auth Server using the
// CA on disk. If no CA is found on disk, Teleport will not verify the Auth
// Server it is connecting to.
func insecureRegisterClient(params RegisterParams) (*Client, error) {
tlsConfig := utils.TLSConfig(params.CipherSuites)
cert, err := readCA(params)
if err != nil && !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
// If no CA was found, then create a insecure connection to the Auth Server,
// otherwise use the CA on disk to validate the Auth Server.
if trace.IsNotFound(err) {
tlsConfig.InsecureSkipVerify = true
log.Warnf("Joining cluster without validating the identity of the Auth " +
"Server. This may open you up to a Man-In-The-Middle (MITM) attack if an " +
"attacker can gain privileged network access. To remedy this, use the CA pin " +
"value provided when join token was generated to validate the identity of " +
"the Auth Server.")
} else {
certPool := x509.NewCertPool()
certPool.AddCert(cert)
tlsConfig.RootCAs = certPool
log.Infof("Joining remote cluster %v, validating connection with certificate on disk.", cert.Subject.CommonName)
}
client, err := NewTLSClient(ClientConfig{Addrs: params.Servers, TLS: tlsConfig})
if err != nil {
return nil, trace.Wrap(err)
}
return client, nil
}
// readCA will read in CA that will be used to validate the certificate that
// the Auth Server presents.
func readCA(params RegisterParams) (*x509.Certificate, error) {
certBytes, err := utils.ReadPath(params.CAPath)
if err != nil {
return nil, trace.Wrap(err)
}
cert, err := tlsca.ParseCertificatePEM(certBytes)
if err != nil {
return nil, trace.Wrap(err, "failed to parse certificate at %v", params.CAPath)
}
return cert, nil
}
// pinRegisterClient first connects to the Auth Server using a insecure
// connection to fetch the root CA. If the root CA matches the provided CA
// pin, a connection will be re-established and the root CA will be used to
// validate the certificate presented. If both conditions hold true, then we
// know we are connecting to the expected Auth Server.
func pinRegisterClient(params RegisterParams) (*Client, error) {
// Build a insecure client to the Auth Server. This is safe because even if
// an attacker were to MITM this connection the CA pin will not match below.
tlsConfig := utils.TLSConfig(params.CipherSuites)
tlsConfig.InsecureSkipVerify = true
client, err := NewTLSClient(ClientConfig{Addrs: params.Servers, TLS: tlsConfig})
if err != nil {
return nil, trace.Wrap(err)
}
defer client.Close()
// Fetch the root CA from the Auth Server. The NOP role has access to the
// GetClusterCACert endpoint.
localCA, err := client.GetClusterCACert()
if err != nil {
return nil, trace.Wrap(err)
}
tlsCA, err := tlsca.ParseCertificatePEM(localCA.TLSCA)
if err != nil {
return nil, trace.Wrap(err)
}
// Check that the SPKI pin matches the CA we fetched over a insecure
// connection. This makes sure the CA fetched over a insecure connection is
// in-fact the expected CA.
err = utils.CheckSPKI(params.CAPin, tlsCA)
if err != nil {
return nil, trace.Wrap(err)
}
log.Infof("Joining remote cluster %v with CA pin.", tlsCA.Subject.CommonName)
// Create another client, but this time with the CA provided to validate
// that the Auth Server was issued a certificate by the same CA.
tlsConfig = utils.TLSConfig(params.CipherSuites)
certPool := x509.NewCertPool()
certPool.AddCert(tlsCA)
tlsConfig.RootCAs = certPool
client, err = NewTLSClient(ClientConfig{Addrs: params.Servers, TLS: tlsConfig})
if err != nil {
return nil, trace.Wrap(err)
}
return client, nil
}
// ReRegisterParams specifies parameters for re-registering
// in the cluster (rotating certificates for existing members)
type ReRegisterParams struct {
// Client is an authenticated client using old credentials
Client ClientI
// ID is identity ID
ID IdentityID
// AdditionalPrincipals is a list of additional principals to dial
AdditionalPrincipals []string
// DNSNames is a list of DNS Names to add to the x509 client certificate
DNSNames []string
// PrivateKey is a PEM encoded private key (not passed to auth servers)
PrivateKey []byte
// PublicTLSKey is a server's public key to sign
PublicTLSKey []byte
// PublicSSHKey is a server's public SSH key to sign
PublicSSHKey []byte
// Rotation is the rotation state of the certificate authority
Rotation services.Rotation
}
// ReRegister renews the certificates and private keys based on the client's existing identity.
func ReRegister(params ReRegisterParams) (*Identity, error) {
hostID, err := params.ID.HostID()
if err != nil {
return nil, trace.Wrap(err)
}
keys, err := params.Client.GenerateServerKeys(GenerateServerKeysRequest{
HostID: hostID,
NodeName: params.ID.NodeName,
Roles: teleport.Roles{params.ID.Role},
AdditionalPrincipals: params.AdditionalPrincipals,
DNSNames: params.DNSNames,
PublicTLSKey: params.PublicTLSKey,
PublicSSHKey: params.PublicSSHKey,
Rotation: ¶ms.Rotation,
})
if err != nil {
return nil, trace.Wrap(err)
}
keys.Key = params.PrivateKey
return ReadIdentityFromKeyPair(keys)
}
// PackedKeys is a collection of private key, SSH host certificate
// and TLS certificate and certificate authority issued the certificate
type PackedKeys struct {
// Key is a private key
Key []byte `json:"key"`
// Cert is an SSH host cert
Cert []byte `json:"cert"`
// TLSCert is an X509 certificate
TLSCert []byte `json:"tls_cert"`
// TLSCACerts is a list of TLS certificate authorities.
TLSCACerts [][]byte `json:"tls_ca_certs"`
// SSHCACerts is a list of SSH certificate authorities.
SSHCACerts [][]byte `json:"ssh_ca_certs"`
}