Skip to content

Commit

Permalink
[v9] Add hint for --user flag in tsh login (#14254)
Browse files Browse the repository at this point in the history
This change adds a hint to tsh login about the use of the --user flag.
  • Loading branch information
atburke authored Jul 11, 2022
1 parent 6105a33 commit f7edeca
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 7 deletions.
38 changes: 31 additions & 7 deletions lib/auth/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package auth

import (
"context"
"errors"
"time"

"github.com/gravitational/teleport/api/client/proto"
Expand Down Expand Up @@ -118,6 +119,29 @@ func (s *Server) AuthenticateUser(req AuthenticateUserRequest) error {
return err
}

var (
// authenticateWebauthnError is the generic error returned for failed WebAuthn
// authentication attempts.
authenticateWebauthnError = trace.AccessDenied("invalid Webauthn response")
// invalidU2FError is the error returned for failed U2F authentication attempts.
invalidU2FError = trace.AccessDenied("invalid U2F response")
// invalidUserPassError is the error for when either the provided username or
// password is incorrect.
invalidUserPassError = trace.AccessDenied("invalid username or password")
// invalidUserpass2FError is the error for when either the provided username,
// password, or second factor is incorrect.
invalidUserPass2FError = trace.AccessDenied("invalid username, password or second factor")
)

// IsInvalidLocalCredentialError checks if an error resulted from an incorrect username,
// password, or second factor.
func IsInvalidLocalCredentialError(err error) bool {
return errors.Is(err, invalidUserPassError) || errors.Is(err, invalidUserPass2FError)
}

// authenticateUser authenticates a user through various methods (password, MFA,
// passwordless)
// Returns the device used to authenticate (if applicable) and the username.
func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserRequest) (*types.MFADevice, error) {
if err := req.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
Expand All @@ -126,7 +150,7 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque

// Try 2nd-factor-enabled authentication schemes first.
var authenticateFn func() (*types.MFADevice, error)
var failMsg string // failMsg kept obscure on purpose, use logging for details
var authErr error // error message kept obscure on purpose, use logging for details
switch {
// cases in order of preference
case req.Webauthn != nil:
Expand All @@ -138,7 +162,7 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque
}
return s.validateMFAAuthResponse(ctx, user, mfaResponse, s.Identity /* u2fStorage */)
}
failMsg = "invalid Webauthn response"
authErr = authenticateWebauthnError
case req.U2F != nil:
authenticateFn = func() (*types.MFADevice, error) {
mfaResponse := &proto.MFAAuthenticateResponse{
Expand All @@ -152,7 +176,7 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque
}
return s.validateMFAAuthResponse(ctx, user, mfaResponse, s.Identity /* u2fStorage */)
}
failMsg = "invalid U2F response"
authErr = invalidU2FError
case req.OTP != nil:
authenticateFn = func() (*types.MFADevice, error) {
// OTP cannot be validated by validateMFAAuthResponse because we need to
Expand All @@ -163,7 +187,7 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque
}
return res.mfaDev, nil
}
failMsg = "invalid username, password or second factor"
authErr = invalidUserPass2FError
}
if authenticateFn != nil {
var dev *types.MFADevice
Expand All @@ -179,12 +203,12 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque
return nil, trace.Wrap(fieldErr)
}

return nil, trace.AccessDenied(failMsg)
return nil, trace.Wrap(authErr)
case dev == nil:
log.Debugf(
"MFA authentication returned nil device (Webauthn = %v, U2F = %v, TOTP = %v): %v.",
req.Webauthn != nil, req.U2F != nil, req.OTP != nil, err)
return nil, trace.AccessDenied(failMsg)
return nil, trace.Wrap(authErr)
default:
return dev, nil
}
Expand Down Expand Up @@ -232,7 +256,7 @@ func (s *Server) authenticateUser(ctx context.Context, req AuthenticateUserReque
// provide obscure message on purpose, while logging the real
// error server side
log.Debugf("User %v failed to authenticate: %v.", user, err)
return nil, trace.AccessDenied("invalid username or password")
return nil, trace.Wrap(invalidUserPassError)
}
return nil, nil
}
Expand Down
8 changes: 8 additions & 0 deletions tool/tsh/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ type CLIConf struct {
Approve, Deny bool
// Username is the Teleport user's username (to login into proxies)
Username string
// ExplicitUsername is true if Username was initially set by the end-user
// (for example, using command-line flags).
ExplicitUsername bool
// Proxy keeps the hostname:port of the SSH proxy to use
Proxy string
// TTL defines how long a session must be active (in minutes)
Expand Down Expand Up @@ -705,6 +708,8 @@ func Run(args []string, opts ...cliOption) error {
app.Usage(args)
return trace.Wrap(err)
}
// Did we initially get the Username from flags/env?
cf.ExplicitUsername = cf.Username != ""

// apply any options after parsing of arguments to ensure
// that defaults don't overwrite options.
Expand Down Expand Up @@ -1095,6 +1100,9 @@ func onLogin(cf *CLIConf) error {

key, err := tc.Login(cf.Context)
if err != nil {
if !cf.ExplicitUsername && auth.IsInvalidLocalCredentialError(err) {
fmt.Fprintf(os.Stderr, "\nhint: set the --user flag to log in as a specific user, or leave it empty to use the system user (%v)\n\n", tc.Username)
}
return trace.Wrap(err)
}
tc.AllowStdinHijack = false
Expand Down

0 comments on commit f7edeca

Please sign in to comment.