diff --git a/auth/store.go b/auth/store.go index f872eb9ba..0aec49a52 100644 --- a/auth/store.go +++ b/auth/store.go @@ -37,6 +37,8 @@ var ( plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "auth") + ErrRootUserNotExist = errors.New("auth: root user does not exist") + ErrRootRoleNotExist = errors.New("auth: root user does not have root role") ErrUserAlreadyExist = errors.New("auth: user already exists") ErrUserNotFound = errors.New("auth: user not found") ErrRoleAlreadyExist = errors.New("auth: role already exists") @@ -47,9 +49,14 @@ var ( ErrPermissionNotGranted = errors.New("auth: permission is not granted to the role") ) +const ( + rootUser = "root" + rootRole = "root" +) + type AuthStore interface { // AuthEnable turns on the authentication feature - AuthEnable() + AuthEnable() error // AuthDisable turns off the authentication feature AuthDisable() @@ -114,15 +121,34 @@ type authStore struct { rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions } -func (as *authStore) AuthEnable() { +func (as *authStore) AuthEnable() error { value := []byte{1} b := as.be tx := b.BatchTx() tx.Lock() + defer func() { + tx.Unlock() + b.ForceCommit() + }() + + u := getUser(tx, rootUser) + if u == nil { + return ErrRootUserNotExist + } + + rootRoleExist := false + for _, r := range u.Roles { + if r == rootRole { + rootRoleExist = true + break + } + } + if !rootRoleExist { + return ErrRootRoleNotExist + } + tx.UnsafePut(authBucketName, enableFlagKey, value) - tx.Unlock() - b.ForceCommit() as.enabledMu.Lock() as.enabled = true @@ -131,6 +157,8 @@ func (as *authStore) AuthEnable() { as.rangePermCache = make(map[string]*unifiedRangePermissions) plog.Noticef("Authentication enabled") + + return nil } func (as *authStore) AuthDisable() { @@ -284,9 +312,11 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser return nil, ErrUserNotFound } - _, vs := tx.UnsafeRange(authRolesBucketName, []byte(r.Role), nil, 0) - if len(vs) != 1 { - return nil, ErrRoleNotFound + if r.Role != rootRole { + _, vs := tx.UnsafeRange(authRolesBucketName, []byte(r.Role), nil, 0) + if len(vs) != 1 { + return nil, ErrRoleNotFound + } } idx := sort.SearchStrings(user.Roles, r.Role) diff --git a/e2e/ctl_v3_auth_test.go b/e2e/ctl_v3_auth_test.go index 60ac64911..e0025f175 100644 --- a/e2e/ctl_v3_auth_test.go +++ b/e2e/ctl_v3_auth_test.go @@ -20,6 +20,15 @@ func TestCtlV3AuthEnable(t *testing.T) { testCtl(t, authEnableTest) } func TestCtlV3AuthDisable(t *testing.T) { testCtl(t, authDisableTest) } func authEnableTest(cx ctlCtx) { + // create root user with root role + if err := ctlV3User(cx, []string{"add", "root", "--interactive=false"}, "User root created", []string{"root"}); err != nil { + cx.t.Fatalf("failed to create root user %v", err) + } + + if err := ctlV3User(cx, []string{"grant-role", "root", "root"}, "Role root is granted to user root", nil); err != nil { + cx.t.Fatalf("failed to grant root user root role %v", err) + } + if err := ctlV3AuthEnable(cx); err != nil { cx.t.Fatalf("authEnableTest ctlV3AuthEnable error (%v)", err) } diff --git a/etcdserver/api/v3rpc/rpctypes/error.go b/etcdserver/api/v3rpc/rpctypes/error.go index 5023d5f26..55e05e7dc 100644 --- a/etcdserver/api/v3rpc/rpctypes/error.go +++ b/etcdserver/api/v3rpc/rpctypes/error.go @@ -38,6 +38,8 @@ var ( ErrGRPCRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large") + ErrGRPCRootUserNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not exist") + ErrGRPCRootRoleNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not have root role") ErrGRPCUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists") ErrGRPCUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found") ErrGRPCRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists") @@ -68,6 +70,8 @@ var ( grpc.ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge, + grpc.ErrorDesc(ErrGRPCRootUserNotExist): ErrGRPCRootUserNotExist, + grpc.ErrorDesc(ErrGRPCRootRoleNotExist): ErrGRPCRootRoleNotExist, grpc.ErrorDesc(ErrGRPCUserAlreadyExist): ErrGRPCUserAlreadyExist, grpc.ErrorDesc(ErrGRPCUserNotFound): ErrGRPCUserNotFound, grpc.ErrorDesc(ErrGRPCRoleAlreadyExist): ErrGRPCRoleAlreadyExist, @@ -99,6 +103,8 @@ var ( ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge) + ErrRootUserNotExist = Error(ErrGRPCRootUserNotExist) + ErrRootRoleNotExist = Error(ErrGRPCRootRoleNotExist) ErrUserAlreadyExist = Error(ErrGRPCUserAlreadyExist) ErrUserNotFound = Error(ErrGRPCUserNotFound) ErrRoleAlreadyExist = Error(ErrGRPCRoleAlreadyExist) diff --git a/etcdserver/api/v3rpc/util.go b/etcdserver/api/v3rpc/util.go index 33afe1d85..997e57892 100644 --- a/etcdserver/api/v3rpc/util.go +++ b/etcdserver/api/v3rpc/util.go @@ -37,6 +37,11 @@ func togRPCError(err error) error { return rpctypes.ErrGRPCRequestTooLarge case etcdserver.ErrNoSpace: return rpctypes.ErrGRPCNoSpace + + case auth.ErrRootUserNotExist: + return rpctypes.ErrGRPCRootUserNotExist + case auth.ErrRootRoleNotExist: + return rpctypes.ErrGRPCRootRoleNotExist case auth.ErrUserAlreadyExist: return rpctypes.ErrGRPCUserAlreadyExist case auth.ErrUserNotFound: diff --git a/etcdserver/apply.go b/etcdserver/apply.go index 7bf55abc8..d8a0cb3cb 100644 --- a/etcdserver/apply.go +++ b/etcdserver/apply.go @@ -521,7 +521,10 @@ func (a *applierV3Capped) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantRe } func (a *applierV3backend) AuthEnable() (*pb.AuthEnableResponse, error) { - a.s.AuthStore().AuthEnable() + err := a.s.AuthStore().AuthEnable() + if err != nil { + return nil, err + } return &pb.AuthEnableResponse{}, nil }