Skip to content

Commit

Permalink
Add UserTokens to allow password resets
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-kovoy committed Jan 16, 2020
1 parent 274b973 commit ef9f4ae
Show file tree
Hide file tree
Showing 29 changed files with 2,081 additions and 1,313 deletions.
65 changes: 0 additions & 65 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import (
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
Expand Down Expand Up @@ -3712,70 +3711,6 @@ func (s *IntSuite) TestList(c *check.C) {
}
}

// TestMultipleSignup makes sure that multiple users can create Teleport accounts.
func (s *IntSuite) TestMultipleSignup(c *check.C) {
tr := utils.NewTracer(utils.ThisFunction()).Start()
defer tr.Stop()

type createNewUserReq struct {
InviteToken string `json:"invite_token"`
Pass string `json:"pass"`
}

// Create and start a Teleport cluster.
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
SessionRecording: services.RecordAtNode,
LocalAuth: services.NewBool(true),
})
c.Assert(err, check.IsNil)

tconf := service.MakeDefaultConfig()
tconf.Auth.Preference.SetSecondFactor("off")
tconf.Auth.Enabled = true
tconf.Auth.ClusterConfig = clusterConfig
tconf.Proxy.Enabled = true
tconf.Proxy.DisableWebService = false
tconf.Proxy.DisableWebInterface = true
tconf.SSH.Enabled = true
return c, nil, nil, tconf
}
main := s.newTeleportWithConfig(makeConfig())
defer main.Stop(true)

mainAuth := main.Process.GetAuthServer()

// Create a few users to make sure the proxy uses the correct identity
// when connecting to the auth server.
for i := 0; i < 5; i++ {
// Create a random username.
username, err := utils.CryptoRandomHex(16)
c.Assert(err, check.IsNil)

// Create signup token, this is like doing "tctl users add foo foo".
token, err := mainAuth.CreateSignupToken(services.UserV1{
Name: username,
AllowedLogins: []string{username},
}, backend.Forever)
c.Assert(err, check.IsNil)

// Create client that will simulate web browser.
clt, err := createWebClient(main)
c.Assert(err, check.IsNil)

// Render the signup page.
_, err = clt.Get(context.Background(), clt.Endpoint("webapi", "users", "invites", token), url.Values{})
c.Assert(err, check.IsNil)

// Make sure signup is successful.
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "users"), createNewUserReq{
InviteToken: token,
Pass: "fake-password-123",
})
c.Assert(err, check.IsNil)
}
}

