1// Copyright 2016 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package auth
16
17import (
18	"bytes"
19	"context"
20	"encoding/binary"
21	"errors"
22	"sort"
23	"strings"
24	"sync"
25	"sync/atomic"
26	"time"
27
28	"go.etcd.io/etcd/auth/authpb"
29	"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
30	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
31	"go.etcd.io/etcd/mvcc/backend"
32
33	"github.com/coreos/pkg/capnslog"
34	"go.uber.org/zap"
35	"golang.org/x/crypto/bcrypt"
36	"google.golang.org/grpc/credentials"
37	"google.golang.org/grpc/metadata"
38	"google.golang.org/grpc/peer"
39)
40
41var (
42	enableFlagKey = []byte("authEnabled")
43	authEnabled   = []byte{1}
44	authDisabled  = []byte{0}
45
46	revisionKey = []byte("authRevision")
47
48	authBucketName      = []byte("auth")
49	authUsersBucketName = []byte("authUsers")
50	authRolesBucketName = []byte("authRoles")
51
52	plog = capnslog.NewPackageLogger("go.etcd.io/etcd", "auth")
53
54	ErrRootUserNotExist     = errors.New("auth: root user does not exist")
55	ErrRootRoleNotExist     = errors.New("auth: root user does not have root role")
56	ErrUserAlreadyExist     = errors.New("auth: user already exists")
57	ErrUserEmpty            = errors.New("auth: user name is empty")
58	ErrUserNotFound         = errors.New("auth: user not found")
59	ErrRoleAlreadyExist     = errors.New("auth: role already exists")
60	ErrRoleNotFound         = errors.New("auth: role not found")
61	ErrRoleEmpty            = errors.New("auth: role name is empty")
62	ErrAuthFailed           = errors.New("auth: authentication failed, invalid user ID or password")
63	ErrNoPasswordUser       = errors.New("auth: authentication failed, password was given for no password user")
64	ErrPermissionDenied     = errors.New("auth: permission denied")
65	ErrRoleNotGranted       = errors.New("auth: role is not granted to the user")
66	ErrPermissionNotGranted = errors.New("auth: permission is not granted to the role")
67	ErrAuthNotEnabled       = errors.New("auth: authentication is not enabled")
68	ErrAuthOldRevision      = errors.New("auth: revision in header is old")
69	ErrInvalidAuthToken     = errors.New("auth: invalid auth token")
70	ErrInvalidAuthOpts      = errors.New("auth: invalid auth options")
71	ErrInvalidAuthMgmt      = errors.New("auth: invalid auth management")
72	ErrInvalidAuthMethod    = errors.New("auth: invalid auth signature method")
73	ErrMissingKey           = errors.New("auth: missing key data")
74	ErrKeyMismatch          = errors.New("auth: public and private keys don't match")
75	ErrVerifyOnly           = errors.New("auth: token signing attempted with verify-only key")
76)
77
78const (
79	rootUser = "root"
80	rootRole = "root"
81
82	tokenTypeSimple = "simple"
83	tokenTypeJWT    = "jwt"
84
85	revBytesLen = 8
86)
87
88type AuthInfo struct {
89	Username string
90	Revision uint64
91}
92
93// AuthenticateParamIndex is used for a key of context in the parameters of Authenticate()
94type AuthenticateParamIndex struct{}
95
96// AuthenticateParamSimpleTokenPrefix is used for a key of context in the parameters of Authenticate()
97type AuthenticateParamSimpleTokenPrefix struct{}
98
99// saveConsistentIndexFunc is used to sync consistentIndex to backend, now reusing store.saveIndex
100type saveConsistentIndexFunc func(tx backend.BatchTx)
101
102// AuthStore defines auth storage interface.
103type AuthStore interface {
104	// AuthEnable turns on the authentication feature
105	AuthEnable() error
106
107	// AuthDisable turns off the authentication feature
108	AuthDisable()
109
110	// IsAuthEnabled returns true if the authentication feature is enabled.
111	IsAuthEnabled() bool
112
113	// Authenticate does authentication based on given user name and password
114	Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error)
115
116	// Recover recovers the state of auth store from the given backend
117	Recover(b backend.Backend)
118
119	// UserAdd adds a new user
120	UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
121
122	// UserDelete deletes a user
123	UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
124
125	// UserChangePassword changes a password of a user
126	UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
127
128	// UserGrantRole grants a role to the user
129	UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error)
130
131	// UserGet gets the detailed information of a users
132	UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error)
133
134	// UserRevokeRole revokes a role of a user
135	UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error)
136
137	// RoleAdd adds a new role
138	RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
139
140	// RoleGrantPermission grants a permission to a role
141	RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error)
142
143	// RoleGet gets the detailed information of a role
144	RoleGet(r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error)
145
146	// RoleRevokePermission gets the detailed information of a role
147	RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error)
148
149	// RoleDelete gets the detailed information of a role
150	RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error)
151
152	// UserList gets a list of all users
153	UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error)
154
155	// RoleList gets a list of all roles
156	RoleList(r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error)
157
158	// IsPutPermitted checks put permission of the user
159	IsPutPermitted(authInfo *AuthInfo, key []byte) error
160
161	// IsRangePermitted checks range permission of the user
162	IsRangePermitted(authInfo *AuthInfo, key, rangeEnd []byte) error
163
164	// IsDeleteRangePermitted checks delete-range permission of the user
165	IsDeleteRangePermitted(authInfo *AuthInfo, key, rangeEnd []byte) error
166
167	// IsAdminPermitted checks admin permission of the user
168	IsAdminPermitted(authInfo *AuthInfo) error
169
170	// GenTokenPrefix produces a random string in a case of simple token
171	// in a case of JWT, it produces an empty string
172	GenTokenPrefix() (string, error)
173
174	// Revision gets current revision of authStore
175	Revision() uint64
176
177	// CheckPassword checks a given pair of username and password is correct
178	CheckPassword(username, password string) (uint64, error)
179
180	// Close does cleanup of AuthStore
181	Close() error
182
183	// AuthInfoFromCtx gets AuthInfo from gRPC's context
184	AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error)
185
186	// AuthInfoFromTLS gets AuthInfo from TLS info of gRPC's context
187	AuthInfoFromTLS(ctx context.Context) *AuthInfo
188
189	// WithRoot generates and installs a token that can be used as a root credential
190	WithRoot(ctx context.Context) context.Context
191
192	// HasRole checks that user has role
193	HasRole(user, role string) bool
194
195	// SetConsistentIndexSyncer sets consistentIndex syncer
196	SetConsistentIndexSyncer(syncer saveConsistentIndexFunc)
197}
198
199type TokenProvider interface {
200	info(ctx context.Context, token string, revision uint64) (*AuthInfo, bool)
201	assign(ctx context.Context, username string, revision uint64) (string, error)
202	enable()
203	disable()
204
205	invalidateUser(string)
206	genTokenPrefix() (string, error)
207}
208
209type authStore struct {
210	// atomic operations; need 64-bit align, or 32-bit tests will crash
211	revision uint64
212
213	lg        *zap.Logger
214	be        backend.Backend
215	enabled   bool
216	enabledMu sync.RWMutex
217
218	rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
219
220	tokenProvider       TokenProvider
221	syncConsistentIndex saveConsistentIndexFunc
222	bcryptCost          int // the algorithm cost / strength for hashing auth passwords
223}
224
225func (as *authStore) SetConsistentIndexSyncer(syncer saveConsistentIndexFunc) {
226	as.syncConsistentIndex = syncer
227}
228func (as *authStore) AuthEnable() error {
229	as.enabledMu.Lock()
230	defer as.enabledMu.Unlock()
231	if as.enabled {
232		if as.lg != nil {
233			as.lg.Info("authentication is already enabled; ignored auth enable request")
234		} else {
235			plog.Noticef("Authentication already enabled")
236		}
237		return nil
238	}
239	b := as.be
240	tx := b.BatchTx()
241	tx.Lock()
242	defer func() {
243		tx.Unlock()
244		b.ForceCommit()
245	}()
246
247	u := getUser(as.lg, tx, rootUser)
248	if u == nil {
249		return ErrRootUserNotExist
250	}
251
252	if !hasRootRole(u) {
253		return ErrRootRoleNotExist
254	}
255
256	tx.UnsafePut(authBucketName, enableFlagKey, authEnabled)
257
258	as.enabled = true
259	as.tokenProvider.enable()
260
261	as.rangePermCache = make(map[string]*unifiedRangePermissions)
262
263	as.setRevision(getRevision(tx))
264
265	if as.lg != nil {
266		as.lg.Info("enabled authentication")
267	} else {
268		plog.Noticef("Authentication enabled")
269	}
270	return nil
271}
272
273func (as *authStore) AuthDisable() {
274	as.enabledMu.Lock()
275	defer as.enabledMu.Unlock()
276	if !as.enabled {
277		return
278	}
279	b := as.be
280	tx := b.BatchTx()
281	tx.Lock()
282	tx.UnsafePut(authBucketName, enableFlagKey, authDisabled)
283	as.commitRevision(tx)
284	as.saveConsistentIndex(tx)
285	tx.Unlock()
286	b.ForceCommit()
287
288	as.enabled = false
289	as.tokenProvider.disable()
290
291	if as.lg != nil {
292		as.lg.Info("disabled authentication")
293	} else {
294		plog.Noticef("Authentication disabled")
295	}
296}
297
298func (as *authStore) Close() error {
299	as.enabledMu.Lock()
300	defer as.enabledMu.Unlock()
301	if !as.enabled {
302		return nil
303	}
304	as.tokenProvider.disable()
305	return nil
306}
307
308func (as *authStore) Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) {
309	if !as.IsAuthEnabled() {
310		return nil, ErrAuthNotEnabled
311	}
312
313	tx := as.be.BatchTx()
314	tx.Lock()
315	defer tx.Unlock()
316
317	user := getUser(as.lg, tx, username)
318	if user == nil {
319		return nil, ErrAuthFailed
320	}
321
322	if user.Options != nil && user.Options.NoPassword {
323		return nil, ErrAuthFailed
324	}
325
326	// Password checking is already performed in the API layer, so we don't need to check for now.
327	// Staleness of password can be detected with OCC in the API layer, too.
328
329	token, err := as.tokenProvider.assign(ctx, username, as.Revision())
330	if err != nil {
331		return nil, err
332	}
333
334	if as.lg != nil {
335		as.lg.Debug(
336			"authenticated a user",
337			zap.String("user-name", username),
338			zap.String("token", token),
339		)
340	} else {
341		plog.Debugf("authorized %s, token is %s", username, token)
342	}
343	return &pb.AuthenticateResponse{Token: token}, nil
344}
345
346func (as *authStore) CheckPassword(username, password string) (uint64, error) {
347	if !as.IsAuthEnabled() {
348		return 0, ErrAuthNotEnabled
349	}
350
351	var user *authpb.User
352	// CompareHashAndPassword is very expensive, so we use closures
353	// to avoid putting it in the critical section of the tx lock.
354	revision, err := func() (uint64, error) {
355		tx := as.be.BatchTx()
356		tx.Lock()
357		defer tx.Unlock()
358
359		user = getUser(as.lg, tx, username)
360		if user == nil {
361			return 0, ErrAuthFailed
362		}
363
364		if user.Options != nil && user.Options.NoPassword {
365			return 0, ErrNoPasswordUser
366		}
367
368		return getRevision(tx), nil
369	}()
370	if err != nil {
371		return 0, err
372	}
373
374	if bcrypt.CompareHashAndPassword(user.Password, []byte(password)) != nil {
375		if as.lg != nil {
376			as.lg.Info("invalid password", zap.String("user-name", username))
377		} else {
378			plog.Noticef("authentication failed, invalid password for user %s", username)
379		}
380		return 0, ErrAuthFailed
381	}
382	return revision, nil
383}
384
385func (as *authStore) Recover(be backend.Backend) {
386	enabled := false
387	as.be = be
388	tx := be.BatchTx()
389	tx.Lock()
390	_, vs := tx.UnsafeRange(authBucketName, enableFlagKey, nil, 0)
391	if len(vs) == 1 {
392		if bytes.Equal(vs[0], authEnabled) {
393			enabled = true
394		}
395	}
396
397	as.setRevision(getRevision(tx))
398
399	tx.Unlock()
400
401	as.enabledMu.Lock()
402	as.enabled = enabled
403	as.enabledMu.Unlock()
404}
405
406func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
407	if len(r.Name) == 0 {
408		return nil, ErrUserEmpty
409	}
410
411	var hashed []byte
412	var err error
413
414	noPassword := r.Options != nil && r.Options.NoPassword
415	if !noPassword {
416		hashed, err = bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
417		if err != nil {
418			if as.lg != nil {
419				as.lg.Warn(
420					"failed to bcrypt hash password",
421					zap.String("user-name", r.Name),
422					zap.Error(err),
423				)
424			} else {
425				plog.Errorf("failed to hash password: %s", err)
426			}
427			return nil, err
428		}
429	}
430
431	tx := as.be.BatchTx()
432	tx.Lock()
433	defer tx.Unlock()
434
435	user := getUser(as.lg, tx, r.Name)
436	if user != nil {
437		return nil, ErrUserAlreadyExist
438	}
439
440	options := r.Options
441	if options == nil {
442		options = &authpb.UserAddOptions{
443			NoPassword: false,
444		}
445	}
446
447	newUser := &authpb.User{
448		Name:     []byte(r.Name),
449		Password: hashed,
450		Options:  options,
451	}
452
453	putUser(as.lg, tx, newUser)
454
455	as.commitRevision(tx)
456	as.saveConsistentIndex(tx)
457
458	if as.lg != nil {
459		as.lg.Info("added a user", zap.String("user-name", r.Name))
460	} else {
461		plog.Noticef("added a new user: %s", r.Name)
462	}
463	return &pb.AuthUserAddResponse{}, nil
464}
465
466func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
467	if as.enabled && r.Name == rootUser {
468		if as.lg != nil {
469			as.lg.Warn("cannot delete 'root' user", zap.String("user-name", r.Name))
470		} else {
471			plog.Errorf("the user root must not be deleted")
472		}
473		return nil, ErrInvalidAuthMgmt
474	}
475
476	tx := as.be.BatchTx()
477	tx.Lock()
478	defer tx.Unlock()
479
480	user := getUser(as.lg, tx, r.Name)
481	if user == nil {
482		return nil, ErrUserNotFound
483	}
484
485	delUser(tx, r.Name)
486
487	as.commitRevision(tx)
488	as.saveConsistentIndex(tx)
489
490	as.invalidateCachedPerm(r.Name)
491	as.tokenProvider.invalidateUser(r.Name)
492
493	if as.lg != nil {
494		as.lg.Info(
495			"deleted a user",
496			zap.String("user-name", r.Name),
497			zap.Strings("user-roles", user.Roles),
498		)
499	} else {
500		plog.Noticef("deleted a user: %s", r.Name)
501	}
502	return &pb.AuthUserDeleteResponse{}, nil
503}
504
505func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
506	// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
507	// If the cost is too high, we should move the encryption to outside of the raft
508	hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
509	if err != nil {
510		if as.lg != nil {
511			as.lg.Warn(
512				"failed to bcrypt hash password",
513				zap.String("user-name", r.Name),
514				zap.Error(err),
515			)
516		} else {
517			plog.Errorf("failed to hash password: %s", err)
518		}
519		return nil, err
520	}
521
522	tx := as.be.BatchTx()
523	tx.Lock()
524	defer tx.Unlock()
525
526	user := getUser(as.lg, tx, r.Name)
527	if user == nil {
528		return nil, ErrUserNotFound
529	}
530
531	updatedUser := &authpb.User{
532		Name:     []byte(r.Name),
533		Roles:    user.Roles,
534		Password: hashed,
535		Options:  user.Options,
536	}
537
538	putUser(as.lg, tx, updatedUser)
539
540	as.commitRevision(tx)
541	as.saveConsistentIndex(tx)
542
543	as.invalidateCachedPerm(r.Name)
544	as.tokenProvider.invalidateUser(r.Name)
545
546	if as.lg != nil {
547		as.lg.Info(
548			"changed a password of a user",
549			zap.String("user-name", r.Name),
550			zap.Strings("user-roles", user.Roles),
551		)
552	} else {
553		plog.Noticef("changed a password of a user: %s", r.Name)
554	}
555	return &pb.AuthUserChangePasswordResponse{}, nil
556}
557
558func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) {
559	tx := as.be.BatchTx()
560	tx.Lock()
561	defer tx.Unlock()
562
563	user := getUser(as.lg, tx, r.User)
564	if user == nil {
565		return nil, ErrUserNotFound
566	}
567
568	if r.Role != rootRole {
569		role := getRole(tx, r.Role)
570		if role == nil {
571			return nil, ErrRoleNotFound
572		}
573	}
574
575	idx := sort.SearchStrings(user.Roles, r.Role)
576	if idx < len(user.Roles) && user.Roles[idx] == r.Role {
577		if as.lg != nil {
578			as.lg.Warn(
579				"ignored grant role request to a user",
580				zap.String("user-name", r.User),
581				zap.Strings("user-roles", user.Roles),
582				zap.String("duplicate-role-name", r.Role),
583			)
584		} else {
585			plog.Warningf("user %s is already granted role %s", r.User, r.Role)
586		}
587		return &pb.AuthUserGrantRoleResponse{}, nil
588	}
589
590	user.Roles = append(user.Roles, r.Role)
591	sort.Strings(user.Roles)
592
593	putUser(as.lg, tx, user)
594
595	as.invalidateCachedPerm(r.User)
596
597	as.commitRevision(tx)
598	as.saveConsistentIndex(tx)
599
600	if as.lg != nil {
601		as.lg.Info(
602			"granted a role to a user",
603			zap.String("user-name", r.User),
604			zap.Strings("user-roles", user.Roles),
605			zap.String("added-role-name", r.Role),
606		)
607	} else {
608		plog.Noticef("granted role %s to user %s", r.Role, r.User)
609	}
610	return &pb.AuthUserGrantRoleResponse{}, nil
611}
612
613func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
614	tx := as.be.BatchTx()
615	tx.Lock()
616	user := getUser(as.lg, tx, r.Name)
617	tx.Unlock()
618
619	if user == nil {
620		return nil, ErrUserNotFound
621	}
622
623	var resp pb.AuthUserGetResponse
624	resp.Roles = append(resp.Roles, user.Roles...)
625	return &resp, nil
626}
627
628func (as *authStore) UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
629	tx := as.be.BatchTx()
630	tx.Lock()
631	users := getAllUsers(as.lg, tx)
632	tx.Unlock()
633
634	resp := &pb.AuthUserListResponse{Users: make([]string, len(users))}
635	for i := range users {
636		resp.Users[i] = string(users[i].Name)
637	}
638	return resp, nil
639}
640
641func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
642	if as.enabled && r.Name == rootUser && r.Role == rootRole {
643		if as.lg != nil {
644			as.lg.Warn(
645				"'root' user cannot revoke 'root' role",
646				zap.String("user-name", r.Name),
647				zap.String("role-name", r.Role),
648			)
649		} else {
650			plog.Errorf("the role root must not be revoked from the user root")
651		}
652		return nil, ErrInvalidAuthMgmt
653	}
654
655	tx := as.be.BatchTx()
656	tx.Lock()
657	defer tx.Unlock()
658
659	user := getUser(as.lg, tx, r.Name)
660	if user == nil {
661		return nil, ErrUserNotFound
662	}
663
664	updatedUser := &authpb.User{
665		Name:     user.Name,
666		Password: user.Password,
667		Options:  user.Options,
668	}
669
670	for _, role := range user.Roles {
671		if role != r.Role {
672			updatedUser.Roles = append(updatedUser.Roles, role)
673		}
674	}
675
676	if len(updatedUser.Roles) == len(user.Roles) {
677		return nil, ErrRoleNotGranted
678	}
679
680	putUser(as.lg, tx, updatedUser)
681
682	as.invalidateCachedPerm(r.Name)
683
684	as.commitRevision(tx)
685	as.saveConsistentIndex(tx)
686
687	if as.lg != nil {
688		as.lg.Info(
689			"revoked a role from a user",
690			zap.String("user-name", r.Name),
691			zap.Strings("old-user-roles", user.Roles),
692			zap.Strings("new-user-roles", updatedUser.Roles),
693			zap.String("revoked-role-name", r.Role),
694		)
695	} else {
696		plog.Noticef("revoked role %s from user %s", r.Role, r.Name)
697	}
698	return &pb.AuthUserRevokeRoleResponse{}, nil
699}
700
701func (as *authStore) RoleGet(r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
702	tx := as.be.BatchTx()
703	tx.Lock()
704	defer tx.Unlock()
705
706	var resp pb.AuthRoleGetResponse
707
708	role := getRole(tx, r.Role)
709	if role == nil {
710		return nil, ErrRoleNotFound
711	}
712	resp.Perm = append(resp.Perm, role.KeyPermission...)
713	return &resp, nil
714}
715
716func (as *authStore) RoleList(r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
717	tx := as.be.BatchTx()
718	tx.Lock()
719	roles := getAllRoles(as.lg, tx)
720	tx.Unlock()
721
722	resp := &pb.AuthRoleListResponse{Roles: make([]string, len(roles))}
723	for i := range roles {
724		resp.Roles[i] = string(roles[i].Name)
725	}
726	return resp, nil
727}
728
729func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
730	tx := as.be.BatchTx()
731	tx.Lock()
732	defer tx.Unlock()
733
734	role := getRole(tx, r.Role)
735	if role == nil {
736		return nil, ErrRoleNotFound
737	}
738
739	updatedRole := &authpb.Role{
740		Name: role.Name,
741	}
742
743	for _, perm := range role.KeyPermission {
744		if !bytes.Equal(perm.Key, r.Key) || !bytes.Equal(perm.RangeEnd, r.RangeEnd) {
745			updatedRole.KeyPermission = append(updatedRole.KeyPermission, perm)
746		}
747	}
748
749	if len(role.KeyPermission) == len(updatedRole.KeyPermission) {
750		return nil, ErrPermissionNotGranted
751	}
752
753	putRole(as.lg, tx, updatedRole)
754
755	// TODO(mitake): currently single role update invalidates every cache
756	// It should be optimized.
757	as.clearCachedPerm()
758
759	as.commitRevision(tx)
760	as.saveConsistentIndex(tx)
761
762	if as.lg != nil {
763		as.lg.Info(
764			"revoked a permission on range",
765			zap.String("role-name", r.Role),
766			zap.String("key", string(r.Key)),
767			zap.String("range-end", string(r.RangeEnd)),
768		)
769	} else {
770		plog.Noticef("revoked key %s from role %s", r.Key, r.Role)
771	}
772	return &pb.AuthRoleRevokePermissionResponse{}, nil
773}
774
775func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
776	if as.enabled && r.Role == rootRole {
777		if as.lg != nil {
778			as.lg.Warn("cannot delete 'root' role", zap.String("role-name", r.Role))
779		} else {
780			plog.Errorf("the role root must not be deleted")
781		}
782		return nil, ErrInvalidAuthMgmt
783	}
784
785	tx := as.be.BatchTx()
786	tx.Lock()
787	defer tx.Unlock()
788
789	role := getRole(tx, r.Role)
790	if role == nil {
791		return nil, ErrRoleNotFound
792	}
793
794	delRole(tx, r.Role)
795
796	users := getAllUsers(as.lg, tx)
797	for _, user := range users {
798		updatedUser := &authpb.User{
799			Name:     user.Name,
800			Password: user.Password,
801			Options:  user.Options,
802		}
803
804		for _, role := range user.Roles {
805			if role != r.Role {
806				updatedUser.Roles = append(updatedUser.Roles, role)
807			}
808		}
809
810		if len(updatedUser.Roles) == len(user.Roles) {
811			continue
812		}
813
814		putUser(as.lg, tx, updatedUser)
815
816		as.invalidateCachedPerm(string(user.Name))
817	}
818
819	as.commitRevision(tx)
820	as.saveConsistentIndex(tx)
821
822	if as.lg != nil {
823		as.lg.Info("deleted a role", zap.String("role-name", r.Role))
824	} else {
825		plog.Noticef("deleted role %s", r.Role)
826	}
827	return &pb.AuthRoleDeleteResponse{}, nil
828}
829
830func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
831	if len(r.Name) == 0 {
832		return nil, ErrRoleEmpty
833	}
834
835	tx := as.be.BatchTx()
836	tx.Lock()
837	defer tx.Unlock()
838
839	role := getRole(tx, r.Name)
840	if role != nil {
841		return nil, ErrRoleAlreadyExist
842	}
843
844	newRole := &authpb.Role{
845		Name: []byte(r.Name),
846	}
847
848	putRole(as.lg, tx, newRole)
849
850	as.commitRevision(tx)
851	as.saveConsistentIndex(tx)
852
853	if as.lg != nil {
854		as.lg.Info("created a role", zap.String("role-name", r.Name))
855	} else {
856		plog.Noticef("Role %s is created", r.Name)
857	}
858	return &pb.AuthRoleAddResponse{}, nil
859}
860
861func (as *authStore) authInfoFromToken(ctx context.Context, token string) (*AuthInfo, bool) {
862	return as.tokenProvider.info(ctx, token, as.Revision())
863}
864
865type permSlice []*authpb.Permission
866
867func (perms permSlice) Len() int {
868	return len(perms)
869}
870
871func (perms permSlice) Less(i, j int) bool {
872	return bytes.Compare(perms[i].Key, perms[j].Key) < 0
873}
874
875func (perms permSlice) Swap(i, j int) {
876	perms[i], perms[j] = perms[j], perms[i]
877}
878
879func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) {
880	tx := as.be.BatchTx()
881	tx.Lock()
882	defer tx.Unlock()
883
884	role := getRole(tx, r.Name)
885	if role == nil {
886		return nil, ErrRoleNotFound
887	}
888
889	idx := sort.Search(len(role.KeyPermission), func(i int) bool {
890		return bytes.Compare(role.KeyPermission[i].Key, r.Perm.Key) >= 0
891	})
892
893	if idx < len(role.KeyPermission) && bytes.Equal(role.KeyPermission[idx].Key, r.Perm.Key) && bytes.Equal(role.KeyPermission[idx].RangeEnd, r.Perm.RangeEnd) {
894		// update existing permission
895		role.KeyPermission[idx].PermType = r.Perm.PermType
896	} else {
897		// append new permission to the role
898		newPerm := &authpb.Permission{
899			Key:      r.Perm.Key,
900			RangeEnd: r.Perm.RangeEnd,
901			PermType: r.Perm.PermType,
902		}
903
904		role.KeyPermission = append(role.KeyPermission, newPerm)
905		sort.Sort(permSlice(role.KeyPermission))
906	}
907
908	putRole(as.lg, tx, role)
909
910	// TODO(mitake): currently single role update invalidates every cache
911	// It should be optimized.
912	as.clearCachedPerm()
913
914	as.commitRevision(tx)
915	as.saveConsistentIndex(tx)
916
917	if as.lg != nil {
918		as.lg.Info(
919			"granted/updated a permission to a user",
920			zap.String("user-name", r.Name),
921			zap.String("permission-name", authpb.Permission_Type_name[int32(r.Perm.PermType)]),
922		)
923	} else {
924		plog.Noticef("role %s's permission of key %s is updated as %s", r.Name, r.Perm.Key, authpb.Permission_Type_name[int32(r.Perm.PermType)])
925	}
926	return &pb.AuthRoleGrantPermissionResponse{}, nil
927}
928
929func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeEnd []byte, permTyp authpb.Permission_Type) error {
930	// TODO(mitake): this function would be costly so we need a caching mechanism
931	if !as.IsAuthEnabled() {
932		return nil
933	}
934
935	// only gets rev == 0 when passed AuthInfo{}; no user given
936	if revision == 0 {
937		return ErrUserEmpty
938	}
939	rev := as.Revision()
940	if revision < rev {
941		if as.lg != nil {
942			as.lg.Warn("request auth revision is less than current node auth revision",
943				zap.Uint64("current node auth revision", rev),
944				zap.Uint64("request auth revision", revision),
945				zap.ByteString("request key", key),
946				zap.Error(ErrAuthOldRevision))
947		} else {
948			plog.Warningf("request auth revision is less than current node auth revision,"+
949				"current node auth revision is %d,"+
950				"request auth revision is %d,"+
951				"request key is %s, "+
952				"err is %v", rev, revision, key, ErrAuthOldRevision)
953		}
954		return ErrAuthOldRevision
955	}
956
957	tx := as.be.BatchTx()
958	tx.Lock()
959	defer tx.Unlock()
960
961	user := getUser(as.lg, tx, userName)
962	if user == nil {
963		if as.lg != nil {
964			as.lg.Warn("cannot find a user for permission check", zap.String("user-name", userName))
965		} else {
966			plog.Errorf("invalid user name %s for permission checking", userName)
967		}
968		return ErrPermissionDenied
969	}
970
971	// root role should have permission on all ranges
972	if hasRootRole(user) {
973		return nil
974	}
975
976	if as.isRangeOpPermitted(tx, userName, key, rangeEnd, permTyp) {
977		return nil
978	}
979
980	return ErrPermissionDenied
981}
982
983func (as *authStore) IsPutPermitted(authInfo *AuthInfo, key []byte) error {
984	return as.isOpPermitted(authInfo.Username, authInfo.Revision, key, nil, authpb.WRITE)
985}
986
987func (as *authStore) IsRangePermitted(authInfo *AuthInfo, key, rangeEnd []byte) error {
988	return as.isOpPermitted(authInfo.Username, authInfo.Revision, key, rangeEnd, authpb.READ)
989}
990
991func (as *authStore) IsDeleteRangePermitted(authInfo *AuthInfo, key, rangeEnd []byte) error {
992	return as.isOpPermitted(authInfo.Username, authInfo.Revision, key, rangeEnd, authpb.WRITE)
993}
994
995func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
996	if !as.IsAuthEnabled() {
997		return nil
998	}
999	if authInfo == nil || authInfo.Username == "" {
1000		return ErrUserEmpty
1001	}
1002
1003	tx := as.be.BatchTx()
1004	tx.Lock()
1005	u := getUser(as.lg, tx, authInfo.Username)
1006	tx.Unlock()
1007
1008	if u == nil {
1009		return ErrUserNotFound
1010	}
1011
1012	if !hasRootRole(u) {
1013		return ErrPermissionDenied
1014	}
1015
1016	return nil
1017}
1018
1019func getUser(lg *zap.Logger, tx backend.BatchTx, username string) *authpb.User {
1020	_, vs := tx.UnsafeRange(authUsersBucketName, []byte(username), nil, 0)
1021	if len(vs) == 0 {
1022		return nil
1023	}
1024
1025	user := &authpb.User{}
1026	err := user.Unmarshal(vs[0])
1027	if err != nil {
1028		if lg != nil {
1029			lg.Panic(
1030				"failed to unmarshal 'authpb.User'",
1031				zap.String("user-name", username),
1032				zap.Error(err),
1033			)
1034		} else {
1035			plog.Panicf("failed to unmarshal user struct (name: %s): %s", username, err)
1036		}
1037	}
1038	return user
1039}
1040
1041func getAllUsers(lg *zap.Logger, tx backend.BatchTx) []*authpb.User {
1042	_, vs := tx.UnsafeRange(authUsersBucketName, []byte{0}, []byte{0xff}, -1)
1043	if len(vs) == 0 {
1044		return nil
1045	}
1046
1047	users := make([]*authpb.User, len(vs))
1048	for i := range vs {
1049		user := &authpb.User{}
1050		err := user.Unmarshal(vs[i])
1051		if err != nil {
1052			if lg != nil {
1053				lg.Panic("failed to unmarshal 'authpb.User'", zap.Error(err))
1054			} else {
1055				plog.Panicf("failed to unmarshal user struct: %s", err)
1056			}
1057		}
1058		users[i] = user
1059	}
1060	return users
1061}
1062
1063func putUser(lg *zap.Logger, tx backend.BatchTx, user *authpb.User) {
1064	b, err := user.Marshal()
1065	if err != nil {
1066		if lg != nil {
1067			lg.Panic("failed to unmarshal 'authpb.User'", zap.Error(err))
1068		} else {
1069			plog.Panicf("failed to marshal user struct (name: %s): %s", user.Name, err)
1070		}
1071	}
1072	tx.UnsafePut(authUsersBucketName, user.Name, b)
1073}
1074
1075func delUser(tx backend.BatchTx, username string) {
1076	tx.UnsafeDelete(authUsersBucketName, []byte(username))
1077}
1078
1079func getRole(tx backend.BatchTx, rolename string) *authpb.Role {
1080	_, vs := tx.UnsafeRange(authRolesBucketName, []byte(rolename), nil, 0)
1081	if len(vs) == 0 {
1082		return nil
1083	}
1084
1085	role := &authpb.Role{}
1086	err := role.Unmarshal(vs[0])
1087	if err != nil {
1088		plog.Panicf("failed to unmarshal role struct (name: %s): %s", rolename, err)
1089	}
1090	return role
1091}
1092
1093func getAllRoles(lg *zap.Logger, tx backend.BatchTx) []*authpb.Role {
1094	_, vs := tx.UnsafeRange(authRolesBucketName, []byte{0}, []byte{0xff}, -1)
1095	if len(vs) == 0 {
1096		return nil
1097	}
1098
1099	roles := make([]*authpb.Role, len(vs))
1100	for i := range vs {
1101		role := &authpb.Role{}
1102		err := role.Unmarshal(vs[i])
1103		if err != nil {
1104			if lg != nil {
1105				lg.Panic("failed to unmarshal 'authpb.Role'", zap.Error(err))
1106			} else {
1107				plog.Panicf("failed to unmarshal role struct: %s", err)
1108			}
1109		}
1110		roles[i] = role
1111	}
1112	return roles
1113}
1114
1115func putRole(lg *zap.Logger, tx backend.BatchTx, role *authpb.Role) {
1116	b, err := role.Marshal()
1117	if err != nil {
1118		if lg != nil {
1119			lg.Panic(
1120				"failed to marshal 'authpb.Role'",
1121				zap.String("role-name", string(role.Name)),
1122				zap.Error(err),
1123			)
1124		} else {
1125			plog.Panicf("failed to marshal role struct (name: %s): %s", role.Name, err)
1126		}
1127	}
1128
1129	tx.UnsafePut(authRolesBucketName, role.Name, b)
1130}
1131
1132func delRole(tx backend.BatchTx, rolename string) {
1133	tx.UnsafeDelete(authRolesBucketName, []byte(rolename))
1134}
1135
1136func (as *authStore) IsAuthEnabled() bool {
1137	as.enabledMu.RLock()
1138	defer as.enabledMu.RUnlock()
1139	return as.enabled
1140}
1141
1142// NewAuthStore creates a new AuthStore.
1143func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider, bcryptCost int) *authStore {
1144	if bcryptCost < bcrypt.MinCost || bcryptCost > bcrypt.MaxCost {
1145		if lg != nil {
1146			lg.Warn(
1147				"use default bcrypt cost instead of the invalid given cost",
1148				zap.Int("min-cost", bcrypt.MinCost),
1149				zap.Int("max-cost", bcrypt.MaxCost),
1150				zap.Int("default-cost", bcrypt.DefaultCost),
1151				zap.Int("given-cost", bcryptCost))
1152		} else {
1153			plog.Warningf("Use default bcrypt-cost %d instead of the invalid value %d",
1154				bcrypt.DefaultCost, bcryptCost)
1155		}
1156
1157		bcryptCost = bcrypt.DefaultCost
1158	}
1159
1160	tx := be.BatchTx()
1161	tx.Lock()
1162
1163	tx.UnsafeCreateBucket(authBucketName)
1164	tx.UnsafeCreateBucket(authUsersBucketName)
1165	tx.UnsafeCreateBucket(authRolesBucketName)
1166
1167	enabled := false
1168	_, vs := tx.UnsafeRange(authBucketName, enableFlagKey, nil, 0)
1169	if len(vs) == 1 {
1170		if bytes.Equal(vs[0], authEnabled) {
1171			enabled = true
1172		}
1173	}
1174
1175	as := &authStore{
1176		revision:       getRevision(tx),
1177		lg:             lg,
1178		be:             be,
1179		enabled:        enabled,
1180		rangePermCache: make(map[string]*unifiedRangePermissions),
1181		tokenProvider:  tp,
1182		bcryptCost:     bcryptCost,
1183	}
1184
1185	if enabled {
1186		as.tokenProvider.enable()
1187	}
1188
1189	if as.Revision() == 0 {
1190		as.commitRevision(tx)
1191	}
1192
1193	as.setupMetricsReporter()
1194
1195	tx.Unlock()
1196	be.ForceCommit()
1197
1198	return as
1199}
1200
1201func hasRootRole(u *authpb.User) bool {
1202	// u.Roles is sorted in UserGrantRole(), so we can use binary search.
1203	idx := sort.SearchStrings(u.Roles, rootRole)
1204	return idx != len(u.Roles) && u.Roles[idx] == rootRole
1205}
1206
1207func (as *authStore) commitRevision(tx backend.BatchTx) {
1208	atomic.AddUint64(&as.revision, 1)
1209	revBytes := make([]byte, revBytesLen)
1210	binary.BigEndian.PutUint64(revBytes, as.Revision())
1211	tx.UnsafePut(authBucketName, revisionKey, revBytes)
1212}
1213
1214func getRevision(tx backend.BatchTx) uint64 {
1215	_, vs := tx.UnsafeRange(authBucketName, revisionKey, nil, 0)
1216	if len(vs) != 1 {
1217		// this can happen in the initialization phase
1218		return 0
1219	}
1220	return binary.BigEndian.Uint64(vs[0])
1221}
1222
1223func (as *authStore) setRevision(rev uint64) {
1224	atomic.StoreUint64(&as.revision, rev)
1225}
1226
1227func (as *authStore) Revision() uint64 {
1228	return atomic.LoadUint64(&as.revision)
1229}
1230
1231func (as *authStore) AuthInfoFromTLS(ctx context.Context) (ai *AuthInfo) {
1232	peer, ok := peer.FromContext(ctx)
1233	if !ok || peer == nil || peer.AuthInfo == nil {
1234		return nil
1235	}
1236
1237	tlsInfo := peer.AuthInfo.(credentials.TLSInfo)
1238	for _, chains := range tlsInfo.State.VerifiedChains {
1239		if len(chains) < 1 {
1240			continue
1241		}
1242		ai = &AuthInfo{
1243			Username: chains[0].Subject.CommonName,
1244			Revision: as.Revision(),
1245		}
1246		md, ok := metadata.FromIncomingContext(ctx)
1247		if !ok {
1248			return nil
1249		}
1250
1251		// gRPC-gateway proxy request to etcd server includes Grpcgateway-Accept
1252		// header. The proxy uses etcd client server certificate. If the certificate
1253		// has a CommonName we should never use this for authentication.
1254		if gw := md["grpcgateway-accept"]; len(gw) > 0 {
1255			if as.lg != nil {
1256				as.lg.Warn(
1257					"ignoring common name in gRPC-gateway proxy request",
1258					zap.String("common-name", ai.Username),
1259					zap.String("user-name", ai.Username),
1260					zap.Uint64("revision", ai.Revision),
1261				)
1262			} else {
1263				plog.Warningf("ignoring common name in gRPC-gateway proxy request %s", ai.Username)
1264			}
1265			return nil
1266		}
1267		if as.lg != nil {
1268			as.lg.Debug(
1269				"found command name",
1270				zap.String("common-name", ai.Username),
1271				zap.String("user-name", ai.Username),
1272				zap.Uint64("revision", ai.Revision),
1273			)
1274		} else {
1275			plog.Debugf("found common name %s", ai.Username)
1276		}
1277		break
1278	}
1279	return ai
1280}
1281
1282func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
1283	md, ok := metadata.FromIncomingContext(ctx)
1284	if !ok {
1285		return nil, nil
1286	}
1287
1288	//TODO(mitake|hexfusion) review unifying key names
1289	ts, ok := md[rpctypes.TokenFieldNameGRPC]
1290	if !ok {
1291		ts, ok = md[rpctypes.TokenFieldNameSwagger]
1292	}
1293	if !ok {
1294		return nil, nil
1295	}
1296
1297	token := ts[0]
1298	authInfo, uok := as.authInfoFromToken(ctx, token)
1299	if !uok {
1300		if as.lg != nil {
1301			as.lg.Warn("invalid auth token", zap.String("token", token))
1302		} else {
1303			plog.Warningf("invalid auth token: %s", token)
1304		}
1305		return nil, ErrInvalidAuthToken
1306	}
1307
1308	return authInfo, nil
1309}
1310
1311func (as *authStore) GenTokenPrefix() (string, error) {
1312	return as.tokenProvider.genTokenPrefix()
1313}
1314
1315func decomposeOpts(lg *zap.Logger, optstr string) (string, map[string]string, error) {
1316	opts := strings.Split(optstr, ",")
1317	tokenType := opts[0]
1318
1319	typeSpecificOpts := make(map[string]string)
1320	for i := 1; i < len(opts); i++ {
1321		pair := strings.Split(opts[i], "=")
1322
1323		if len(pair) != 2 {
1324			if lg != nil {
1325				lg.Warn("invalid token option", zap.String("option", optstr))
1326			} else {
1327				plog.Errorf("invalid token specific option: %s", optstr)
1328			}
1329			return "", nil, ErrInvalidAuthOpts
1330		}
1331
1332		if _, ok := typeSpecificOpts[pair[0]]; ok {
1333			if lg != nil {
1334				lg.Warn(
1335					"invalid token option",
1336					zap.String("option", optstr),
1337					zap.String("duplicate-parameter", pair[0]),
1338				)
1339			} else {
1340				plog.Errorf("invalid token specific option, duplicated parameters (%s): %s", pair[0], optstr)
1341			}
1342			return "", nil, ErrInvalidAuthOpts
1343		}
1344
1345		typeSpecificOpts[pair[0]] = pair[1]
1346	}
1347
1348	return tokenType, typeSpecificOpts, nil
1349
1350}
1351
1352// NewTokenProvider creates a new token provider.
1353func NewTokenProvider(
1354	lg *zap.Logger,
1355	tokenOpts string,
1356	indexWaiter func(uint64) <-chan struct{},
1357	TokenTTL time.Duration) (TokenProvider, error) {
1358	tokenType, typeSpecificOpts, err := decomposeOpts(lg, tokenOpts)
1359	if err != nil {
1360		return nil, ErrInvalidAuthOpts
1361	}
1362
1363	switch tokenType {
1364	case tokenTypeSimple:
1365		if lg != nil {
1366			lg.Warn("simple token is not cryptographically signed")
1367		} else {
1368			plog.Warningf("simple token is not cryptographically signed")
1369		}
1370		return newTokenProviderSimple(lg, indexWaiter, TokenTTL), nil
1371
1372	case tokenTypeJWT:
1373		return newTokenProviderJWT(lg, typeSpecificOpts)
1374
1375	case "":
1376		return newTokenProviderNop()
1377
1378	default:
1379		if lg != nil {
1380			lg.Warn(
1381				"unknown token type",
1382				zap.String("type", tokenType),
1383				zap.Error(ErrInvalidAuthOpts),
1384			)
1385		} else {
1386			plog.Errorf("unknown token type: %s", tokenType)
1387		}
1388		return nil, ErrInvalidAuthOpts
1389	}
1390}
1391
1392func (as *authStore) WithRoot(ctx context.Context) context.Context {
1393	if !as.IsAuthEnabled() {
1394		return ctx
1395	}
1396
1397	var ctxForAssign context.Context
1398	if ts, ok := as.tokenProvider.(*tokenSimple); ok && ts != nil {
1399		ctx1 := context.WithValue(ctx, AuthenticateParamIndex{}, uint64(0))
1400		prefix, err := ts.genTokenPrefix()
1401		if err != nil {
1402			if as.lg != nil {
1403				as.lg.Warn(
1404					"failed to generate prefix of internally used token",
1405					zap.Error(err),
1406				)
1407			} else {
1408				plog.Errorf("failed to generate prefix of internally used token")
1409			}
1410			return ctx
1411		}
1412		ctxForAssign = context.WithValue(ctx1, AuthenticateParamSimpleTokenPrefix{}, prefix)
1413	} else {
1414		ctxForAssign = ctx
1415	}
1416
1417	token, err := as.tokenProvider.assign(ctxForAssign, "root", as.Revision())
1418	if err != nil {
1419		// this must not happen
1420		if as.lg != nil {
1421			as.lg.Warn(
1422				"failed to assign token for lease revoking",
1423				zap.Error(err),
1424			)
1425		} else {
1426			plog.Errorf("failed to assign token for lease revoking: %s", err)
1427		}
1428		return ctx
1429	}
1430
1431	mdMap := map[string]string{
1432		rpctypes.TokenFieldNameGRPC: token,
1433	}
1434	tokenMD := metadata.New(mdMap)
1435
1436	// use "mdIncomingKey{}" since it's called from local etcdserver
1437	return metadata.NewIncomingContext(ctx, tokenMD)
1438}
1439
1440func (as *authStore) HasRole(user, role string) bool {
1441	tx := as.be.BatchTx()
1442	tx.Lock()
1443	u := getUser(as.lg, tx, user)
1444	tx.Unlock()
1445
1446	if u == nil {
1447		if as.lg != nil {
1448			as.lg.Warn(
1449				"'has-role' requested for non-existing user",
1450				zap.String("user-name", user),
1451				zap.String("role-name", role),
1452			)
1453		} else {
1454			plog.Warningf("tried to check user %s has role %s, but user %s doesn't exist", user, role, user)
1455		}
1456		return false
1457	}
1458
1459	for _, r := range u.Roles {
1460		if role == r {
1461			return true
1462		}
1463	}
1464	return false
1465}
1466
1467func (as *authStore) BcryptCost() int {
1468	return as.bcryptCost
1469}
1470
1471func (as *authStore) saveConsistentIndex(tx backend.BatchTx) {
1472	if as.syncConsistentIndex != nil {
1473		as.syncConsistentIndex(tx)
1474	} else {
1475		if as.lg != nil {
1476			as.lg.Error("failed to save consistentIndex,syncConsistentIndex is nil")
1477		} else {
1478			plog.Error("failed to save consistentIndex,syncConsistentIndex is nil")
1479		}
1480	}
1481}
1482
1483func (as *authStore) setupMetricsReporter() {
1484	reportCurrentAuthRevMu.Lock()
1485	reportCurrentAuthRev = func() float64 {
1486		return float64(as.Revision())
1487	}
1488	reportCurrentAuthRevMu.Unlock()
1489}
1490