Skip to content

Commit bd9251e

Browse files
committed
Perform full SSH RBAC checks in VerifiedPublicKeyCallback and only check cert/key in PublicKeyCallback.
Signed-off-by: Chris Thach <chris.thach@goteleport.com>
1 parent a1d3cd8 commit bd9251e

File tree

6 files changed

+92
-20
lines changed

6 files changed

+92
-20
lines changed

lib/srv/authhandlers.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,44 @@ func (h *AuthHandlers) CheckPortForward(addr string, ctx *ServerContext, request
372372
return nil
373373
}
374374

375-
// UserKeyAuth implements SSH client authentication using public keys and is
376-
// called by the server every time the client connects.
377-
func (h *AuthHandlers) UserKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (ppms *ssh.Permissions, rerr error) {
375+
// PublicKeyCallback validates a user key or certificate for SSH public key authentication before key possession has
376+
// been proven.
377+
//
378+
// This method is to be used as the PublicKeyCallback in ssh.ServerConfig and performs a basic check to verify that the
379+
// certificate was signed by a trusted authority and that the certificate metadata is valid. It DOES NOT perform any
380+
// RBAC checks and is used as a preliminary step before the more in-depth VerifiedPublicKeyCallback, which performs RBAC
381+
// checks and is called only after the client proves possession of the key. If the certificate is valid, this callback
382+
// returns an empty ssh.Permissions object. If the certificate is invalid, it returns a non-nil error to reject the auth
383+
// attempt.
384+
func (h *AuthHandlers) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
385+
checker := apisshutils.CertChecker{
386+
CertChecker: ssh.CertChecker{
387+
IsUserAuthority: h.IsUserAuthority,
388+
Clock: h.c.Clock.Now,
389+
},
390+
FIPS: h.c.FIPS,
391+
}
392+
393+
if _, err := checker.Authenticate(conn, key); err != nil {
394+
return nil, trace.Wrap(err)
395+
}
396+
397+
return &ssh.Permissions{}, nil
398+
}
399+
400+
// VerifiedPublicKeyCallback performs full SSH user authentication and authorization after the client proves key
401+
// possession.
402+
//
403+
// This method is to be used as the VerifiedPublicKeyCallback in ssh.ServerConfig and performs RBAC checks to
404+
// determine if the user is authorized to log in. If the user is authorized, this callback returns an ssh.Permissions
405+
// object that includes any relevant access permits encoded in the Extensions field, which can then be used by the rest
406+
// of the connection handling logic to determine what actions the user is authorized to perform in their session.
407+
func (h *AuthHandlers) VerifiedPublicKeyCallback(
408+
conn ssh.ConnMetadata,
409+
key ssh.PublicKey,
410+
_ *ssh.Permissions,
411+
_ string,
412+
) (ppms *ssh.Permissions, rerr error) {
378413
ctx := context.Background()
379414

380415
fingerprint := fmt.Sprintf("%v %v", key.Type(), sshutils.Fingerprint(key))

lib/srv/authhandlers_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func TestRBAC(t *testing.T) {
294294
require.NoError(t, err)
295295

296296
// perform public key authentication
297-
_, err = ah.UserKeyAuth(&mockConnMetadata{}, cert)
297+
_, err = ah.VerifiedPublicKeyCallback(&mockConnMetadata{}, cert, nil, "")
298298
require.NoError(t, err)
299299

300300
tt.loginRBACCheck(t, lc.rbacChecked)
@@ -608,7 +608,7 @@ func TestScopedRBAC(t *testing.T) {
608608
require.NoError(t, err)
609609

610610
// preform public key authentication
611-
_, err = ah.UserKeyAuth(&mockConnMetadata{}, cert)
611+
_, err = ah.VerifiedPublicKeyCallback(&mockConnMetadata{}, cert, nil, "")
612612
if tt.allowed {
613613
require.NoError(t, err)
614614
} else {
@@ -736,11 +736,11 @@ func TestForwardingGitLocalOnly(t *testing.T) {
736736
require.NoError(t, err)
737737

738738
// verify that authentication succeeds for local cert but is rejected categorically for remote
739-
_, err = ah.UserKeyAuth(&mockConnMetadata{}, localCert)
739+
_, err = ah.VerifiedPublicKeyCallback(&mockConnMetadata{}, localCert, nil, "")
740740
require.NoError(t, err)
741741
require.True(t, gc.rbacChecked)
742742

743-
_, err = ah.UserKeyAuth(&mockConnMetadata{}, remoteCert)
743+
_, err = ah.VerifiedPublicKeyCallback(&mockConnMetadata{}, remoteCert, nil, "")
744744
require.Error(t, err)
745745
require.Contains(t, err.Error(), "cross-cluster git forwarding is not supported")
746746
}
@@ -1067,7 +1067,7 @@ func TestAuthAttemptAuditEvent(t *testing.T) {
10671067
require.NoError(t, err)
10681068

10691069
// perform public key authentication, should fail because no login checker set
1070-
_, err = ah.UserKeyAuth(&mockConnMetadata{}, cert)
1070+
_, err = ah.VerifiedPublicKeyCallback(&mockConnMetadata{}, cert, nil, "")
10711071
require.Error(t, err)
10721072

10731073
// audit event (AuthAttempt) should include node's host id and hostname

lib/srv/forward/sshserver.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,9 @@ func (s *Server) Serve() {
579579
config = &ssh.ServerConfig{}
580580
)
581581

582-
// Configure callback for user certificate authentication.
583-
config.PublicKeyCallback = s.authHandlers.UserKeyAuth
582+
// Configure callbacks for user certificate authentication.
583+
config.PublicKeyCallback = s.authHandlers.PublicKeyCallback
584+
config.VerifiedPublicKeyCallback = s.authHandlers.VerifiedPublicKeyCallback
584585

585586
// Set host certificate the in-memory server will present to clients.
586587
config.AddHostKey(s.hostCertificate)

lib/srv/git/forward.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ func (s *ForwardServer) Serve() {
267267
sshutils.NewChanHandlerFunc(s.onChannel),
268268
sshutils.StaticHostSigners(s.cfg.HostCertificate),
269269
sshutils.AuthMethods{
270-
PublicKey: s.userKeyAuth,
270+
PublicKey: s.publicKeyCallback,
271+
VerifiedPublicKey: s.verifiedPublicKeyCallback,
271272
},
272273
sshutils.SetFIPS(s.cfg.FIPS),
273274
sshutils.SetCiphers(s.cfg.Ciphers),
@@ -295,7 +296,7 @@ func (s *ForwardServer) close() {
295296
}
296297
}
297298

298-
func (s *ForwardServer) userKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
299+
func checkAndSetGitUser(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.ConnMetadata, error) {
299300
cert, ok := key.(*ssh.Certificate)
300301
if !ok {
301302
return nil, trace.BadParameter("unsupported key type")
@@ -319,9 +320,31 @@ func (s *ForwardServer) userKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*
319320
conn = sshutils.NewSSHConnMetadataWithUser(conn, ident.Principals[0])
320321
}
321322

322-
// Use auth.UserKeyAuth to verify user cert is signed by UserCA and to evaluate
323-
// RBAC permissions.
324-
permissions, err := s.auth.UserKeyAuth(conn, key)
323+
return conn, nil
324+
}
325+
326+
func (s *ForwardServer) publicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
327+
conn, err := checkAndSetGitUser(conn, key)
328+
if err != nil {
329+
return nil, trace.Wrap(err)
330+
}
331+
332+
permissions, err := s.auth.PublicKeyCallback(conn, key)
333+
if err != nil {
334+
userKeyAuthFailureCounter.Inc()
335+
return nil, trace.Wrap(err)
336+
}
337+
338+
return permissions, nil
339+
}
340+
341+
func (s *ForwardServer) verifiedPublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey, _ *ssh.Permissions, _ string) (*ssh.Permissions, error) {
342+
conn, err := checkAndSetGitUser(conn, key)
343+
if err != nil {
344+
return nil, trace.Wrap(err)
345+
}
346+
347+
permissions, err := s.auth.VerifiedPublicKeyCallback(conn, key, nil, "")
325348
if err != nil {
326349
userKeyAuthFailureCounter.Inc()
327350
return nil, trace.Wrap(err)

lib/srv/regular/sshserver.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,10 @@ func New(
956956
component,
957957
addr, s,
958958
getHostSigners,
959-
sshutils.AuthMethods{PublicKey: s.authHandlers.UserKeyAuth},
959+
sshutils.AuthMethods{
960+
PublicKey: s.authHandlers.PublicKeyCallback,
961+
VerifiedPublicKey: s.authHandlers.VerifiedPublicKeyCallback,
962+
},
960963
sshutils.SetLimiter(s.limiter),
961964
sshutils.SetRequestHandler(s),
962965
sshutils.SetNewConnHandler(s),

lib/sshutils/server.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ func NewServer(
237237
}
238238

239239
s.cfg.PublicKeyCallback = ah.PublicKey
240+
s.cfg.VerifiedPublicKeyCallback = ah.VerifiedPublicKey
240241
s.cfg.PasswordCallback = ah.Password
241242
s.cfg.NoClientAuth = ah.NoClient
242243

@@ -723,9 +724,10 @@ func (f NewConnHandlerFunc) HandleNewConn(ctx context.Context, ccx *ConnectionCo
723724
}
724725

725726
type AuthMethods struct {
726-
PublicKey PublicKeyFunc
727-
Password PasswordFunc
728-
NoClient bool
727+
PublicKey PublicKeyFunc
728+
VerifiedPublicKey VerifiedPublicKeyFunc
729+
Password PasswordFunc
730+
NoClient bool
729731
}
730732

731733
// GetHostSignersFunc is an infallible function that returns host signers for
@@ -802,7 +804,15 @@ func validateHostSigner(fips bool, signer ssh.Signer) error {
802804

803805
type (
804806
PublicKeyFunc func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
805-
PasswordFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
807+
808+
VerifiedPublicKeyFunc func(
809+
conn ssh.ConnMetadata,
810+
key ssh.PublicKey,
811+
permissions *ssh.Permissions,
812+
signatureAlgorithm string,
813+
) (*ssh.Permissions, error)
814+
815+
PasswordFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
806816
)
807817

808818
// ClusterDetails specifies information about a cluster

0 commit comments

Comments
 (0)