mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
842 lines
26 KiB
Go
842 lines
26 KiB
Go
// 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 (
|
|
"context"
|
|
"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"
|
|
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(_ context.Context, _ *pb.InternalRaftRequest, _ 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(
|
|
lg,
|
|
kv,
|
|
alarmStore,
|
|
authStore,
|
|
lessor,
|
|
cluster,
|
|
&fakeRaftStatusGetter{},
|
|
&fakeSnapshotServer{},
|
|
consistentIndex,
|
|
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.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.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 *pb.InternalRaftRequest
|
|
expectResult *Result
|
|
}{
|
|
{
|
|
name: "request does not need admin permission",
|
|
request: &pb.InternalRaftRequest{
|
|
Header: &pb.RequestHeader{},
|
|
},
|
|
expectResult: &Result{},
|
|
},
|
|
{
|
|
name: "request needs admin permission but permission denied",
|
|
request: &pb.InternalRaftRequest{
|
|
Header: &pb.RequestHeader{
|
|
Username: userReadOnly,
|
|
},
|
|
AuthEnable: &pb.AuthEnableRequest{},
|
|
},
|
|
expectResult: &Result{
|
|
Err: auth.ErrPermissionDenied,
|
|
},
|
|
},
|
|
{
|
|
name: "request needs admin permission and permitted",
|
|
request: &pb.InternalRaftRequest{
|
|
Header: &pb.RequestHeader{
|
|
Username: userRoot,
|
|
},
|
|
AuthEnable: &pb.AuthEnableRequest{},
|
|
},
|
|
expectResult: &Result{},
|
|
},
|
|
}
|
|
|
|
authApplier := defaultAuthApplierV3(t)
|
|
mustCreateRolesAndEnableAuth(t, authApplier)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := authApplier.Apply(ctx, tc.request, false, 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 *pb.InternalRaftRequest
|
|
adminPermissionNeeded bool
|
|
}{
|
|
{
|
|
name: "Range does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Range: &pb.RangeRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "Put does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Put: &pb.PutRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "DeleteRange does not need admin permission",
|
|
request: &pb.InternalRaftRequest{DeleteRange: &pb.DeleteRangeRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "Txn does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Txn: &pb.TxnRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "Compaction does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Compaction: &pb.CompactionRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "LeaseGrant does not need admin permission",
|
|
request: &pb.InternalRaftRequest{LeaseGrant: &pb.LeaseGrantRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "LeaseRevoke does not need admin permission",
|
|
request: &pb.InternalRaftRequest{LeaseRevoke: &pb.LeaseRevokeRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "Alarm does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Alarm: &pb.AlarmRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "LeaseCheckpoint does not need admin permission",
|
|
request: &pb.InternalRaftRequest{LeaseCheckpoint: &pb.LeaseCheckpointRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "Authenticate does not need admin permission",
|
|
request: &pb.InternalRaftRequest{Authenticate: &pb.InternalAuthenticateRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "ClusterVersionSet does not need admin permission",
|
|
request: &pb.InternalRaftRequest{ClusterVersionSet: &membershippb.ClusterVersionSetRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "ClusterMemberAttrSet does not need admin permission",
|
|
request: &pb.InternalRaftRequest{ClusterMemberAttrSet: &membershippb.ClusterMemberAttrSetRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "DowngradeInfoSet does not need admin permission",
|
|
request: &pb.InternalRaftRequest{DowngradeInfoSet: &membershippb.DowngradeInfoSetRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "AuthUserGet does not need admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserGet: &pb.AuthUserGetRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "AuthRoleGet does not need admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleGet: &pb.AuthRoleGetRequest{}},
|
|
adminPermissionNeeded: false,
|
|
},
|
|
{
|
|
name: "AuthEnable needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthEnable: &pb.AuthEnableRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthDisable needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthDisable: &pb.AuthDisableRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthStatus needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthStatus: &pb.AuthStatusRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserAdd needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserAdd: &pb.AuthUserAddRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserDelete needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserDelete: &pb.AuthUserDeleteRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserChangePassword needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserChangePassword: &pb.AuthUserChangePasswordRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserGrantRole needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserGrantRole: &pb.AuthUserGrantRoleRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserRevokeRole needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserRevokeRole: &pb.AuthUserRevokeRoleRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthUserList needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthUserList: &pb.AuthUserListRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthRoleList needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleList: &pb.AuthRoleListRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthRoleAdd needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleAdd: &pb.AuthRoleAddRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthRoleDelete needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleDelete: &pb.AuthRoleDeleteRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthRoleGrantPermission needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleGrantPermission: &pb.AuthRoleGrantPermissionRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
{
|
|
name: "AuthRoleRevokePermission needs admin permission",
|
|
request: &pb.InternalRaftRequest{AuthRoleRevokePermission: &pb.AuthRoleRevokePermissionRequest{}},
|
|
adminPermissionNeeded: true,
|
|
},
|
|
}
|
|
authApplier := defaultAuthApplierV3(t)
|
|
mustCreateRolesAndEnableAuth(t, authApplier)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
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(ctx, tc.request, false, dummyApplyFunc)
|
|
require.Equal(t, result.Err == auth.ErrPermissionDenied, tc.adminPermissionNeeded,
|
|
"Admin permission needed: got %v, expect: %v", result.Err == auth.ErrPermissionDenied, tc.adminPermissionNeeded)
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setAuthInfo(authApplier, tc.userName)
|
|
_, _, err := authApplier.Put(ctx, nil, 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)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
_, 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(ctx, nil, &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(ctx, nil, &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(ctx, nil, &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)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setAuthInfo(authApplier, tc.userName)
|
|
_, err := authApplier.Range(ctx, nil, 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(nil, 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)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setAuthInfo(authApplier, tc.userName)
|
|
_, _, err := authApplier.Txn(ctx, tc.request)
|
|
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)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
_, 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(ctx, nil, &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)
|
|
assert.NoError(t, aa.checkLeasePutsKeys(lease.NewLease(lease.LeaseID(1), 3600)), "auth is disabled, should allow puts")
|
|
mustCreateRolesAndEnableAuth(t, aa)
|
|
aa.authInfo = auth.AuthInfo{Username: "root"}
|
|
assert.NoError(t, aa.checkLeasePutsKeys(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}
|
|
assert.ErrorIs(t, aa.checkLeasePutsKeys(l), auth.ErrUserEmpty, "auth is enabled, should not allow bob, non existing at rev 0")
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: 1}
|
|
assert.ErrorIs(t, aa.checkLeasePutsKeys(l), auth.ErrAuthOldRevision, "auth is enabled, old revision")
|
|
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()}
|
|
assert.ErrorIs(t, aa.checkLeasePutsKeys(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}})
|
|
assert.NoError(t, err, "bob should be added without error")
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()}
|
|
assert.ErrorIs(t, aa.checkLeasePutsKeys(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"})
|
|
assert.NoError(t, err, "bobsrole should be added without error")
|
|
_, err = aa.as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{
|
|
Name: "bobsrole",
|
|
Perm: &authpb.Permission{
|
|
PermType: authpb.READWRITE,
|
|
Key: []byte("a"),
|
|
RangeEnd: nil,
|
|
},
|
|
})
|
|
assert.NoError(t, err, "bobsrole should be granted permissions without error")
|
|
_, err = aa.as.UserGrantRole(&pb.AuthUserGrantRoleRequest{
|
|
User: "bob",
|
|
Role: "bobsrole",
|
|
})
|
|
assert.NoError(t, err, "bob should be granted bobsrole without error")
|
|
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()}
|
|
assert.NoError(t, aa.checkLeasePutsKeys(l), "bob should be able to access key 'a'")
|
|
|
|
}
|