mirror of
https://github.com/etcd-io/etcd.git
synced 2026-02-05 15:46:51 +01:00
Any client managing their own JWTs outside of etcd, need to track the auth revision to create another token. One possible solution is to loosen the access control on AuthStatus so that you can always get the latest authRevision no matter whether or not you have a valid token. Signed-off-by: Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>
823 lines
26 KiB
Go
823 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 (
|
|
"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"
|
|
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(_ *pb.InternalRaftRequest, 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.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)
|
|
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 *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: "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)
|
|
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)
|
|
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, aa.checkLeasePutsKeys(lease.NewLease(lease.LeaseID(1), 3600)), "auth is disabled, should allow puts")
|
|
mustCreateRolesAndEnableAuth(t, aa)
|
|
aa.authInfo = auth.AuthInfo{Username: "root"}
|
|
require.NoErrorf(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}
|
|
require.ErrorIsf(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}
|
|
require.ErrorIsf(t, aa.checkLeasePutsKeys(l), auth.ErrAuthOldRevision, "auth is enabled, old revision")
|
|
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()}
|
|
require.ErrorIsf(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}})
|
|
require.NoErrorf(t, err, "bob should be added without error")
|
|
aa.authInfo = auth.AuthInfo{Username: "bob", Revision: aa.as.Revision()}
|
|
require.ErrorIsf(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"})
|
|
require.NoErrorf(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,
|
|
},
|
|
})
|
|
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, aa.checkLeasePutsKeys(l), "bob should be able to access key 'a'")
|
|
}
|