// TestDataTransfer makes sure that a "session.data" event is emitted at the
// end of a session that matches the amount of data that was transferred.
func (s *IntSuite) TestDataTransfer(c *check.C) {
Expand Down
154 changes: 52 additions & 102 deletions lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import (
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/julienschmidt/httprouter"
"github.com/tstranex/u2f"
)

type APIConfig struct {
Expand Down Expand Up @@ -103,9 +102,11 @@ func NewAPIServer(config *APIConfig) http.Handler {
srv.POST("/:version/users/:user/ssh/authenticate", srv.withAuth(srv.authenticateSSHUser))
srv.GET("/:version/users/:user/web/sessions/:sid", srv.withAuth(srv.getWebSession))
srv.DELETE("/:version/users/:user/web/sessions/:sid", srv.withAuth(srv.deleteWebSession))
srv.GET("/:version/signuptokens/:token", srv.withAuth(srv.getSignupTokenData))
srv.POST("/:version/signuptokens/users", srv.withAuth(srv.createUserWithToken))
srv.POST("/:version/signuptokens", srv.withAuth(srv.createSignupToken))

srv.POST("/:version/usertokens", srv.withAuth(srv.createUserToken))
srv.POST("/:version/usertokens/password", srv.withRate(srv.withAuth(srv.changePasswordWithToken)))
srv.GET("/:version/usertokens/:token", srv.withAuth(srv.getUserToken))
srv.GET("/:version/usertokens/:token/secrets", srv.withRate(srv.withAuth(srv.rotateUserTokenSecrets)))

// Servers and presence heartbeat
srv.POST("/:version/namespaces/:namespace/nodes", srv.withAuth(srv.upsertNode))
Expand Down Expand Up @@ -215,7 +216,6 @@ func NewAPIServer(config *APIConfig) http.Handler {

// U2F
srv.GET("/:version/u2f/signuptokens/:token", srv.withAuth(srv.getSignupU2FRegisterRequest))
srv.POST("/:version/u2f/users", srv.withAuth(srv.createUserWithU2FToken))
srv.POST("/:version/u2f/users/:user/sign", srv.withAuth(srv.u2fSignRequest))
srv.GET("/:version/u2f/appid", srv.withAuth(srv.getU2FAppID))

Expand Down Expand Up @@ -1123,6 +1123,53 @@ func (s *APIServer) getClusterCACert(auth ClientI, w http.ResponseWriter, r *htt
return localCA, nil
}

func (s *APIServer) rotateUserTokenSecrets(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
secrets, err := auth.RotateUserTokenSecrets(p.ByName("token"))
if err != nil {
return nil, trace.Wrap(err)
}

return secrets, nil
}

func (s *APIServer) getUserToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
usertoken, err := auth.GetUserToken(p.ByName("token"))
if err != nil {
return nil, trace.Wrap(err)
}

return usertoken, nil
}

func (s *APIServer) createUserToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req CreateUserTokenRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

resetToken, err := auth.CreateUserToken(req)
if err != nil {
return nil, trace.Wrap(err)
}

return resetToken, nil
}

func (s *APIServer) changePasswordWithToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req ChangePasswordWithTokenRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

webSession, err := auth.ChangePasswordWithToken(req)
if err != nil {
log.Debugf("failed to change user password with token: %v", err)
return nil, trace.Wrap(err)
}

return rawMessage(services.GetWebSessionMarshaler().MarshalWebSession(webSession, services.WithVersion(version)))
}

// getU2FAppID returns the U2F AppID in the auth configuration
func (s *APIServer) getU2FAppID(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
cap, err := auth.GetAuthPreference()
Expand Down Expand Up @@ -1226,26 +1273,6 @@ func (s *APIServer) getSession(auth ClientI, w http.ResponseWriter, r *http.Requ
return se, nil
}

type getSignupTokenDataResponse struct {
User string `json:"user"`
QRImg []byte `json:"qrimg"`
}

// getSignupTokenData returns the signup data for a token.
func (s *APIServer) getSignupTokenData(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
token := p.ByName("token")

user, otpQRCode, err := auth.GetSignupTokenData(token)
if err != nil {
return nil, trace.Wrap(err)
}

return &getSignupTokenDataResponse{
User: user,
QRImg: otpQRCode,
}, nil
}

func (s *APIServer) getSignupU2FRegisterRequest(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
token := p.ByName("token")
u2fRegReq, err := auth.GetSignupU2FRegisterRequest(token)
Expand All @@ -1255,83 +1282,6 @@ func (s *APIServer) getSignupU2FRegisterRequest(auth ClientI, w http.ResponseWri
return u2fRegReq, nil
}

type createSignupTokenReq struct {
User services.UserV1 `json:"user"`
TTL time.Duration `json:"ttl"`
}

func (s *APIServer) createSignupToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *createSignupTokenReq

if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

if err := req.User.Check(); err != nil {
return nil, trace.Wrap(err)
}

token, err := auth.CreateSignupToken(req.User, req.TTL)
if err != nil {
return nil, trace.Wrap(err)
}

return token, nil
}

type createUserWithTokenReq struct {
Token string `json:"token"`
Password string `json:"password"`
OTPToken string `json:"otp_token"`
}

func (s *APIServer) createUserWithToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *createUserWithTokenReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

cap, err := auth.GetAuthPreference()
if err != nil {
return nil, trace.Wrap(err)
}

var webSession services.WebSession

switch cap.GetSecondFactor() {
case teleport.OFF:
webSession, err = auth.CreateUserWithoutOTP(req.Token, req.Password)
case teleport.OTP, teleport.TOTP, teleport.HOTP:
webSession, err = auth.CreateUserWithOTP(req.Token, req.Password, req.OTPToken)
}
if err != nil {
log.Warningf("failed to create user: %v", err.Error())
return nil, trace.Wrap(err)
}

return rawMessage(services.GetWebSessionMarshaler().MarshalWebSession(webSession, services.WithVersion(version)))
}

type createUserWithU2FTokenReq struct {
Token string `json:"token"`
Password string `json:"password"`
U2FRegisterResponse u2f.RegisterResponse `json:"u2f_register_response"`
}

func (s *APIServer) createUserWithU2FToken(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *createUserWithU2FTokenReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

sess, err := auth.CreateUserWithU2FToken(req.Token, req.Password, req.U2FRegisterResponse)
if err != nil {
log.Error(err)
return nil, trace.Wrap(err)
}
return rawMessage(services.GetWebSessionMarshaler().MarshalWebSession(sess, services.WithVersion(version)))
}

type upsertOIDCConnectorRawReq struct {
Connector json.RawMessage `json:"connector"`
TTL time.Duration `json:"ttl"`
Expand Down
6 changes: 3 additions & 3 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,7 @@ func (s *AuthServer) DeleteToken(token string) (err error) {
}
}
// delete user token:
if err = s.Identity.DeleteSignupToken(token); err == nil {
if err = s.Identity.DeleteUserToken(token); err == nil {
return nil
}
// delete node token:
Expand All @@ -1222,14 +1222,14 @@ func (s *AuthServer) GetTokens(opts ...services.MarshalOption) (tokens []service
tokens = append(tokens, tkns.GetStaticTokens()...)
}
// get user tokens:
userTokens, err := s.Identity.GetSignupTokens()
userTokens, err := s.Identity.GetUserTokens()
if err != nil {
return nil, trace.Wrap(err)
}
// convert user tokens to machine tokens:
for _, t := range userTokens {
roles := teleport.Roles{teleport.RoleSignup}
tok, err := services.NewProvisionToken(t.Token, roles, t.Expires)
tok, err := services.NewProvisionToken(t.GetName(), roles, t.Expiry())
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
49 changes: 19 additions & 30 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,13 +734,6 @@ func (a *AuthWithRoles) UpsertTOTP(user string, otpSecret string) error {
return a.authServer.UpsertTOTP(user, otpSecret)
}

func (a *AuthWithRoles) GetOTPData(user string) (string, []byte, error) {
if err := a.currentUserAction(user); err != nil {
return "", nil, trace.Wrap(err)
}
return a.authServer.GetOTPData(user)
}

func (a *AuthWithRoles) PreAuthenticatedSignIn(user string) (services.WebSession, error) {
if err := a.currentUserAction(user); err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -1041,41 +1034,37 @@ func (a *AuthWithRoles) GenerateUserCerts(ctx context.Context, req proto.UserCer
}, nil
}

func (a *AuthWithRoles) CreateSignupToken(user services.UserV1, ttl time.Duration) (token string, e error) {
if err := a.action(defaults.Namespace, services.KindUser, services.VerbCreate); err != nil {
return "", trace.Wrap(err)
}
return a.authServer.CreateSignupToken(user, ttl)
}

func (a *AuthWithRoles) GetSignupTokenData(token string) (user string, otpQRCode []byte, err error) {
func (a *AuthWithRoles) GetSignupU2FRegisterRequest(token string) (u2fRegisterRequest *u2f.RegisterRequest, e error) {
// signup token are their own authz resource
return a.authServer.GetSignupTokenData(token)
return a.authServer.CreateSignupU2FRegisterRequest(token)
}

func (a *AuthWithRoles) GetSignupToken(token string) (*services.SignupToken, error) {
// signup token are their own authz resource
return a.authServer.GetSignupToken(token)
}
func (a *AuthWithRoles) CreateUserToken(req CreateUserTokenRequest) (services.UserToken, error) {
if err := a.action(defaults.Namespace, services.KindUser, services.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}

func (a *AuthWithRoles) GetSignupU2FRegisterRequest(token string) (u2fRegisterRequest *u2f.RegisterRequest, e error) {
// signup token are their own authz resource
return a.authServer.CreateSignupU2FRegisterRequest(token)
a.EmitAuditEvent(events.UserTokenCreated, events.EventFields{
events.UserTokenFor: req.Name,
events.UserTokenTTL: req.TTL,
})

return a.authServer.CreateUserToken(req)
}

func (a *AuthWithRoles) CreateUserWithOTP(token, password, otpToken string) (services.WebSession, error) {
func (a *AuthWithRoles) GetUserToken(tokenID string) (services.UserToken, error) {
// tokens are their own authz mechanism, no need to double check
return a.authServer.CreateUserWithOTP(token, password, otpToken)
return a.authServer.GetUserToken(tokenID)
}

func (a *AuthWithRoles) CreateUserWithoutOTP(token string, password string) (services.WebSession, error) {
func (a *AuthWithRoles) RotateUserTokenSecrets(tokenID string) (services.UserTokenSecrets, error) {
// tokens are their own authz mechanism, no need to double check
return a.authServer.CreateUserWithoutOTP(token, password)
return a.authServer.RotateUserTokenSecrets(tokenID)
}

func (a *AuthWithRoles) CreateUserWithU2FToken(token string, password string, u2fRegisterResponse u2f.RegisterResponse) (services.WebSession, error) {
// signup tokens are their own authz resource
return a.authServer.CreateUserWithU2FToken(token, password, u2fRegisterResponse)
func (a *AuthWithRoles) ChangePasswordWithToken(req ChangePasswordWithTokenRequest) (services.WebSession, error) {
// token is it's own auth, no need for extra auth
return a.authServer.ChangePasswordWithToken(req)
}

func (a *AuthWithRoles) UpsertUser(u services.User) error {
Expand Down
Loading

0 comments on commit ef9f4ae

Please sign in to comment.