/
githubmirror
/
etcd
Обзор
Документация
Войти
/
githubmirror
/
etcd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
server/etcdserver/apply/auth_test.go
1 187 строк
37 KB
Alessio Attilio
apply: fix data inconsistency in txns by skipping range execution
22 май 2026, 13:14
22 май 2026, 13:14
fbba4f4
Код
Авторство
О чём код?
// Copyright 2023 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package apply import ( "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "golang.org/x/crypto/bcrypt" "go.etcd.io/etcd/api/v3/authpb" pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/membershippb" "go.etcd.io/etcd/client/pkg/v3/types" "go.etcd.io/etcd/server/v3/auth" "go.etcd.io/etcd/server/v3/etcdserver/api/membership" "go.etcd.io/etcd/server/v3/etcdserver/api/v3alarm" "go.etcd.io/etcd/server/v3/etcdserver/cindex" "go.etcd.io/etcd/server/v3/lease" "go.etcd.io/etcd/server/v3/storage/backend" betesting "go.etcd.io/etcd/server/v3/storage/backend/testing" "go.etcd.io/etcd/server/v3/storage/mvcc" "go.etcd.io/etcd/server/v3/storage/schema" ) func dummyIndexWaiter(_ uint64) <-chan struct{} { ch := make(chan struct{}, 1) ch <- struct{}{} return ch } func dummyApplyFunc(_ *InternalRaftRequestWrapper, shouldApplyV3 membership.ShouldApplyV3) *Result { return &Result{} } type fakeRaftStatusGetter struct{} func (*fakeRaftStatusGetter) MemberID() types.ID { return 0 } func (*fakeRaftStatusGetter) Leader() types.ID { return 0 } func (*fakeRaftStatusGetter) CommittedIndex() uint64 { return 0 } func (*fakeRaftStatusGetter) AppliedIndex() uint64 { return 0 } func (*fakeRaftStatusGetter) Term() uint64 { return 0 } type fakeSnapshotServer struct{} func (*fakeSnapshotServer) ForceSnapshot() {} func defaultAuthApplierV3(t *testing.T) *authApplierV3 { lg := zaptest.NewLogger(t) be, _ := betesting.NewDefaultTmpBackend(t) t.Cleanup(func() { betesting.Close(t, be) }) cluster := membership.NewCluster(lg) lessor := lease.NewLessor(lg, be, cluster, lease.LessorConfig{}) kv := mvcc.NewStore(lg, be, lessor, mvcc.StoreConfig{}) alarmStore, err := v3alarm.NewAlarmStore(lg, schema.NewAlarmBackend(lg, be)) require.NoError(t, err) tp, err := auth.NewTokenProvider(lg, "simple", dummyIndexWaiter, 300*time.Second) require.NoError(t, err) authStore := auth.NewAuthStore( lg, schema.NewAuthBackend(lg, be), tp, bcrypt.DefaultCost, ) consistentIndex := cindex.NewConsistentIndex(be) return newAuthApplierV3( authStore, newApplierV3Backend(ApplierOptions{ Logger: lg, KV: kv, AlarmStore: alarmStore, ConsistentIndex: consistentIndex, AuthStore: authStore, Lessor: lessor, Cluster: cluster, RaftStatus: &fakeRaftStatusGetter{}, SnapshotServer: &fakeSnapshotServer{}, TxnModeWriteWithSharedBuffer: false, }), lessor) } const ( userRoot = "root" roleRoot = "root" userReadOnly = "user_read_only" roleReadOnly = "role_read_only" userWriteOnly = "user_write_only" roleWriteOnly = "role_write_only" key = "key" rangeEnd = "rangeEnd" keyOutsideRange = "rangeEnd_outside" leaseID = 1 ) func mustCreateRolesAndEnableAuth(t *testing.T, authApplier *authApplierV3) { _, err := authApplier.UserAdd(&pb.AuthUserAddRequest{Name: userRoot, Options: &authpb.UserAddOptions{NoPassword: true}}) require.NoError(t, err) _, err = authApplier.RoleAdd(&pb.AuthRoleAddRequest{Name: roleRoot}) require.NoError(t, err) _, err = authApplier.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: userRoot, Role: roleRoot}) require.NoError(t, err) _, err = authApplier.UserAdd(&pb.AuthUserAddRequest{Name: userReadOnly, Options: &authpb.UserAddOptions{NoPassword: true}}) require.NoError(t, err) _, err = authApplier.RoleAdd(&pb.AuthRoleAddRequest{Name: roleReadOnly}) require.NoError(t, err) _, err = authApplier.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: userReadOnly, Role: roleReadOnly}) require.NoError(t, err) _, err = authApplier.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{Name: roleReadOnly, Perm: &authpb.Permission{ PermType: authpb.Permission_READ, Key: []byte(key), RangeEnd: []byte(rangeEnd), }}) require.NoError(t, err) _, err = authApplier.UserAdd(&pb.AuthUserAddRequest{Name: userWriteOnly, Options: &authpb.UserAddOptions{NoPassword: true}}) require.NoError(t, err) _, err = authApplier.RoleAdd(&pb.AuthRoleAddRequest{Name: roleWriteOnly}) require.NoError(t, err) _, err = authApplier.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: userWriteOnly, Role: roleWriteOnly}) require.NoError(t, err) _, err = authApplier.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{Name: roleWriteOnly, Perm: &authpb.Permission{ PermType: authpb.Permission_WRITE, Key: []byte(key), RangeEnd: []byte(rangeEnd), }}) require.NoError(t, err) _, err = authApplier.AuthEnable() require.NoError(t, err) } // setAuthInfo manually sets the authInfo of the applier. In reality, authInfo is filled before Apply() func setAuthInfo(authApplier *authApplierV3, userName string) { authApplier.authInfo = auth.AuthInfo{ Username: userName, Revision: authApplier.as.Revision(), } } // TestAuthApplierV3_Apply ensures Apply() calls applyFunc() when permission is granted // and returns an error when permission is denied func TestAuthApplierV3_Apply(t *testing.T) { tcs := []struct { name string request *InternalRaftRequestWrapper expectResult *Result }{ { name: "request does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{ Header: &pb.RequestHeader{}, }}, expectResult: &Result{}, }, { name: "request needs admin permission but permission denied", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{ Header: &pb.RequestHeader{ Username: userReadOnly, }, AuthEnable: &pb.AuthEnableRequest{}, }}, expectResult: &Result{ Err: auth.ErrPermissionDenied, }, }, { name: "request needs admin permission and permitted", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{ Header: &pb.RequestHeader{ Username: userRoot, }, AuthEnable: &pb.AuthEnableRequest{}, }}, expectResult: &Result{}, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { result := authApplier.Apply(tc.request, membership.ApplyBoth, dummyApplyFunc) require.Equalf(t, result, tc.expectResult, "Apply: got %v, expect: %v", result, tc.expectResult) }) } } // TestAuthApplierV3_AdminPermission ensures the admin permission is checked for certain // operations func TestAuthApplierV3_AdminPermission(t *testing.T) { tcs := []struct { name string request *InternalRaftRequestWrapper adminPermissionNeeded bool }{ { name: "Range does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Range: &pb.RangeRequest{}}}, adminPermissionNeeded: false, }, { name: "Put does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Put: &pb.PutRequest{}}}, adminPermissionNeeded: false, }, { name: "DeleteRange does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{DeleteRange: &pb.DeleteRangeRequest{}}}, adminPermissionNeeded: false, }, { name: "Txn does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Txn: &pb.TxnRequest{}}}, adminPermissionNeeded: false, }, { name: "Compaction does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Compaction: &pb.CompactionRequest{}}}, adminPermissionNeeded: false, }, { name: "LeaseGrant does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{LeaseGrant: &pb.LeaseGrantRequest{}}}, adminPermissionNeeded: false, }, { name: "LeaseRevoke does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{LeaseRevoke: &pb.LeaseRevokeRequest{}}}, adminPermissionNeeded: false, }, { name: "Alarm does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Alarm: &pb.AlarmRequest{}}}, adminPermissionNeeded: false, }, { name: "LeaseCheckpoint does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{LeaseCheckpoint: &pb.LeaseCheckpointRequest{}}}, adminPermissionNeeded: false, }, { name: "Authenticate does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{Authenticate: &pb.InternalAuthenticateRequest{}}}, adminPermissionNeeded: false, }, { name: "ClusterVersionSet does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{ClusterVersionSet: &membershippb.ClusterVersionSetRequest{}}}, adminPermissionNeeded: false, }, { name: "ClusterMemberAttrSet does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{ClusterMemberAttrSet: &membershippb.ClusterMemberAttrSetRequest{}}}, adminPermissionNeeded: false, }, { name: "DowngradeInfoSet does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{DowngradeInfoSet: &membershippb.DowngradeInfoSetRequest{}}}, adminPermissionNeeded: false, }, { name: "AuthUserGet does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserGet: &pb.AuthUserGetRequest{}}}, adminPermissionNeeded: false, }, { name: "AuthRoleGet does not need admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleGet: &pb.AuthRoleGetRequest{}}}, adminPermissionNeeded: false, }, { name: "AuthEnable needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthEnable: &pb.AuthEnableRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthDisable needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthDisable: &pb.AuthDisableRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserAdd needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserAdd: &pb.AuthUserAddRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserDelete needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserDelete: &pb.AuthUserDeleteRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserChangePassword needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserChangePassword: &pb.AuthUserChangePasswordRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserGrantRole needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserGrantRole: &pb.AuthUserGrantRoleRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserRevokeRole needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserRevokeRole: &pb.AuthUserRevokeRoleRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthUserList needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthUserList: &pb.AuthUserListRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthRoleList needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleList: &pb.AuthRoleListRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthRoleAdd needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleAdd: &pb.AuthRoleAddRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthRoleDelete needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleDelete: &pb.AuthRoleDeleteRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthRoleGrantPermission needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleGrantPermission: &pb.AuthRoleGrantPermissionRequest{}}}, adminPermissionNeeded: true, }, { name: "AuthRoleRevokePermission needs admin permission", request: &InternalRaftRequestWrapper{InternalRaftRequest: &pb.InternalRaftRequest{AuthRoleRevokePermission: &pb.AuthRoleRevokePermissionRequest{}}}, adminPermissionNeeded: true, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { if tc.adminPermissionNeeded { tc.request.Header = &pb.RequestHeader{Username: userReadOnly} } result := authApplier.Apply(tc.request, membership.ApplyBoth, dummyApplyFunc) require.Equalf(t, errors.Is(result.Err, auth.ErrPermissionDenied), tc.adminPermissionNeeded, "Admin permission needed") }) } } // TestAuthApplierV3_Put verifies only users with write permissions in the key range can put func TestAuthApplierV3_Put(t *testing.T) { tcs := []struct { name string userName string request *pb.PutRequest expectError error }{ { name: "put permission denied", userName: userReadOnly, request: &pb.PutRequest{}, expectError: auth.ErrPermissionDenied, }, { name: "prevKv is set, but user does not have read permission", userName: userWriteOnly, request: &pb.PutRequest{ Key: []byte(key), Value: []byte("1"), PrevKv: true, }, expectError: auth.ErrPermissionDenied, }, { name: "put success", userName: userWriteOnly, request: &pb.PutRequest{ Key: []byte(key), Value: []byte("1"), }, expectError: nil, }, { name: "put success with PrevKv set", userName: userRoot, request: &pb.PutRequest{ Key: []byte(key), Value: []byte("1"), PrevKv: true, }, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, _, err := authApplier.Put(tc.request) require.Equalf(t, tc.expectError, err, "Put returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } // TestAuthApplierV3_LeasePut verifies users cannot put with lease if the lease is attached with a key out of range func TestAuthApplierV3_LeasePut(t *testing.T) { authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) _, err := authApplier.LeaseGrant(&pb.LeaseGrantRequest{ TTL: lease.MaxLeaseTTL, ID: leaseID, }) require.NoError(t, err) // The user should be able to put the key setAuthInfo(authApplier, userWriteOnly) _, _, err = authApplier.Put(&pb.PutRequest{ Key: []byte(key), Value: []byte("1"), Lease: leaseID, }) require.NoError(t, err) // Put a key under the lease outside user's key range setAuthInfo(authApplier, userRoot) _, _, err = authApplier.Put(&pb.PutRequest{ Key: []byte(keyOutsideRange), Value: []byte("1"), Lease: leaseID, }) require.NoError(t, err) // The user should not be able to put the key anymore setAuthInfo(authApplier, userWriteOnly) _, _, err = authApplier.Put(&pb.PutRequest{ Key: []byte(key), Value: []byte("1"), Lease: leaseID, }) require.Equal(t, err, auth.ErrPermissionDenied) } // TestAuthApplierV3_Range verifies only users with read permissions can do range in the key range func TestAuthApplierV3_Range(t *testing.T) { tcs := []struct { name string userName string request *pb.RangeRequest expectError error }{ { name: "range permission denied", userName: userWriteOnly, request: &pb.RangeRequest{}, expectError: auth.ErrPermissionDenied, }, { name: "range key out of range", userName: userReadOnly, request: &pb.RangeRequest{ Key: []byte(keyOutsideRange), }, expectError: auth.ErrPermissionDenied, }, { name: "range success", userName: userReadOnly, request: &pb.RangeRequest{ Key: []byte(key), RangeEnd: []byte(rangeEnd), }, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, _, err := authApplier.Range(tc.request) require.Equalf(t, tc.expectError, err, "Range returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } // TestAuthApplierV3_DeleteRange verifies only users with write permissions can do delete range in the key range func TestAuthApplierV3_DeleteRange(t *testing.T) { tcs := []struct { name string userName string request *pb.DeleteRangeRequest expectError error }{ { name: "delete range permission denied", userName: userReadOnly, request: &pb.DeleteRangeRequest{}, expectError: auth.ErrPermissionDenied, }, { name: "delete range key out of range", userName: userWriteOnly, request: &pb.DeleteRangeRequest{ Key: []byte(keyOutsideRange), }, expectError: auth.ErrPermissionDenied, }, { name: "prevKv is set, but user does not have read permission", userName: userWriteOnly, request: &pb.DeleteRangeRequest{ Key: []byte(key), RangeEnd: []byte(rangeEnd), PrevKv: true, }, expectError: auth.ErrPermissionDenied, }, { name: "delete range success", userName: userWriteOnly, request: &pb.DeleteRangeRequest{ Key: []byte(key), RangeEnd: []byte(rangeEnd), }, expectError: nil, }, { name: "delete range success with PrevKv", userName: userRoot, request: &pb.DeleteRangeRequest{ Key: []byte(key), RangeEnd: []byte(rangeEnd), PrevKv: true, }, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, _, err := authApplier.DeleteRange(tc.request) require.Equalf(t, tc.expectError, err, "Range returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } // TestAuthApplierV3_Txn verifies txns can only be applied with proper permissions func TestAuthApplierV3_Txn(t *testing.T) { tcs := []struct { name string userName string request *pb.TxnRequest expectError error }{ { name: "txn range permission denied", userName: userWriteOnly, request: &pb.TxnRequest{ Compare: []*pb.Compare{ { Key: []byte(key), }, }, }, expectError: auth.ErrPermissionDenied, }, { name: "txn put permission denied", userName: userReadOnly, request: &pb.TxnRequest{ Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestPut{ RequestPut: &pb.PutRequest{ Key: []byte(key), }, }, }, }, }, expectError: auth.ErrPermissionDenied, }, { name: "txn success", userName: userRoot, request: &pb.TxnRequest{ Compare: []*pb.Compare{ { Key: []byte(key), }, }, Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestPut{ RequestPut: &pb.PutRequest{ Key: []byte(key), }, }, }, }, }, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, _, err := authApplier.Txn(tc.request, false) require.Equalf(t, tc.expectError, err, "Range returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } // TestAuthApplierV3_LeaseRevoke verifies user cannot revoke a lease if the lease is attached with // a key out of range by someone else func TestAuthApplierV3_LeaseRevoke(t *testing.T) { authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) _, err := authApplier.LeaseGrant(&pb.LeaseGrantRequest{ TTL: lease.MaxLeaseTTL, ID: leaseID, }) require.NoError(t, err) // The user should be able to revoke the lease setAuthInfo(authApplier, userWriteOnly) _, err = authApplier.LeaseRevoke(&pb.LeaseRevokeRequest{ ID: leaseID, }) require.NoError(t, err) _, err = authApplier.LeaseGrant(&pb.LeaseGrantRequest{ TTL: lease.MaxLeaseTTL, ID: leaseID, }) require.NoError(t, err) // Put a key under the lease outside user's key range setAuthInfo(authApplier, userRoot) _, _, err = authApplier.Put(&pb.PutRequest{ Key: []byte(keyOutsideRange), Value: []byte("1"), Lease: leaseID, }) require.NoError(t, err) // The user should not be able to revoke the lease anymore setAuthInfo(authApplier, userWriteOnly) _, err = authApplier.LeaseRevoke(&pb.LeaseRevokeRequest{ ID: leaseID, }) require.Equal(t, err, auth.ErrPermissionDenied) } // TestAuthApplierV3_UserGet verifies UserGet can only be performed by the user itself or the root func TestAuthApplierV3_UserGet(t *testing.T) { tcs := []struct { name string userName string request *pb.AuthUserGetRequest expectError error }{ { name: "UserGet permission denied with non-root role and requests other user", userName: userWriteOnly, request: &pb.AuthUserGetRequest{Name: userReadOnly}, expectError: auth.ErrPermissionDenied, }, { name: "UserGet success with non-root role but requests itself", userName: userWriteOnly, request: &pb.AuthUserGetRequest{Name: userWriteOnly}, expectError: nil, }, { name: "UserGet success with root role", userName: userRoot, request: &pb.AuthUserGetRequest{Name: userWriteOnly}, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, err := authApplier.UserGet(tc.request) require.Equalf(t, tc.expectError, err, "Range returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } // TestAuthApplierV3_RoleGet verifies RoleGet can only be performed by the user in the role itself or the root func TestAuthApplierV3_RoleGet(t *testing.T) { tcs := []struct { name string userName string request *pb.AuthRoleGetRequest expectError error }{ { name: "RoleGet permission denied with non-root role and requests other role", userName: userWriteOnly, request: &pb.AuthRoleGetRequest{Role: roleReadOnly}, expectError: auth.ErrPermissionDenied, }, { name: "RoleGet success with non-root role but requests itself", userName: userWriteOnly, request: &pb.AuthRoleGetRequest{Role: roleWriteOnly}, expectError: nil, }, { name: "RoleGet success with root role", userName: userRoot, request: &pb.AuthRoleGetRequest{Role: roleWriteOnly}, expectError: nil, }, } authApplier := defaultAuthApplierV3(t) mustCreateRolesAndEnableAuth(t, authApplier) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { setAuthInfo(authApplier, tc.userName) _, err := authApplier.RoleGet(tc.request) require.Equalf(t, tc.expectError, err, "Range returned unexpected error (or lack thereof), expected: %v, got: %v", tc.expectError, err) }) } } func TestCheckLeasePutsKeys(t *testing.T) { aa := defaultAuthApplierV3(t) require.NoErrorf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, lease.NewLease(lease.LeaseID(1), 3600)), "auth is disabled, should allow puts") mustCreateRolesAndEnableAuth(t, aa) aa.authInfo = auth.AuthInfo{Username: "root"} require.NoErrorf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, lease.NewLease(lease.LeaseID(1), 3600)), "auth is enabled, should allow puts for root") l := lease.NewLease(lease.LeaseID(1), 3600) l.SetLeaseItem(lease.LeaseItem{Key: "a"}) aa.authInfo = auth.AuthInfo{Username: "bob", Revision: 0} require.ErrorIsf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, l), auth.ErrUserEmpty, "auth is enabled, should not allow bob, non existing at rev 0") aa.authInfo = auth.AuthInfo{Username: "bob", Revision: 1} require.ErrorIsf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, l), auth.ErrAuthOldRevision, "auth is enabled, old revision") aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()} require.ErrorIsf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, l), auth.ErrPermissionDenied, "auth is enabled, bob does not have permissions, bob does not exist") _, err := aa.as.UserAdd(&pb.AuthUserAddRequest{Name: "bob", Options: &authpb.UserAddOptions{NoPassword: true}}) require.NoErrorf(t, err, "bob should be added without error") aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()} require.ErrorIsf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, l), auth.ErrPermissionDenied, "auth is enabled, bob exists yet does not have permissions") // allow bob to access "a" _, err = aa.as.RoleAdd(&pb.AuthRoleAddRequest{Name: "bobsrole"}) require.NoErrorf(t, err, "bobsrole should be added without error") _, err = aa.as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{ Name: "bobsrole", Perm: &authpb.Permission{ PermType: authpb.Permission_READWRITE, Key: []byte("a"), RangeEnd: nil, }, }) require.NoErrorf(t, err, "bobsrole should be granted permissions without error") _, err = aa.as.UserGrantRole(&pb.AuthUserGrantRoleRequest{ User: "bob", Role: "bobsrole", }) require.NoErrorf(t, err, "bob should be granted bobsrole without error") aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()} assert.NoErrorf(t, checkLeasePutsKeys(aa.as, &aa.authInfo, l), "bob should be able to access key 'a'") } func TestCheckTxnAuth(t *testing.T) { be, _ := betesting.NewDefaultTmpBackend(t) defer betesting.Close(t, be) as := setupAuth(t, be) tests := []struct { name string txnRequest *pb.TxnRequest err error }{ { name: "Out of range compare is unauthorized", txnRequest: &pb.TxnRequest{ Compare: []*pb.Compare{outOfRangeCompare}, }, err: auth.ErrPermissionDenied, }, { name: "In range compare is authorized", txnRequest: &pb.TxnRequest{ Compare: []*pb.Compare{inRangeCompare}, }, err: nil, }, { name: "Nil request range is always authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{nilRequestRange}, }, err: nil, }, { name: "Range request in range is authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestRange}, Failure: []*pb.RequestOp{inRangeRequestRange}, }, err: nil, }, { name: "Range request out of range success case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestRange}, Failure: []*pb.RequestOp{inRangeRequestRange}, }, err: auth.ErrPermissionDenied, }, { name: "Range request out of range failure case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestRange}, Failure: []*pb.RequestOp{outOfRangeRequestRange}, }, err: auth.ErrPermissionDenied, }, { name: "Nil Put request is always authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{nilRequestPut}, }, err: nil, }, { name: "Put request in range in authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestPut}, Failure: []*pb.RequestOp{inRangeRequestPut}, }, err: nil, }, { name: "Put request out of range success case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestPut}, Failure: []*pb.RequestOp{inRangeRequestPut}, }, err: auth.ErrPermissionDenied, }, { name: "Put request out of range failure case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestPut}, Failure: []*pb.RequestOp{outOfRangeRequestPut}, }, err: auth.ErrPermissionDenied, }, { name: "Nil delete request is authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{nilRequestDeleteRange}, }, err: nil, }, { name: "Delete range request in range is authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestDeleteRange}, Failure: []*pb.RequestOp{inRangeRequestDeleteRange}, }, err: nil, }, { name: "Delete range request out of range success case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestDeleteRange}, Failure: []*pb.RequestOp{inRangeRequestDeleteRange}, }, err: auth.ErrPermissionDenied, }, { name: "Delete range request out of range failure case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestDeleteRange}, Failure: []*pb.RequestOp{outOfRangeRequestDeleteRange}, }, err: auth.ErrPermissionDenied, }, { name: "Delete range request out of range and PrevKv false success case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestDeleteRangeKvFalse}, Failure: []*pb.RequestOp{inRangeRequestDeleteRange}, }, err: auth.ErrPermissionDenied, }, { name: "Delete range request out of range and PrevKv false failure case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestDeleteRange}, Failure: []*pb.RequestOp{outOfRangeRequestDeleteRangeKvFalse}, }, err: auth.ErrPermissionDenied, }, { name: "Nested txn request in range is authorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Success: []*pb.RequestOp{inRangeRequestRange, inRangeRequestPut}, Failure: []*pb.RequestOp{inRangeRequestDeleteRange}, }, }, }, }, }, err: nil, }, { name: "Nested txn request out of range success case is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestRange}, }, }, }, }, }, err: auth.ErrPermissionDenied, }, { name: "Nested txn request out of range failure case is unauthorized", txnRequest: &pb.TxnRequest{ Failure: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Failure: []*pb.RequestOp{outOfRangeRequestPut}, }, }, }, }, }, err: auth.ErrPermissionDenied, }, { name: "Nested txn request out of range delete is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestDeleteRange}, }, }, }, }, }, err: auth.ErrPermissionDenied, }, { name: "Two level nested txn request out of range delete is unauthorized", txnRequest: &pb.TxnRequest{ Success: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Failure: []*pb.RequestOp{ { Request: &pb.RequestOp_RequestTxn{ RequestTxn: &pb.TxnRequest{ Success: []*pb.RequestOp{outOfRangeRequestDeleteRange}, }, }, }, }, }, }, }, }, }, err: auth.ErrPermissionDenied, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := CheckTxnAuth(as, &auth.AuthInfo{Username: "foo", Revision: 8}, &lease.FakeLessor{}, tt.txnRequest) assert.Equal(t, tt.err, err) }) } } // CheckTxnAuth test setup. func setupAuth(t *testing.T, be backend.Backend) auth.AuthStore { lg := zaptest.NewLogger(t) simpleTokenTTLDefault := 300 * time.Second tokenTypeSimple := "simple" dummyIndexWaiter1 := func(index uint64) <-chan struct{} { ch := make(chan struct{}, 1) go func() { ch <- struct{}{} }() return ch } tp, _ := auth.NewTokenProvider(zaptest.NewLogger(t), tokenTypeSimple, dummyIndexWaiter1, simpleTokenTTLDefault) as := auth.NewAuthStore(lg, schema.NewAuthBackend(lg, be), tp, 4) // create "root" user and "foo" user with limited range _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "root"}) require.NoError(t, err) _, err = as.RoleAdd(&pb.AuthRoleAddRequest{Name: "rw"}) require.NoError(t, err) _, err = as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{ Name: "rw", Perm: &authpb.Permission{ PermType: authpb.Permission_READWRITE, Key: []byte("foo"), RangeEnd: []byte("zoo"), }, }) require.NoError(t, err) _, err = as.UserAdd(&pb.AuthUserAddRequest{Name: "root", Password: "foo"}) require.NoError(t, err) _, err = as.UserAdd(&pb.AuthUserAddRequest{Name: "foo", Password: "foo"}) require.NoError(t, err) _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "root", Role: "root"}) require.NoError(t, err) _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "rw"}) require.NoError(t, err) err = as.AuthEnable() require.NoError(t, err) return as } // CheckTxnAuth variables setup. var ( inRangeCompare = &pb.Compare{ Key: []byte("foo"), RangeEnd: []byte("zoo"), } outOfRangeCompare = &pb.Compare{ Key: []byte("boo"), RangeEnd: []byte("zoo"), } nilRequestPut = &pb.RequestOp{ Request: &pb.RequestOp_RequestPut{ RequestPut: nil, }, } inRangeRequestPut = &pb.RequestOp{ Request: &pb.RequestOp_RequestPut{ RequestPut: &pb.PutRequest{ Key: []byte("foo"), }, }, } outOfRangeRequestPut = &pb.RequestOp{ Request: &pb.RequestOp_RequestPut{ RequestPut: &pb.PutRequest{ Key: []byte("boo"), }, }, } nilRequestRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestRange{ RequestRange: nil, }, } inRangeRequestRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestRange{ RequestRange: &pb.RangeRequest{ Key: []byte("foo"), RangeEnd: []byte("zoo"), }, }, } outOfRangeRequestRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestRange{ RequestRange: &pb.RangeRequest{ Key: []byte("boo"), RangeEnd: []byte("zoo"), }, }, } nilRequestDeleteRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestDeleteRange{ RequestDeleteRange: nil, }, } inRangeRequestDeleteRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestDeleteRange{ RequestDeleteRange: &pb.DeleteRangeRequest{ Key: []byte("foo"), RangeEnd: []byte("zoo"), PrevKv: true, }, }, } outOfRangeRequestDeleteRange = &pb.RequestOp{ Request: &pb.RequestOp_RequestDeleteRange{ RequestDeleteRange: &pb.DeleteRangeRequest{ Key: []byte("boo"), RangeEnd: []byte("zoo"), PrevKv: true, }, }, } outOfRangeRequestDeleteRangeKvFalse = &pb.RequestOp{ Request: &pb.RequestOp_RequestDeleteRange{ RequestDeleteRange: &pb.DeleteRangeRequest{ Key: []byte("boo"), RangeEnd: []byte("zoo"), PrevKv: false, }, }, } )