Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add reset/passwd capability for local users #3287

Merged
merged 9 commits into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
}
}

// TestMultiple# makes sure that multiple users can create Teleport accounts.
func (s *IntSuite) TestMultiple#(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 # token, this is like doing "tctl users add foo foo".
token, err := mainAuth.Create#Token(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 # page.
_, err = clt.Get(context.Background(), clt.Endpoint("webapi", "users", "invites", token), url.Values{})
c.Assert(err, check.IsNil)

// Make sure # 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/#tokens/:token", srv.withAuth(srv.get#TokenData))
srv.POST("/:version/#tokens/users", srv.withAuth(srv.createUserWithToken))
srv.POST("/:version/#tokens", srv.withAuth(srv.create#Token))
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/#tokens/:token", srv.withAuth(srv.get#U2FRegisterRequest))
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 get#TokenDataResponse struct {
User string `json:"user"`
QRImg []byte `json:"qrimg"`
}

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

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

return &get#TokenDataResponse{
User: user,
QRImg: otpQRCode,
}, nil
}

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

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

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

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.Create#Token(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.Delete#Token(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.Get#Tokens()
// 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.Role#}
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) Create#Token(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.Create#Token(user, ttl)
}

func (a *AuthWithRoles) Get#TokenData(token string) (user string, otpQRCode []byte, err error) {
func (a *AuthWithRoles) Get#U2FRegisterRequest(token string) (u2fRegisterRequest *u2f.RegisterRequest, e error) {
// # token are their own authz resource
return a.authServer.Get#TokenData(token)
return a.authServer.Create#U2FRegisterRequest(token)
}

func (a *AuthWithRoles) Get#Token(token string) (*services.#Token, error) {
// # token are their own authz resource
return a.authServer.Get#Token(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) Get#U2FRegisterRequest(token string) (u2fRegisterRequest *u2f.RegisterRequest, e error) {
// # token are their own authz resource
return a.authServer.Create#U2FRegisterRequest(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) {
// # 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