forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
/
authhandlers.go
443 lines (376 loc) · 14.1 KB
/
authhandlers.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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
/*
Copyright 2017 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 srv
import (
"fmt"
"net"
"os"
"os/user"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
)
// AuthHandlers are common authorization and authentication related handlers
// used by the regular and forwarding server.
type AuthHandlers struct {
*log.Entry
// Server is the services.Server in the backend.
Server services.Server
// Component is the type of SSH server (node, proxy, or recording proxy).
Component string
// AuditLog is the service used to access Audit Log.
AuditLog events.IAuditLog
// AccessPoint is used to access the Auth Server.
AccessPoint auth.AccessPoint
}
// BuildIdentityContext returns an IdentityContext populated with information
// about the logged in user on the connection.
func (h *AuthHandlers) CreateIdentityContext(sconn *ssh.ServerConn) (IdentityContext, error) {
identity := IdentityContext{
TeleportUser: sconn.Permissions.Extensions[utils.CertTeleportUser],
Certificate: []byte(sconn.Permissions.Extensions[utils.CertTeleportUserCertificate]),
Login: sconn.User(),
}
clusterName, err := h.AccessPoint.GetDomainName()
if err != nil {
return IdentityContext{}, trace.Wrap(err)
}
certificate, err := identity.GetCertificate()
if err != nil {
return IdentityContext{}, trace.Wrap(err)
}
certAuthority, err := h.authorityForCert(services.UserCA, certificate.SignatureKey)
if err != nil {
return IdentityContext{}, trace.Wrap(err)
}
identity.CertAuthority = certAuthority
roleSet, err := h.fetchRoleSet(certificate, certAuthority, identity.TeleportUser, clusterName)
if err != nil {
return IdentityContext{}, trace.Wrap(err)
}
identity.RoleSet = roleSet
return identity, nil
}
// CheckAgentForward checks if agent forwarding is allowed for the users RoleSet.
func (h *AuthHandlers) CheckAgentForward(ctx *ServerContext) error {
if err := ctx.Identity.RoleSet.CheckAgentForward(ctx.Identity.Login); err != nil {
return trace.Wrap(err)
}
return nil
}
// CheckPortForward checks if port forwarding is allowed for the users RoleSet.
func (h *AuthHandlers) CheckPortForward(addr string, ctx *ServerContext) error {
if ok := ctx.Identity.RoleSet.CanPortForward(); !ok {
systemErrorMessage := fmt.Sprintf("port forwarding not allowed by role set: %v", ctx.Identity.RoleSet)
userErrorMessage := "port forwarding not allowed"
// emit port forward failure event
h.AuditLog.EmitAuditEvent(events.PortForwardEvent, events.EventFields{
events.PortForwardAddr: addr,
events.PortForwardSuccess: false,
events.PortForwardErr: systemErrorMessage,
events.EventLogin: ctx.Identity.Login,
events.LocalAddr: ctx.Conn.LocalAddr().String(),
events.RemoteAddr: ctx.Conn.RemoteAddr().String(),
})
h.Warnf("Port forwarding request denied: %v.", systemErrorMessage)
return trace.AccessDenied(userErrorMessage)
}
return nil
}
// UserKeyAuth implements SSH client authentication using public keys and is
// called by the server every time the client connects.
func (h *AuthHandlers) UserKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
fingerprint := fmt.Sprintf("%v %v", key.Type(), sshutils.Fingerprint(key))
// as soon as key auth starts, we know something about the connection, so
// update *log.Entry.
h.Entry = log.WithFields(log.Fields{
trace.Component: h.Component,
trace.ComponentFields: log.Fields{
"local": conn.LocalAddr(),
"remote": conn.RemoteAddr(),
"user": conn.User(),
"fingerprint": fingerprint,
},
})
cid := fmt.Sprintf("conn(%v->%v, user=%v)", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
h.Debugf("%v auth attempt", cid)
cert, ok := key.(*ssh.Certificate)
h.Debugf("%v auth attempt with key %v, %#v", cid, fingerprint, cert)
if !ok {
h.Debugf("auth attempt, unsupported key type")
return nil, trace.BadParameter("unsupported key type: %v", fingerprint)
}
if len(cert.ValidPrincipals) == 0 {
h.Debugf("need a valid principal for key")
return nil, trace.BadParameter("need a valid principal for key %v", fingerprint)
}
if len(cert.KeyId) == 0 {
h.Debugf("need a valid key ID for key")
return nil, trace.BadParameter("need a valid key for key %v", fingerprint)
}
teleportUser := cert.KeyId
// only failed attempts are logged right now
recordFailedLogin := func(err error) {
fields := events.EventFields{
events.EventUser: teleportUser,
events.AuthAttemptSuccess: false,
events.AuthAttemptErr: err.Error(),
}
h.Warnf("failed login attempt %#v", fields)
h.AuditLog.EmitAuditEvent(events.AuthAttemptEvent, fields)
}
certChecker := ssh.CertChecker{IsAuthority: h.IsUserAuthority}
permissions, err := certChecker.Authenticate(conn, key)
if err != nil {
recordFailedLogin(err)
return nil, trace.Wrap(err)
}
if err := certChecker.CheckCert(conn.User(), cert); err != nil {
recordFailedLogin(err)
return nil, trace.Wrap(err)
}
h.Debugf("Successfully authenticated")
// see if the host user is valid, we only do this when logging into a teleport node
if h.isTeleportNode() {
_, err = user.Lookup(conn.User())
if err != nil {
host, _ := os.Hostname()
h.Warnf("host '%s' does not have OS user '%s'", host, conn.User())
h.Errorf("no such user")
return nil, trace.AccessDenied("no such user: '%s'", conn.User())
}
}
clusterName, err := h.AccessPoint.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}
// this is the only way we know of to pass valid additional data about the
// connection to the handlers
permissions.Extensions[utils.CertTeleportUser] = teleportUser
permissions.Extensions[utils.CertTeleportClusterName] = clusterName
permissions.Extensions[utils.CertTeleportUserCertificate] = string(ssh.MarshalAuthorizedKey(cert))
if h.isProxy() {
return permissions, nil
}
// check if the user has permission to log into the node.
switch {
case h.Component == teleport.ComponentForwardingNode:
err = h.canLoginWithoutRBAC(cert, clusterName, teleportUser, conn.User())
default:
err = h.canLoginWithRBAC(cert, clusterName, teleportUser, conn.User())
}
if err != nil {
h.Errorf("Permission denied: %v", err)
recordFailedLogin(err)
return nil, trace.Wrap(err)
}
return permissions, nil
}
// HostKeyAuth implements host key verification and is called by the client
// to validate the certificate presented by the target server. If the target
// server presents a SSH certificate, we validate that it was Teleport that
// generated the certificate. If the target server presents a public key, if
// we are strictly checking keys, we reject the target server. If we are not
// we take whatever.
func (h *AuthHandlers) HostKeyAuth(hostport string, remote net.Addr, key ssh.PublicKey) error {
fingerprint := fmt.Sprintf("%v %v", key.Type(), sshutils.Fingerprint(key))
// update entry to include a fingerprint of the key so admins can track down
// the key causing problems
h.Entry = log.WithFields(log.Fields{
trace.Component: h.Component,
trace.ComponentFields: log.Fields{
"remote": remote.String(),
"fingerprint": fingerprint,
},
})
clusterConfig, err := h.AccessPoint.GetClusterConfig()
if err != nil {
return trace.Wrap(err)
}
cert, ok := key.(*ssh.Certificate)
if ok {
err := h.IsHostAuthority(hostport, remote, cert)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// if we are strictly checking host keys then reject this request right away
if clusterConfig.GetProxyChecksHostKeys() == services.HostKeyCheckYes {
return trace.AccessDenied("remote host presented a public key, expected a host certificate")
}
// if we are not stricting rejecting host keys, we need to log that we
// trusted a insecure key and then return nil
h.Warn("Insecure configuration! Strict host key checking disabled, allowing login without checking host key.")
return nil
}
// IsUserAuthority is called during checking the client key, to see if the
// key used to sign the certificate was a Teleport CA.
func (h *AuthHandlers) IsUserAuthority(cert ssh.PublicKey) bool {
if _, err := h.authorityForCert(services.UserCA, cert); err != nil {
return false
}
return true
}
// IsHostAuthority is called when checking the host certificate a server
// presents. It make sure that the key used to sign the host certificate was a
// Teleport CA.
func (h *AuthHandlers) IsHostAuthority(hostport string, remote net.Addr, cert ssh.PublicKey) error {
if _, err := h.authorityForCert(services.HostCA, cert); err != nil {
return trace.Wrap(err)
}
return nil
}
// canLoginWithoutRBAC checks the given certificate (supplied by a connected
// client) to see if this certificate can be allowed to login as user:login
// pair to requested server.
func (h *AuthHandlers) canLoginWithoutRBAC(cert *ssh.Certificate, clusterName string, teleportUser, osUser string) error {
h.Debugf("Checking permissions for (%v,%v) to login to node without RBAC checks.", teleportUser, osUser)
// check if the ca that signed the certificate is known to the cluster
_, err := h.authorityForCert(services.UserCA, cert.SignatureKey)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// canLoginWithRBAC checks the given certificate (supplied by a connected
// client) to see if this certificate can be allowed to login as user:login
// pair to requested server and if RBAC rules allow login.
func (h *AuthHandlers) canLoginWithRBAC(cert *ssh.Certificate, clusterName string, teleportUser, osUser string) error {
h.Debugf("Checking permissions for (%v,%v) to login to node with RBAC checks.", teleportUser, osUser)
// get the ca that signd the users certificate
ca, err := h.authorityForCert(services.UserCA, cert.SignatureKey)
if err != nil {
return trace.Wrap(err)
}
// get roles assigned to this user
roles, err := h.fetchRoleSet(cert, ca, teleportUser, clusterName)
if err != nil {
return trace.Wrap(err)
}
// check if roles allow access to server
if err := roles.CheckAccessToServer(osUser, h.Server); err != nil {
return trace.AccessDenied("user %s@%s is not authorized to login as %v@%s: %v",
teleportUser, ca.GetClusterName(), osUser, clusterName, err)
}
return nil
}
// fetchRoleSet fetches the services.RoleSet assigned to a Teleport user.
func (h *AuthHandlers) fetchRoleSet(cert *ssh.Certificate, ca services.CertAuthority, teleportUser string, clusterName string) (services.RoleSet, error) {
// for local users, go and check their individual permissions
var roles services.RoleSet
if clusterName == ca.GetClusterName() {
users, err := h.AccessPoint.GetUsers()
if err != nil {
return nil, trace.Wrap(err)
}
for _, u := range users {
if u.GetName() == teleportUser {
// pass along the traits so we get the substituted roles for this user
roles, err = services.FetchRoles(u.GetRoles(), h.AccessPoint, u.GetTraits())
if err != nil {
return nil, trace.Wrap(err)
}
}
}
} else {
certRoles, err := extractRolesFromCert(cert)
if err != nil {
return nil, trace.AccessDenied("failed to parse certificate roles")
}
roleNames, err := ca.CombinedMapping().Map(certRoles)
if err != nil {
return nil, trace.AccessDenied("failed to map roles")
}
// pass the principals on the certificate along as the login traits
// to the remote cluster.
traits := map[string][]string{
teleport.TraitLogins: cert.ValidPrincipals,
}
roles, err = services.FetchRoles(roleNames, h.AccessPoint, traits)
if err != nil {
return nil, trace.Wrap(err)
}
}
return roles, nil
}
// authorityForCert checks if the certificate was signed by a Teleport
// Certificate Authority and returns it.
func (h *AuthHandlers) authorityForCert(caType services.CertAuthType, key ssh.PublicKey) (services.CertAuthority, error) {
// get all certificate authorities for given type
cas, err := h.AccessPoint.GetCertAuthorities(caType, false)
if err != nil {
h.Warnf("%v", trace.DebugReport(err))
return nil, trace.Wrap(err)
}
// find the one that signed our certificate
var ca services.CertAuthority
for i := range cas {
checkers, err := cas[i].Checkers()
if err != nil {
h.Warnf("%v", err)
return nil, trace.Wrap(err)
}
for _, checker := range checkers {
// if we have a certificate, compare the certificate signing key against
// the ca key. otherwise check the public key that was passed in. this is
// due to the differences in how this function is called by the user and
// host checkers.
switch v := key.(type) {
case *ssh.Certificate:
if sshutils.KeysEqual(v.SignatureKey, checker) {
ca = cas[i]
break
}
default:
if sshutils.KeysEqual(key, checker) {
ca = cas[i]
break
}
}
}
}
// the certificate was signed by unknown authority
if ca == nil {
return nil, trace.AccessDenied("the certificate signed by untrusted CA")
}
return ca, nil
}
// isProxy returns true if it's a regular SSH proxy.
func (h *AuthHandlers) isProxy() bool {
if h.Component == teleport.ComponentProxy {
return true
}
return false
}
func (h *AuthHandlers) isTeleportNode() bool {
if h.Component == teleport.ComponentNode {
return true
}
return false
}
// extractRolesFromCert extracts roles from certificate metadata extensions.
func extractRolesFromCert(cert *ssh.Certificate) ([]string, error) {
data, ok := cert.Extensions[teleport.CertExtensionTeleportRoles]
if !ok {
// it's ok to not have any roles in the metadata
return nil, nil
}
return services.UnmarshalCertRoles(data)
}