Skip to content

Commit

Permalink
Add reset/passwd capability for local users (#3287)
Browse files Browse the repository at this point in the history
* Add UserTokens to allow password resets

* Pass context down through ChangePasswordWithToken

* Rename UserToken to ResetPasswordToken

* Add auto formatting for proto files

* Add common Marshaller interfaces to reset password token
  • Loading branch information
alex-kovoy authored Feb 4, 2020
1 parent 274b973 commit 8f0e967
Show file tree
Hide file tree
Showing 41 changed files with 5,291 additions and 2,310 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ grpc: buildbox
buildbox-grpc:
# standard GRPC output
echo $$PROTO_INCLUDE
find lib/ -iname *.proto | xargs clang-format -i -style='{ColumnLimit: 100, IndentWidth: 4, Language: Proto}'

cd lib/events && protoc -I=.:$$PROTO_INCLUDE \
--gofast_out=plugins=grpc:.\
*.proto
Expand Down
2 changes: 1 addition & 1 deletion build.assets/grpc/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ARG PLATFORM
ENV TARBALL protoc-${PROTOC_VER}-${PLATFORM}.zip
ENV GOGOPROTO_ROOT ${GOPATH}/src/github.com/gogo/protobuf

RUN apt-get update && apt-get install unzip
RUN apt-get update && apt-get install unzip clang-format -y

RUN curl -L -o /tmp/${TARBALL} https://github.com/google/protobuf/releases/download/v${PROTOC_VER}/${TARBALL}
RUN cd /tmp && unzip /tmp/protoc-${PROTOC_VER}-linux-x86_64.zip -d /usr/local && rm /tmp/${TARBALL}
Expand Down
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
118 changes: 16 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,7 @@ 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/web/password/token", srv.withRate(srv.withAuth(srv.changePasswordWithToken)))

// Servers and presence heartbeat
srv.POST("/:version/namespaces/:namespace/nodes", srv.withAuth(srv.upsertNode))
Expand Down Expand Up @@ -215,7 +212,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 +1119,21 @@ func (s *APIServer) getClusterCACert(auth ClientI, w http.ResponseWriter, r *htt
return localCA, 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(r.Context(), 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 +1237,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 +1246,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
14 changes: 7 additions & 7 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1194,8 +1194,8 @@ func (s *AuthServer) DeleteToken(token string) (err error) {
return trace.BadParameter("token %s is statically configured and cannot be removed", token)
}
}
// delete user token:
if err = s.Identity.DeleteSignupToken(token); err == nil {
// delete reset password token:
if err = s.Identity.DeleteResetPasswordToken(context.TODO(), token); err == nil {
return nil
}
// delete node token:
Expand All @@ -1221,15 +1221,15 @@ func (s *AuthServer) GetTokens(opts ...services.MarshalOption) (tokens []service
if err == nil {
tokens = append(tokens, tkns.GetStaticTokens()...)
}
// get user tokens:
userTokens, err := s.Identity.GetSignupTokens()
// get reset password tokens:
resetPasswordTokens, err := s.Identity.GetResetPasswordTokens(context.TODO())
if err != nil {
return nil, trace.Wrap(err)
}
// convert user tokens to machine tokens:
for _, t := range userTokens {
// convert reset password tokens to machine tokens:
for _, t := range resetPasswordTokens {
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
50 changes: 20 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,38 @@ 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) CreateResetPasswordToken(ctx context.Context, req CreateResetPasswordTokenRequest) (services.ResetPasswordToken, 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.ResetPasswordTokenCreated, events.EventFields{
events.ResetPasswordTokenFor: req.Name,
events.ResetPasswordTokenTTL: req.TTL.String(),
events.EventUser: a.user.GetName(),
})

return a.authServer.CreateResetPasswordToken(ctx, req)
}

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

func (a *AuthWithRoles) CreateUserWithoutOTP(token string, password string) (services.WebSession, error) {
func (a *AuthWithRoles) RotateResetPasswordTokenSecrets(ctx context.Context, tokenID string) (services.ResetPasswordTokenSecrets, error) {
// tokens are their own authz mechanism, no need to double check
return a.authServer.CreateUserWithoutOTP(token, password)
return a.authServer.RotateResetPasswordTokenSecrets(ctx, 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(ctx context.Context, req ChangePasswordWithTokenRequest) (services.WebSession, error) {
// Token is it's own authentication, no need to double check.
return a.authServer.ChangePasswordWithToken(ctx, req)
}

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

0 comments on commit 8f0e967

Please sign in to comment.