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