mirror of
https://gitee.com/milvus-io/milvus.git
synced 2026-01-07 19:31:51 +08:00
enhance: [2.4] RBAC built in privilege groups and grant v2 (#37787)
cherry-pick from master: https://github.com/milvus-io/milvus/pull/37720, https://github.com/milvus-io/milvus/pull/37785 issue: https://github.com/milvus-io/milvus/issues/37031 Signed-off-by: shaoting-huang <shaoting.huang@zilliz.com>
This commit is contained in:
parent
ff6e8e2f2b
commit
d8f1af68e9
@ -797,6 +797,30 @@ common:
|
||||
# like the old password verification when updating the credential
|
||||
superUsers:
|
||||
defaultRootPassword: Milvus # default password for root user
|
||||
rbac:
|
||||
overrideBuiltInPrivilgeGroups:
|
||||
enabled: false # Whether to override build-in privilege groups
|
||||
cluster:
|
||||
readonly:
|
||||
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups # Cluster level readonly privileges
|
||||
readwrite:
|
||||
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,FlushAll,TransferNode,TransferReplica,UpdateResourceGroups # Cluster level readwrite privileges
|
||||
admin:
|
||||
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,FlushAll,TransferNode,TransferReplica,UpdateResourceGroups,BackupRBAC,RestoreRBAC,CreateDatabase,DropDatabase,CreateOwnership,DropOwnership,ManageOwnership,CreateResourceGroup,DropResourceGroup,UpdateUser # Cluster level admin privileges
|
||||
database:
|
||||
readonly:
|
||||
privileges: ShowCollections,DescribeDatabase # Database level readonly privileges
|
||||
readwrite:
|
||||
privileges: ShowCollections,DescribeDatabase,AlterDatabase # Database level readwrite privileges
|
||||
admin:
|
||||
privileges: ShowCollections,DescribeDatabase,AlterDatabase,CreateCollection,DropCollection # Database level admin privileges
|
||||
collection:
|
||||
readonly:
|
||||
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases # Collection level readonly privileges
|
||||
readwrite:
|
||||
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateIndex,DropIndex,CreatePartition,DropPartition # Collection level readwrite privileges
|
||||
admin:
|
||||
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateIndex,DropIndex,CreatePartition,DropPartition,CreateAlias,DropAlias # Collection level admin privileges
|
||||
tlsMode: 0
|
||||
session:
|
||||
ttl: 30 # ttl value when session granting a lease to register service
|
||||
|
||||
2
go.mod
2
go.mod
@ -26,7 +26,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1
|
||||
github.com/minio/minio-go/v7 v7.0.73
|
||||
github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
|
||||
4
go.sum
4
go.sum
@ -608,8 +608,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu
|
||||
github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
|
||||
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
|
||||
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17 h1:ANkXdUKKpIPPQkw9pkV9ku9AEtSaPyua9XzdMTUxjCs=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1 h1:Xp4zOR85XFFtM7Eif945BeSmDf30hbdijbeNSuy92Bg=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
|
||||
github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70 h1:Z+sp64fmAOxAG7mU0dfVOXvAXlwRB0c8a96rIM5HevI=
|
||||
github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70/go.mod h1:GPETMcTZq1gLY1WA6Na5kiNAKnq8SEMMiVKUZrM3sho=
|
||||
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=
|
||||
|
||||
@ -43,6 +43,8 @@ const (
|
||||
RevokeRoleAction = "revoke_role"
|
||||
GrantPrivilegeAction = "grant_privilege"
|
||||
RevokePrivilegeAction = "revoke_privilege"
|
||||
GrantPrivilegeActionV2 = "grant_privilege_v2"
|
||||
RevokePrivilegeActionV2 = "revoke_privilege_v2"
|
||||
AlterAction = "alter"
|
||||
GetProgressAction = "get_progress" // deprecated, keep it for compatibility, use `/v2/vectordb/jobs/import/describe` instead
|
||||
AddPrivilegesToGroupAction = "add_privileges_to_group"
|
||||
|
||||
@ -131,6 +131,8 @@ func (h *HandlersV2) RegisterRoutesToV2(router gin.IRouter) {
|
||||
router.POST(RoleCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.dropRole))))
|
||||
router.POST(RoleCategory+GrantPrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.addPrivilegeToRole))))
|
||||
router.POST(RoleCategory+RevokePrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.removePrivilegeFromRole))))
|
||||
router.POST(RoleCategory+GrantPrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.grantV2))))
|
||||
router.POST(RoleCategory+RevokePrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.revokeV2))))
|
||||
|
||||
// privilege group
|
||||
router.POST(PrivilegeGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.createPrivilegeGroup))))
|
||||
@ -1702,6 +1704,37 @@ func (h *HandlersV2) operatePrivilegeToRole(ctx context.Context, c *gin.Context,
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *HandlersV2) operatePrivilegeToRoleV2(ctx context.Context, c *gin.Context, httpReq *GrantV2Req, operateType milvuspb.OperatePrivilegeType) (interface{}, error) {
|
||||
req := &milvuspb.OperatePrivilegeRequest{
|
||||
Entity: &milvuspb.GrantEntity{
|
||||
Role: &milvuspb.RoleEntity{Name: httpReq.RoleName},
|
||||
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
|
||||
ObjectName: httpReq.CollectionName,
|
||||
DbName: httpReq.DbName,
|
||||
Grantor: &milvuspb.GrantorEntity{
|
||||
Privilege: &milvuspb.PrivilegeEntity{Name: httpReq.Privilege},
|
||||
},
|
||||
},
|
||||
Type: operateType,
|
||||
Version: "v2",
|
||||
}
|
||||
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilege", func(reqCtx context.Context, req any) (interface{}, error) {
|
||||
return h.proxy.OperatePrivilege(reqCtx, req.(*milvuspb.OperatePrivilegeRequest))
|
||||
})
|
||||
if err == nil {
|
||||
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *HandlersV2) grantV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
|
||||
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Grant)
|
||||
}
|
||||
|
||||
func (h *HandlersV2) revokeV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
|
||||
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Revoke)
|
||||
}
|
||||
|
||||
func (h *HandlersV2) addPrivilegeToRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
|
||||
return h.operatePrivilegeToRole(ctx, c, anyReq.(*GrantReq), milvuspb.OperatePrivilegeType_Grant, dbName)
|
||||
}
|
||||
|
||||
@ -1106,7 +1106,7 @@ func TestMethodPost(t *testing.T) {
|
||||
mp.EXPECT().UpdateCredential(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
|
||||
mp.EXPECT().OperateUserRole(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
|
||||
mp.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
|
||||
mp.EXPECT().OperatePrivilege(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
|
||||
mp.EXPECT().OperatePrivilege(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Times(4)
|
||||
mp.EXPECT().CreateIndex(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
|
||||
mp.EXPECT().CreateIndex(mock.Anything, mock.Anything).Return(commonErrorStatus, nil).Once()
|
||||
mp.EXPECT().CreateAlias(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
|
||||
@ -1179,6 +1179,12 @@ func TestMethodPost(t *testing.T) {
|
||||
queryTestCases = append(queryTestCases, rawTestCase{
|
||||
path: versionalV2(RoleCategory, RevokePrivilegeAction),
|
||||
})
|
||||
queryTestCases = append(queryTestCases, rawTestCase{
|
||||
path: versionalV2(RoleCategory, GrantPrivilegeActionV2),
|
||||
})
|
||||
queryTestCases = append(queryTestCases, rawTestCase{
|
||||
path: versionalV2(RoleCategory, RevokePrivilegeActionV2),
|
||||
})
|
||||
queryTestCases = append(queryTestCases, rawTestCase{
|
||||
path: versionalV2(IndexCategory, CreateAction),
|
||||
})
|
||||
|
||||
@ -273,6 +273,13 @@ type PrivilegeGroupReq struct {
|
||||
Privileges []string `json:"privileges"`
|
||||
}
|
||||
|
||||
type GrantV2Req struct {
|
||||
RoleName string `json:"roleName" binding:"required"`
|
||||
DbName string `json:"dbName"`
|
||||
CollectionName string `json:"collectionName"`
|
||||
Privilege string `json:"privilege" binding:"required"`
|
||||
}
|
||||
|
||||
type GrantReq struct {
|
||||
RoleName string `json:"roleName" binding:"required"`
|
||||
ObjectType string `json:"objectType" binding:"required"`
|
||||
|
||||
@ -4734,6 +4734,61 @@ func (_c *MockProxy_OperatePrivilegeGroup_Call) RunAndReturn(run func(context.Co
|
||||
return _c
|
||||
}
|
||||
|
||||
// OperatePrivilegeV2 provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockProxy) OperatePrivilegeV2(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeV2Request) (*commonpb.Status, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 *commonpb.Status
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeV2Request) (*commonpb.Status, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeV2Request) *commonpb.Status); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*commonpb.Status)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.OperatePrivilegeV2Request) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockProxy_OperatePrivilegeV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OperatePrivilegeV2'
|
||||
type MockProxy_OperatePrivilegeV2_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// OperatePrivilegeV2 is a helper method to define mock.On call
|
||||
// - _a0 context.Context
|
||||
// - _a1 *milvuspb.OperatePrivilegeV2Request
|
||||
func (_e *MockProxy_Expecter) OperatePrivilegeV2(_a0 interface{}, _a1 interface{}) *MockProxy_OperatePrivilegeV2_Call {
|
||||
return &MockProxy_OperatePrivilegeV2_Call{Call: _e.mock.On("OperatePrivilegeV2", _a0, _a1)}
|
||||
}
|
||||
|
||||
func (_c *MockProxy_OperatePrivilegeV2_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.OperatePrivilegeV2Request)) *MockProxy_OperatePrivilegeV2_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*milvuspb.OperatePrivilegeV2Request))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockProxy_OperatePrivilegeV2_Call) Return(_a0 *commonpb.Status, _a1 error) *MockProxy_OperatePrivilegeV2_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockProxy_OperatePrivilegeV2_Call) RunAndReturn(run func(context.Context, *milvuspb.OperatePrivilegeV2Request) (*commonpb.Status, error)) *MockProxy_OperatePrivilegeV2_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// OperateUserRole provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockProxy) OperateUserRole(_a0 context.Context, _a1 *milvuspb.OperateUserRoleRequest) (*commonpb.Status, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
@ -5210,6 +5210,87 @@ func (node *Proxy) validPrivilegeParams(req *milvuspb.OperatePrivilegeRequest) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *Proxy) validOperatePrivilegeV2Params(req *milvuspb.OperatePrivilegeV2Request) error {
|
||||
if req.Role == nil {
|
||||
return fmt.Errorf("the role in the request is nil")
|
||||
}
|
||||
if err := ValidateRoleName(req.Role.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidatePrivilege(req.Grantor.Privilege.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Type != milvuspb.OperatePrivilegeType_Grant && req.Type != milvuspb.OperatePrivilegeType_Revoke {
|
||||
return fmt.Errorf("the type in the request not grant or revoke")
|
||||
}
|
||||
if err := ValidateObjectName(req.DbName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidateObjectName(req.CollectionName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *Proxy) OperatePrivilegeV2(ctx context.Context, req *milvuspb.OperatePrivilegeV2Request) (*commonpb.Status, error) {
|
||||
ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-OperatePrivilegeV2")
|
||||
defer sp.End()
|
||||
|
||||
log := log.Ctx(ctx)
|
||||
|
||||
log.Info("OperatePrivilegeV2",
|
||||
zap.Any("req", req))
|
||||
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if err := node.validOperatePrivilegeV2Params(req); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
curUser, err := GetCurUserFromContext(ctx)
|
||||
if err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
req.Grantor.User = &milvuspb.UserEntity{Name: curUser}
|
||||
request := &milvuspb.OperatePrivilegeRequest{
|
||||
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_OperatePrivilege},
|
||||
Entity: &milvuspb.GrantEntity{
|
||||
Role: req.Role,
|
||||
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
|
||||
ObjectName: req.CollectionName,
|
||||
DbName: req.DbName,
|
||||
Grantor: req.Grantor,
|
||||
},
|
||||
Type: req.Type,
|
||||
Version: "v2",
|
||||
}
|
||||
req.Grantor.User = &milvuspb.UserEntity{Name: curUser}
|
||||
result, err := node.rootCoord.OperatePrivilege(ctx, request)
|
||||
if err != nil {
|
||||
log.Warn("fail to operate privilege", zap.Error(err))
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
relatedPrivileges := util.RelatedPrivileges[util.PrivilegeNameForMetastore(req.Grantor.Privilege.Name)]
|
||||
if len(relatedPrivileges) != 0 {
|
||||
for _, relatedPrivilege := range relatedPrivileges {
|
||||
relatedReq := proto.Clone(request).(*milvuspb.OperatePrivilegeRequest)
|
||||
relatedReq.Entity.Grantor.Privilege.Name = util.PrivilegeNameForAPI(relatedPrivilege)
|
||||
result, err = node.rootCoord.OperatePrivilege(ctx, relatedReq)
|
||||
if err != nil {
|
||||
log.Warn("fail to operate related privilege", zap.String("related_privilege", relatedPrivilege), zap.Error(err))
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if !merr.Ok(result) {
|
||||
log.Warn("fail to operate related privilege", zap.String("related_privilege", relatedPrivilege), zap.Any("result", result))
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if merr.Ok(result) {
|
||||
SendReplicateMessagePack(ctx, node.replicateMsgStream, req)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (node *Proxy) OperatePrivilege(ctx context.Context, req *milvuspb.OperatePrivilegeRequest) (*commonpb.Status, error) {
|
||||
ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-OperatePrivilege")
|
||||
defer sp.End()
|
||||
@ -6399,8 +6480,11 @@ func (node *Proxy) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.Creat
|
||||
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if req.GroupName == "" {
|
||||
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
|
||||
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
|
||||
log.Warn("CreatePrivilegeGroup failed",
|
||||
zap.Error(err),
|
||||
)
|
||||
return getErrResponse(err, "CreatePrivilegeGroup", "", ""), nil
|
||||
}
|
||||
if req.Base == nil {
|
||||
req.Base = &commonpb.MsgBase{}
|
||||
@ -6428,8 +6512,11 @@ func (node *Proxy) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPri
|
||||
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if req.GroupName == "" {
|
||||
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
|
||||
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
|
||||
log.Warn("DropPrivilegeGroup failed",
|
||||
zap.Error(err),
|
||||
)
|
||||
return getErrResponse(err, "DropPrivilegeGroup", "", ""), nil
|
||||
}
|
||||
if req.Base == nil {
|
||||
req.Base = &commonpb.MsgBase{}
|
||||
@ -6486,8 +6573,11 @@ func (node *Proxy) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.Oper
|
||||
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if req.GroupName == "" {
|
||||
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
|
||||
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
|
||||
log.Warn("OperatePrivilegeGroup failed",
|
||||
zap.Error(err),
|
||||
)
|
||||
return getErrResponse(err, "OperatePrivilegeGroup", "", ""), nil
|
||||
}
|
||||
for _, priv := range req.GetPrivileges() {
|
||||
if err := ValidatePrivilege(priv.Name); err != nil {
|
||||
|
||||
@ -162,8 +162,8 @@ func PrivilegeInterceptor(ctx context.Context, req interface{}) (context.Context
|
||||
|
||||
e := getEnforcer()
|
||||
for _, roleName := range roleNames {
|
||||
permitFunc := func(resName string) (bool, error) {
|
||||
object := funcutil.PolicyForResource(dbName, objectType, resName)
|
||||
permitFunc := func(objectName string) (bool, error) {
|
||||
object := funcutil.PolicyForResource(dbName, objectType, objectName)
|
||||
isPermit, cached, version := GetPrivilegeCache(roleName, object, objectPrivilege)
|
||||
if cached {
|
||||
return isPermit, nil
|
||||
|
||||
@ -563,3 +563,45 @@ func TestPrivilegeGroup(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuiltinPrivilegeGroup(t *testing.T) {
|
||||
t.Run("ClusterAdmin", func(t *testing.T) {
|
||||
paramtable.Get().Save(Params.CommonCfg.AuthorizationEnabled.Key, "true")
|
||||
initPrivilegeGroups()
|
||||
|
||||
var err error
|
||||
ctx := GetContext(context.Background(), "fooo:123456")
|
||||
client := &MockRootCoordClientInterface{}
|
||||
queryCoord := &mocks.MockQueryCoordClient{}
|
||||
mgr := newShardClientMgr()
|
||||
|
||||
policies := []string{}
|
||||
for _, priv := range util.BuiltinPrivilegeGroups["ClusterReadOnly"] {
|
||||
objectType := util.GetObjectType(priv)
|
||||
policies = append(policies, funcutil.PolicyForPrivilege("role1", objectType, "*", util.PrivilegeNameForMetastore(priv), "default"))
|
||||
}
|
||||
client.listPolicy = func(ctx context.Context, in *internalpb.ListPolicyRequest) (*internalpb.ListPolicyResponse, error) {
|
||||
return &internalpb.ListPolicyResponse{
|
||||
Status: merr.Success(),
|
||||
PolicyInfos: policies,
|
||||
UserRoles: []string{
|
||||
funcutil.EncodeUserRoleCache("fooo", "role1"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
InitMetaCache(ctx, client, queryCoord, mgr)
|
||||
defer CleanPrivilegeCache()
|
||||
|
||||
_, err = PrivilegeInterceptor(GetContext(context.Background(), "fooo:123456"), &milvuspb.SelectUserRequest{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = PrivilegeInterceptor(GetContext(context.Background(), "fooo:123456"), &milvuspb.DescribeResourceGroupRequest{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = PrivilegeInterceptor(GetContext(context.Background(), "fooo:123456"), &milvuspb.ListResourceGroupsRequest{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = PrivilegeInterceptor(GetContext(context.Background(), "fooo:123456"), &milvuspb.CreateResourceGroupRequest{})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2700,6 +2700,8 @@ func TestProxy(t *testing.T) {
|
||||
|
||||
testProxyRole(ctx, t, proxy)
|
||||
testProxyPrivilege(ctx, t, proxy)
|
||||
testProxyOperatePrivilegeV2(ctx, t, proxy)
|
||||
assert.False(t, false, true)
|
||||
testProxyRefreshPolicyInfoCache(ctx, t, proxy)
|
||||
|
||||
// proxy unhealthy
|
||||
@ -4368,6 +4370,106 @@ func testProxyPrivilege(ctx context.Context, t *testing.T, proxy *Proxy) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func testProxyOperatePrivilegeV2(ctx context.Context, t *testing.T, proxy *Proxy) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
t.Run("Operate Privilege V2, Select Grant", func(t *testing.T) {
|
||||
defer wg.Done()
|
||||
|
||||
// GrantPrivilege
|
||||
req := &milvuspb.OperatePrivilegeV2Request{}
|
||||
resp, _ := proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.Role = &milvuspb.RoleEntity{}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.Grantor = &milvuspb.GrantorEntity{}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.Grantor.Privilege = &milvuspb.PrivilegeEntity{}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.Grantor.Privilege.Name = util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeAll.String())
|
||||
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.DbName = ""
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
req.CollectionName = ""
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, req)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq := &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoad.String())}},
|
||||
DbName: "default",
|
||||
CollectionName: "col1",
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.Equal(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq = &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String())}},
|
||||
DbName: util.AnyWord,
|
||||
CollectionName: util.AnyWord,
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.Equal(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq = &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String())}},
|
||||
DbName: "db1",
|
||||
CollectionName: util.AnyWord,
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq = &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String())}},
|
||||
DbName: "db1",
|
||||
CollectionName: util.AnyWord,
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.Equal(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq = &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String())}},
|
||||
DbName: "db1",
|
||||
CollectionName: "col1",
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
|
||||
roleReq = &milvuspb.OperatePrivilegeV2Request{
|
||||
Role: &milvuspb.RoleEntity{Name: "public"},
|
||||
Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String())}},
|
||||
DbName: "db1",
|
||||
CollectionName: util.AnyWord,
|
||||
Type: milvuspb.OperatePrivilegeType_Grant,
|
||||
}
|
||||
resp, _ = proxy.OperatePrivilegeV2(ctx, roleReq)
|
||||
assert.Equal(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func testProxyPrivilegeUnhealthy(ctx context.Context, t *testing.T, proxy *Proxy) {
|
||||
testProxyPrivilegeFail(ctx, t, proxy, "unhealthy")
|
||||
}
|
||||
|
||||
@ -155,6 +155,32 @@ func validateCollectionNameOrAlias(entity, entityType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidatePrivilegeGroupName(groupName string) error {
|
||||
if groupName == "" {
|
||||
return merr.WrapErrDatabaseNameInvalid(groupName, "privilege group name couldn't be empty")
|
||||
}
|
||||
|
||||
if len(groupName) > Params.ProxyCfg.MaxNameLength.GetAsInt() {
|
||||
return merr.WrapErrDatabaseNameInvalid(groupName,
|
||||
fmt.Sprintf("the length of a privilege group name must be less than %d characters", Params.ProxyCfg.MaxNameLength.GetAsInt()))
|
||||
}
|
||||
|
||||
firstChar := groupName[0]
|
||||
if firstChar != '_' && !isAlpha(firstChar) {
|
||||
return merr.WrapErrDatabaseNameInvalid(groupName,
|
||||
"the first character of a privilege group name must be an underscore or letter")
|
||||
}
|
||||
|
||||
for i := 1; i < len(groupName); i++ {
|
||||
c := groupName[i]
|
||||
if c != '_' && !isAlpha(c) && !isNumber(c) {
|
||||
return merr.WrapErrDatabaseNameInvalid(groupName,
|
||||
"privilege group name can only contain numbers, letters and underscores")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateResourceGroupName(entity string) error {
|
||||
if entity == "" {
|
||||
return errors.New("resource group name couldn't be empty")
|
||||
|
||||
@ -98,6 +98,7 @@ type mockMetaTable struct {
|
||||
DescribeDatabaseFunc func(ctx context.Context, dbName string) (*model.Database, error)
|
||||
CreatePrivilegeGroupFunc func(groupName string) error
|
||||
DropPrivilegeGroupFunc func(groupName string) error
|
||||
IsCustomPrivilegeGroupFunc func(groupName string) (bool, error)
|
||||
ListPrivilegeGroupsFunc func() ([]*milvuspb.PrivilegeGroupInfo, error)
|
||||
OperatePrivilegeGroupFunc func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error
|
||||
GetPrivilegeGroupRolesFunc func(groupName string) ([]*milvuspb.RoleEntity, error)
|
||||
@ -267,6 +268,10 @@ func (m mockMetaTable) ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, er
|
||||
return m.ListPrivilegeGroupsFunc()
|
||||
}
|
||||
|
||||
func (m mockMetaTable) IsCustomPrivilegeGroup(groupName string) (bool, error) {
|
||||
return m.IsCustomPrivilegeGroupFunc(groupName)
|
||||
}
|
||||
|
||||
func (m mockMetaTable) OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error {
|
||||
return m.OperatePrivilegeGroupFunc(groupName, privileges, operateType)
|
||||
}
|
||||
@ -558,6 +563,9 @@ func withInvalidMeta() Opt {
|
||||
meta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) {
|
||||
return nil, errors.New("error mock ListPrivilegeGroups")
|
||||
}
|
||||
meta.IsCustomPrivilegeGroupFunc = func(groupName string) (bool, error) {
|
||||
return false, errors.New("error mock IsCustomPrivilegeGroup")
|
||||
}
|
||||
meta.OperatePrivilegeGroupFunc = func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error {
|
||||
return errors.New("error mock OperatePrivilegeGroup")
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -601,6 +602,50 @@ func (c *Core) initPublicRolePrivilege() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) initBuiltinPrivilegeGroups() []*milvuspb.PrivilegeGroupInfo {
|
||||
// init built in privilege groups, override by config if rbac config enabled
|
||||
builtinGroups := make([]*milvuspb.PrivilegeGroupInfo, 0)
|
||||
for groupName, privileges := range util.BuiltinPrivilegeGroups {
|
||||
if Params.RbacConfig.Enabled.GetAsBool() {
|
||||
var confPrivs []string
|
||||
switch groupName {
|
||||
case "ClusterReadOnly":
|
||||
confPrivs = Params.RbacConfig.ClusterReadOnlyPrivileges.GetAsStrings()
|
||||
case "ClusterReadWrite":
|
||||
confPrivs = Params.RbacConfig.ClusterReadWritePrivileges.GetAsStrings()
|
||||
case "ClusterAdmin":
|
||||
confPrivs = Params.RbacConfig.ClusterAdminPrivileges.GetAsStrings()
|
||||
case "DatabaseReadOnly":
|
||||
confPrivs = Params.RbacConfig.DBReadOnlyPrivileges.GetAsStrings()
|
||||
case "DatabaseReadWrite":
|
||||
confPrivs = Params.RbacConfig.DBReadWritePrivileges.GetAsStrings()
|
||||
case "DatabaseAdmin":
|
||||
confPrivs = Params.RbacConfig.DBAdminPrivileges.GetAsStrings()
|
||||
case "CollectionReadOnly":
|
||||
confPrivs = Params.RbacConfig.CollectionReadOnlyPrivileges.GetAsStrings()
|
||||
case "CollectionReadWrite":
|
||||
confPrivs = Params.RbacConfig.CollectionReadWritePrivileges.GetAsStrings()
|
||||
case "CollectionAdmin":
|
||||
confPrivs = Params.RbacConfig.CollectionAdminPrivileges.GetAsStrings()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if len(confPrivs) > 0 {
|
||||
privileges = confPrivs
|
||||
}
|
||||
}
|
||||
|
||||
privs := lo.Map(privileges, func(name string, _ int) *milvuspb.PrivilegeEntity {
|
||||
return &milvuspb.PrivilegeEntity{Name: name}
|
||||
})
|
||||
builtinGroups = append(builtinGroups, &milvuspb.PrivilegeGroupInfo{
|
||||
GroupName: groupName,
|
||||
Privileges: privs,
|
||||
})
|
||||
}
|
||||
return builtinGroups
|
||||
}
|
||||
|
||||
func (c *Core) initBuiltinRoles() error {
|
||||
rolePrivilegesMap := Params.RoleCfg.Roles.GetAsRoleDetails()
|
||||
for role, privilegesJSON := range rolePrivilegesMap {
|
||||
@ -612,7 +657,7 @@ func (c *Core) initBuiltinRoles() error {
|
||||
for _, privilege := range privilegesJSON[util.RoleConfigPrivileges] {
|
||||
privilegeName := privilege[util.RoleConfigPrivilege]
|
||||
if !util.IsAnyWord(privilege[util.RoleConfigPrivilege]) {
|
||||
dbPrivName, err := c.getMetastorePrivilegeName(privilege[util.RoleConfigPrivilege])
|
||||
dbPrivName, err := c.getMetastorePrivilegeName(c.ctx, privilege[util.RoleConfigPrivilege])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get metastore privilege name for: %s", privilege[util.RoleConfigPrivilege])
|
||||
}
|
||||
@ -2491,44 +2536,72 @@ func (c *Core) isValidObject(entity *milvuspb.ObjectEntity) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) isValidGrantor(entity *milvuspb.GrantorEntity, object string) error {
|
||||
if entity == nil {
|
||||
return errors.New("the grantor entity is nil")
|
||||
}
|
||||
if entity.User == nil || entity.User.Name == "" {
|
||||
return errors.New("the user entity in the grantor entity is nil or empty")
|
||||
}
|
||||
if _, err := c.meta.SelectUser(util.DefaultTenant, &milvuspb.UserEntity{Name: entity.User.Name}, false); err != nil {
|
||||
log.Warn("fail to select the user", zap.String("username", entity.User.Name), zap.Error(err))
|
||||
return errors.New("not found the user, maybe the user isn't existed or internal system error")
|
||||
}
|
||||
if entity.Privilege == nil {
|
||||
return errors.New("the privilege entity in the grantor entity is nil")
|
||||
}
|
||||
if util.IsAnyWord(entity.Privilege.Name) {
|
||||
func (c *Core) isValidPrivilege(ctx context.Context, privilegeName string, object string) error {
|
||||
if util.IsAnyWord(privilegeName) {
|
||||
return nil
|
||||
}
|
||||
customPrivGroup, err := c.meta.IsCustomPrivilegeGroup(privilegeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if customPrivGroup {
|
||||
return fmt.Errorf("can not operate the custom privilege group [%s]", privilegeName)
|
||||
}
|
||||
if lo.Contains(lo.Keys(util.BuiltinPrivilegeGroups), privilegeName) {
|
||||
return fmt.Errorf("can not operate the built-in privilege group [%s]", privilegeName)
|
||||
}
|
||||
// check object privileges for built-in privileges
|
||||
if util.IsPrivilegeNameDefined(entity.Privilege.Name) {
|
||||
if util.IsPrivilegeNameDefined(privilegeName) {
|
||||
privileges, ok := util.ObjectPrivileges[object]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found the object type[name: %s], supported the object types: %v", object, lo.Keys(commonpb.ObjectType_value))
|
||||
}
|
||||
for _, privilege := range privileges {
|
||||
if privilege == entity.Privilege.Name {
|
||||
if privilege == privilegeName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if it is a custom privilege group
|
||||
customPrivGroup, err := c.meta.IsCustomPrivilegeGroup(entity.Privilege.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if customPrivGroup {
|
||||
return fmt.Errorf("not found the privilege name[%s] in object[%s]", privilegeName, object)
|
||||
}
|
||||
|
||||
func (c *Core) isValidPrivilegeV2(ctx context.Context, privilegeName, dbName, collectionName string) error {
|
||||
if util.IsAnyWord(privilegeName) {
|
||||
return nil
|
||||
}
|
||||
var privilegeLevel string
|
||||
for group, privileges := range util.BuiltinPrivilegeGroups {
|
||||
if privilegeName == group || lo.Contains(privileges, privilegeName) {
|
||||
privilegeLevel = group
|
||||
break
|
||||
}
|
||||
}
|
||||
if privilegeLevel == "" {
|
||||
customPrivGroup, err := c.meta.IsCustomPrivilegeGroup(privilegeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if customPrivGroup {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("not found the privilege name[%s] in the custom privilege groups", privilegeName)
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(privilegeLevel, milvuspb.PrivilegeLevel_Cluster.String()):
|
||||
if !util.IsAnyWord(dbName) || !util.IsAnyWord(collectionName) {
|
||||
return fmt.Errorf("dbName and collectionName should be * for the cluster level privilege: %s", privilegeName)
|
||||
}
|
||||
return nil
|
||||
case strings.HasPrefix(privilegeLevel, milvuspb.PrivilegeLevel_Database.String()):
|
||||
if collectionName != "" && collectionName != util.AnyWord {
|
||||
return fmt.Errorf("collectionName should be empty or * for the database level privilege: %s", privilegeName)
|
||||
}
|
||||
return nil
|
||||
case strings.HasPrefix(privilegeLevel, milvuspb.PrivilegeLevel_Collection.String()):
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("not found the privilege name[%s] in object[%s]", entity.Privilege.Name, object)
|
||||
}
|
||||
|
||||
// OperatePrivilege operate the privilege, including grant and revoke
|
||||
@ -2545,50 +2618,42 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
|
||||
ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in))
|
||||
ctxLog.Debug(method)
|
||||
|
||||
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
|
||||
return merr.Status(err), nil
|
||||
}
|
||||
if in.Type != milvuspb.OperatePrivilegeType_Grant && in.Type != milvuspb.OperatePrivilegeType_Revoke {
|
||||
errMsg := fmt.Sprintf("invalid operate privilege type, current type: %s, valid value: [%s, %s]", in.Type, milvuspb.OperatePrivilegeType_Grant, milvuspb.OperatePrivilegeType_Revoke)
|
||||
ctxLog.Warn(errMsg)
|
||||
return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
if in.Entity == nil {
|
||||
errMsg := "the grant entity in the request is nil"
|
||||
ctxLog.Error(errMsg)
|
||||
return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
if err := c.isValidObject(in.Entity.Object); err != nil {
|
||||
ctxLog.Warn("", zap.Error(err))
|
||||
if err := c.operatePrivilegeCommonCheck(ctx, in); err != nil {
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
if err := c.isValidRole(in.Entity.Role); err != nil {
|
||||
ctxLog.Warn("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
if err := c.isValidGrantor(in.Entity.Grantor, in.Entity.Object.Name); err != nil {
|
||||
ctxLog.Error("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
|
||||
switch in.Version {
|
||||
case "v2":
|
||||
if err := c.isValidPrivilegeV2(ctx, in.Entity.Grantor.Privilege.Name,
|
||||
in.Entity.DbName, in.Entity.ObjectName); err != nil {
|
||||
ctxLog.Error("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
default:
|
||||
if err := c.isValidPrivilege(ctx, in.Entity.Grantor.Privilege.Name, in.Entity.Object.Name); err != nil {
|
||||
ctxLog.Error("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
// set up object name if it is global object type and not built in privilege group
|
||||
if in.Entity.Object.Name == commonpb.ObjectType_Global.String() && !lo.Contains(lo.Keys(util.BuiltinPrivilegeGroups), in.Entity.Grantor.Privilege.Name) {
|
||||
in.Entity.ObjectName = util.AnyWord
|
||||
}
|
||||
}
|
||||
|
||||
// set up privilege name for metastore
|
||||
privName := in.Entity.Grantor.Privilege.Name
|
||||
ctxLog.Debug("before PrivilegeNameForMetastore", zap.String("privilege", privName))
|
||||
if !util.IsAnyWord(privName) {
|
||||
dbPrivName, err := c.getMetastorePrivilegeName(privName)
|
||||
if err != nil {
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
in.Entity.Grantor.Privilege.Name = dbPrivName
|
||||
}
|
||||
ctxLog.Debug("after PrivilegeNameForMetastore", zap.String("privilege", privName))
|
||||
|
||||
if in.Entity.Object.Name == commonpb.ObjectType_Global.String() {
|
||||
in.Entity.ObjectName = util.AnyWord
|
||||
}
|
||||
|
||||
redoTask := newBaseRedoTask(c.stepExecutor)
|
||||
redoTask.AddSyncStep(NewSimpleStep("operate privilege meta data", func(ctx context.Context) ([]nestedStep, error) {
|
||||
if !util.IsAnyWord(privName) {
|
||||
// set up privilege name for metastore
|
||||
dbPrivName, err := c.getMetastorePrivilegeName(ctx, privName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in.Entity.Grantor.Privilege.Name = dbPrivName
|
||||
}
|
||||
|
||||
err := c.meta.OperatePrivilege(util.DefaultTenant, in.Entity, in.Type)
|
||||
if err != nil && !common.IsIgnorableError(err) {
|
||||
log.Warn("fail to operate the privilege", zap.Any("in", in), zap.Error(err))
|
||||
@ -2597,6 +2662,8 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
|
||||
return nil, nil
|
||||
}))
|
||||
redoTask.AddAsyncStep(NewSimpleStep("operate privilege cache", func(ctx context.Context) ([]nestedStep, error) {
|
||||
// set back to expand privilege group
|
||||
in.Entity.Grantor.Privilege.Name = privName
|
||||
var opType int32
|
||||
switch in.Type {
|
||||
case milvuspb.OperatePrivilegeType_Grant:
|
||||
@ -2607,9 +2674,23 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
|
||||
log.Warn("invalid operate type for the OperatePrivilege api", zap.Any("in", in))
|
||||
return nil, nil
|
||||
}
|
||||
grants := []*milvuspb.GrantEntity{in.Entity}
|
||||
|
||||
allGroups, err := c.meta.ListPrivilegeGroups()
|
||||
allGroups = append(allGroups, c.initBuiltinPrivilegeGroups()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groups := lo.SliceToMap(allGroups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) {
|
||||
return group.GroupName, group.Privileges
|
||||
})
|
||||
expandGrants, err := c.expandPrivilegeGroups(grants, groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{
|
||||
OpType: opType,
|
||||
OpKey: funcutil.PolicyForPrivilege(in.Entity.Role.Name, in.Entity.Object.Name, in.Entity.ObjectName, in.Entity.Grantor.Privilege.Name, in.Entity.DbName),
|
||||
OpKey: funcutil.PolicyForPrivileges(expandGrants),
|
||||
}); err != nil {
|
||||
log.Warn("fail to refresh policy info cache", zap.Any("in", in), zap.Error(err))
|
||||
return nil, err
|
||||
@ -2630,7 +2711,42 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
|
||||
return merr.Success(), nil
|
||||
}
|
||||
|
||||
func (c *Core) getMetastorePrivilegeName(privName string) (string, error) {
|
||||
func (c *Core) operatePrivilegeCommonCheck(ctx context.Context, in *milvuspb.OperatePrivilegeRequest) error {
|
||||
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Type != milvuspb.OperatePrivilegeType_Grant && in.Type != milvuspb.OperatePrivilegeType_Revoke {
|
||||
errMsg := fmt.Sprintf("invalid operate privilege type, current type: %s, valid value: [%s, %s]", in.Type, milvuspb.OperatePrivilegeType_Grant, milvuspb.OperatePrivilegeType_Revoke)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if in.Entity == nil {
|
||||
errMsg := "the grant entity in the request is nil"
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if err := c.isValidObject(in.Entity.Object); err != nil {
|
||||
return errors.New("the object entity in the request is nil or invalid")
|
||||
}
|
||||
if err := c.isValidRole(in.Entity.Role); err != nil {
|
||||
return err
|
||||
}
|
||||
entity := in.Entity.Grantor
|
||||
if entity == nil {
|
||||
return errors.New("the grantor entity is nil")
|
||||
}
|
||||
if entity.User == nil || entity.User.Name == "" {
|
||||
return errors.New("the user entity in the grantor entity is nil or empty")
|
||||
}
|
||||
if _, err := c.meta.SelectUser(util.DefaultTenant, &milvuspb.UserEntity{Name: entity.User.Name}, false); err != nil {
|
||||
log.Warn("fail to select the user", zap.String("username", entity.User.Name), zap.Error(err))
|
||||
return errors.New("not found the user, maybe the user isn't existed or internal system error")
|
||||
}
|
||||
if entity.Privilege == nil {
|
||||
return errors.New("the privilege entity in the grantor entity is nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) getMetastorePrivilegeName(ctx context.Context, privName string) (string, error) {
|
||||
// if it is built-in privilege, return the privilege name directly
|
||||
if util.IsPrivilegeNameDefined(privName) {
|
||||
return util.PrivilegeNameForMetastore(privName), nil
|
||||
@ -3026,6 +3142,18 @@ func (c *Core) ListPrivilegeGroups(ctx context.Context, in *milvuspb.ListPrivile
|
||||
ctxLog.Debug(method + " success")
|
||||
metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc()
|
||||
metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds()))
|
||||
|
||||
// append built in privilege groups
|
||||
for groupName, privileges := range util.BuiltinPrivilegeGroups {
|
||||
privGroups = append(privGroups, &milvuspb.PrivilegeGroupInfo{
|
||||
GroupName: groupName,
|
||||
Privileges: lo.Map(privileges, func(p string, _ int) *milvuspb.PrivilegeEntity {
|
||||
return &milvuspb.PrivilegeEntity{
|
||||
Name: p,
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
return &milvuspb.ListPrivilegeGroupsResponse{
|
||||
Status: merr.Success(),
|
||||
PrivilegeGroups: privGroups,
|
||||
@ -3072,6 +3200,14 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr
|
||||
newGroups[k] = lo.UniqBy(newPrivs, func(p *milvuspb.PrivilegeEntity) string {
|
||||
return p.Name
|
||||
})
|
||||
|
||||
// check if privileges are the same object type
|
||||
objectTypes := lo.SliceToMap(newPrivs, func(p *milvuspb.PrivilegeEntity) (string, struct{}) {
|
||||
return util.GetObjectType(p.Name), struct{}{}
|
||||
})
|
||||
if len(objectTypes) > 1 {
|
||||
return nil, errors.New("privileges are not the same object type")
|
||||
}
|
||||
case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup:
|
||||
newPrivs, _ := lo.Difference(v, in.Privileges)
|
||||
newGroups[k] = newPrivs
|
||||
@ -3083,11 +3219,11 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr
|
||||
rolesToRevoke := []*milvuspb.GrantEntity{}
|
||||
rolesToGrant := []*milvuspb.GrantEntity{}
|
||||
compareGrants := func(a, b *milvuspb.GrantEntity) bool {
|
||||
return a.Role.GetName() == b.Role.GetName() &&
|
||||
a.Object.GetName() == b.Object.GetName() &&
|
||||
return a.Role.Name == b.Role.Name &&
|
||||
a.Object.Name == b.Object.Name &&
|
||||
a.ObjectName == b.ObjectName &&
|
||||
a.Grantor.GetUser().GetName() == b.Grantor.GetUser().GetName() &&
|
||||
a.Grantor.GetPrivilege().GetName() == b.Grantor.GetPrivilege().GetName() &&
|
||||
a.Grantor.User.Name == b.Grantor.User.Name &&
|
||||
a.Grantor.Privilege.Name == b.Grantor.Privilege.Name &&
|
||||
a.DbName == b.DbName
|
||||
}
|
||||
for _, role := range roles {
|
||||
@ -3098,8 +3234,14 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currGrants := c.expandPrivilegeGroups(grants, currGroups)
|
||||
newGrants := c.expandPrivilegeGroups(grants, newGroups)
|
||||
currGrants, err := c.expandPrivilegeGroups(grants, currGroups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newGrants, err := c.expandPrivilegeGroups(grants, newGroups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toRevoke := lo.Filter(currGrants, func(item *milvuspb.GrantEntity, _ int) bool {
|
||||
return !lo.ContainsBy(newGrants, func(newItem *milvuspb.GrantEntity) bool {
|
||||
@ -3163,28 +3305,50 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr
|
||||
return merr.Success(), nil
|
||||
}
|
||||
|
||||
func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[string][]*milvuspb.PrivilegeEntity) []*milvuspb.GrantEntity {
|
||||
func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[string][]*milvuspb.PrivilegeEntity) ([]*milvuspb.GrantEntity, error) {
|
||||
newGrants := []*milvuspb.GrantEntity{}
|
||||
createGrantEntity := func(grant *milvuspb.GrantEntity, privilegeName string) (*milvuspb.GrantEntity, error) {
|
||||
metaName, err := c.getMetastorePrivilegeName(c.ctx, privilegeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objectType := util.GetObjectType(privilegeName); objectType != "" {
|
||||
grant.Object.Name = objectType
|
||||
}
|
||||
return &milvuspb.GrantEntity{
|
||||
Role: grant.Role,
|
||||
Object: grant.Object,
|
||||
ObjectName: grant.ObjectName,
|
||||
Grantor: &milvuspb.GrantorEntity{
|
||||
User: grant.Grantor.User,
|
||||
Privilege: &milvuspb.PrivilegeEntity{
|
||||
Name: metaName,
|
||||
},
|
||||
},
|
||||
DbName: grant.DbName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
for _, grant := range grants {
|
||||
if groups[grant.Grantor.Privilege.Name] == nil {
|
||||
newGrants = append(newGrants, grant)
|
||||
privName := grant.Grantor.Privilege.Name
|
||||
if privGroup, exists := groups[privName]; !exists {
|
||||
newGrant, err := createGrantEntity(grant, privName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newGrants = append(newGrants, newGrant)
|
||||
} else {
|
||||
for _, priv := range groups[grant.Grantor.Privilege.Name] {
|
||||
newGrants = append(newGrants, &milvuspb.GrantEntity{
|
||||
Role: grant.Role,
|
||||
Object: grant.Object,
|
||||
ObjectName: grant.ObjectName,
|
||||
Grantor: &milvuspb.GrantorEntity{
|
||||
User: grant.Grantor.User,
|
||||
Privilege: priv,
|
||||
},
|
||||
DbName: grant.DbName,
|
||||
})
|
||||
for _, priv := range privGroup {
|
||||
newGrant, err := createGrantEntity(grant, priv.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newGrants = append(newGrants, newGrant)
|
||||
}
|
||||
}
|
||||
}
|
||||
// uniq by role + object + object name + grantor user + privilege name + db name
|
||||
return lo.UniqBy(newGrants, func(g *milvuspb.GrantEntity) string {
|
||||
return fmt.Sprintf("%s-%s-%s-%s-%s-%s", g.Role, g.Object, g.ObjectName, g.Grantor.User, g.Grantor.Privilege.Name, g.DbName)
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
||||
@ -1764,7 +1764,9 @@ func TestRootCoord_RBACError(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
|
||||
}
|
||||
|
||||
mockMeta.IsCustomPrivilegeGroupFunc = func(groupName string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
mockMeta.SelectUserFunc = func(tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@ -2009,6 +2011,29 @@ func TestCore_InitRBAC(t *testing.T) {
|
||||
err := c.initRbac()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("init default privilege groups", func(t *testing.T) {
|
||||
clusterReadWrite := `SelectOwnership,SelectUser,DescribeResourceGroup`
|
||||
meta := mockrootcoord.NewIMetaTable(t)
|
||||
c := newTestCore(withHealthyCode(), withMeta(meta))
|
||||
|
||||
Params.Save(Params.RbacConfig.Enabled.Key, "true")
|
||||
Params.Save(Params.RbacConfig.ClusterReadWritePrivileges.Key, clusterReadWrite)
|
||||
|
||||
defer func() {
|
||||
Params.Reset(Params.RbacConfig.Enabled.Key)
|
||||
Params.Reset(Params.RbacConfig.ClusterReadWritePrivileges.Key)
|
||||
}()
|
||||
|
||||
builtinGroups := c.initBuiltinPrivilegeGroups()
|
||||
fmt.Println(builtinGroups)
|
||||
assert.Equal(t, len(util.BuiltinPrivilegeGroups), len(builtinGroups))
|
||||
for _, group := range builtinGroups {
|
||||
if group.GroupName == "ClusterReadWrite" {
|
||||
assert.Equal(t, len(group.Privileges), 3)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCore_BackupRBAC(t *testing.T) {
|
||||
|
||||
@ -12,7 +12,7 @@ require (
|
||||
github.com/expr-lang/expr v1.15.7
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/klauspost/compress v1.17.7
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1
|
||||
github.com/nats-io/nats-server/v2 v2.10.12
|
||||
github.com/nats-io/nats.go v1.34.1
|
||||
github.com/panjf2000/ants/v2 v2.7.2
|
||||
|
||||
@ -503,8 +503,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu
|
||||
github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
|
||||
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
|
||||
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17 h1:ANkXdUKKpIPPQkw9pkV9ku9AEtSaPyua9XzdMTUxjCs=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1 h1:Xp4zOR85XFFtM7Eif945BeSmDf30hbdijbeNSuy92Bg=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
|
||||
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=
|
||||
github.com/milvus-io/pulsar-client-go v0.6.10/go.mod h1:lQqCkgwDF8YFYjKA+zOheTk1tev2B+bKj5j7+nm8M1w=
|
||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||
|
||||
@ -19,6 +19,8 @@ package util
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
|
||||
"github.com/milvus-io/milvus/pkg/common"
|
||||
"github.com/milvus-io/milvus/pkg/util/typeutil"
|
||||
@ -160,6 +162,15 @@ var (
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPrivilegeGroup.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListPrivilegeGroups.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeOperatePrivilegeGroup.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadWrite.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterAdmin.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadWrite.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseAdmin.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadWrite.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionAdmin.String()),
|
||||
},
|
||||
commonpb.ObjectType_User.String(): {
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()),
|
||||
@ -283,6 +294,97 @@ var (
|
||||
commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeFlush.String(),
|
||||
}
|
||||
|
||||
BuiltinPrivilegeGroups = map[string][]string{
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String()): CollectionReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadWrite.String()): CollectionReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionAdmin.String()): CollectionAdminPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String()): DatabaseReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadWrite.String()): DatabaseReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseAdmin.String()): DatabaseAdminPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String()): ClusterReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadWrite.String()): ClusterReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterAdmin.String()): ClusterAdminPrivilegeGroup,
|
||||
}
|
||||
|
||||
CollectionReadOnlyPrivilegeGroup = []string{
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeQuery.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSearch.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeIndexDetail.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetFlushState.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadState.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadingProgress.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeHasPartition.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowPartitions.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeCollection.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeAlias.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetStatistics.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListAliases.String()),
|
||||
}
|
||||
|
||||
CollectionReadWritePrivilegeGroup = append(CollectionReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoad.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRelease.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeInsert.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDelete.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpsert.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeImport.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlush.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCompaction.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoadBalance.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRenameCollection.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateIndex.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropIndex.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePartition.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPartition.String()),
|
||||
)
|
||||
|
||||
CollectionAdminPrivilegeGroup = append(CollectionReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateAlias.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropAlias.String()),
|
||||
)
|
||||
|
||||
DatabaseReadOnlyPrivilegeGroup = []string{
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowCollections.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeDatabase.String()),
|
||||
}
|
||||
|
||||
DatabaseReadWritePrivilegeGroup = append(DatabaseReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String()),
|
||||
)
|
||||
|
||||
DatabaseAdminPrivilegeGroup = append(DatabaseReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateCollection.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropCollection.String()),
|
||||
)
|
||||
|
||||
ClusterReadOnlyPrivilegeGroup = []string{
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListDatabases.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectOwnership.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectUser.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeResourceGroup.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListResourceGroups.String()),
|
||||
}
|
||||
|
||||
ClusterReadWritePrivilegeGroup = append(ClusterReadOnlyPrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlushAll.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferNode.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferReplica.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateResourceGroups.String()),
|
||||
)
|
||||
|
||||
ClusterAdminPrivilegeGroup = append(ClusterReadWritePrivilegeGroup,
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeBackupRBAC.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRestoreRBAC.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropDatabase.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateOwnership.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropOwnership.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeManageOwnership.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateResourceGroup.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropResourceGroup.String()),
|
||||
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()),
|
||||
)
|
||||
)
|
||||
|
||||
// StringSet convert array to map for conveniently check if the array contains an element
|
||||
@ -344,6 +446,12 @@ func IsPrivilegeNameDefined(name string) bool {
|
||||
return PrivilegeNameForMetastore(name) != ""
|
||||
}
|
||||
|
||||
func IsBuiltinPrivilegeGroup(name string) bool {
|
||||
dbPrivilege := PrivilegeGroupWord + name
|
||||
_, ok := commonpb.ObjectPrivilege_value[dbPrivilege]
|
||||
return ok
|
||||
}
|
||||
|
||||
func PrivilegeGroupNameForMetastore(name string) string {
|
||||
return PrivilegeGroupWord + name
|
||||
}
|
||||
@ -360,3 +468,12 @@ func IsBuiltinRole(roleName string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetObjectType(privName string) string {
|
||||
for objectType, privs := range ObjectPrivileges {
|
||||
if lo.Contains(privs, privName) {
|
||||
return objectType
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -74,6 +74,7 @@ type ComponentParam struct {
|
||||
HTTPCfg httpConfig
|
||||
LogCfg logConfig
|
||||
RoleCfg roleConfig
|
||||
RbacConfig rbacConfig
|
||||
|
||||
RootCoordGrpcServerCfg GrpcServerConfig
|
||||
ProxyGrpcServerCfg GrpcServerConfig
|
||||
@ -124,6 +125,7 @@ func (p *ComponentParam) init(bt *BaseTable) {
|
||||
p.HTTPCfg.init(bt)
|
||||
p.LogCfg.init(bt)
|
||||
p.RoleCfg.init(bt)
|
||||
p.RbacConfig.init(bt)
|
||||
p.GpuConfig.init(bt)
|
||||
|
||||
p.RootCoordGrpcServerCfg.Init("rootCoord", bt)
|
||||
|
||||
41
pkg/util/paramtable/rbac_config_test.go
Normal file
41
pkg/util/paramtable/rbac_config_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Licensed to the LF AI & Data foundation under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 paramtable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/milvus-io/milvus/pkg/util"
|
||||
)
|
||||
|
||||
func TestRbacConfig_Init(t *testing.T) {
|
||||
params := ComponentParam{}
|
||||
params.Init(NewBaseTable(SkipRemote(true)))
|
||||
cfg := ¶ms.RbacConfig
|
||||
assert.Equal(t, cfg.Enabled.GetAsBool(), false)
|
||||
assert.Equal(t, cfg.ClusterReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterReadOnly"])
|
||||
assert.Equal(t, cfg.ClusterReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterReadWrite"])
|
||||
assert.Equal(t, cfg.ClusterAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterAdmin"])
|
||||
assert.Equal(t, cfg.DBReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseReadOnly"])
|
||||
assert.Equal(t, cfg.DBReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseReadWrite"])
|
||||
assert.Equal(t, cfg.DBAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseAdmin"])
|
||||
assert.Equal(t, cfg.CollectionReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionReadOnly"])
|
||||
assert.Equal(t, cfg.CollectionReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionReadWrite"])
|
||||
assert.Equal(t, cfg.CollectionAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionAdmin"])
|
||||
}
|
||||
114
pkg/util/paramtable/rbac_param.go
Normal file
114
pkg/util/paramtable/rbac_param.go
Normal file
@ -0,0 +1,114 @@
|
||||
package paramtable
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/milvus-io/milvus/pkg/util"
|
||||
)
|
||||
|
||||
type rbacConfig struct {
|
||||
Enabled ParamItem `refreshable:"false"`
|
||||
ClusterReadOnlyPrivileges ParamItem `refreshable:"false"`
|
||||
ClusterReadWritePrivileges ParamItem `refreshable:"false"`
|
||||
ClusterAdminPrivileges ParamItem `refreshable:"false"`
|
||||
|
||||
DBReadOnlyPrivileges ParamItem `refreshable:"false"`
|
||||
DBReadWritePrivileges ParamItem `refreshable:"false"`
|
||||
DBAdminPrivileges ParamItem `refreshable:"false"`
|
||||
|
||||
CollectionReadOnlyPrivileges ParamItem `refreshable:"false"`
|
||||
CollectionReadWritePrivileges ParamItem `refreshable:"false"`
|
||||
CollectionAdminPrivileges ParamItem `refreshable:"false"`
|
||||
}
|
||||
|
||||
func (p *rbacConfig) init(base *BaseTable) {
|
||||
p.Enabled = ParamItem{
|
||||
Key: "common.security.rbac.overrideBuiltInPrivilgeGroups.enabled",
|
||||
DefaultValue: "false",
|
||||
Version: "2.4.16",
|
||||
Doc: "Whether to override build-in privilege groups",
|
||||
Export: true,
|
||||
}
|
||||
p.Enabled.Init(base.mgr)
|
||||
|
||||
p.ClusterReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.readonly.privileges",
|
||||
DefaultValue: strings.Join(util.ClusterReadOnlyPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level readonly privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.ClusterReadOnlyPrivileges.Init(base.mgr)
|
||||
|
||||
p.ClusterReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.readwrite.privileges",
|
||||
DefaultValue: strings.Join(util.ClusterReadWritePrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level readwrite privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.ClusterReadWritePrivileges.Init(base.mgr)
|
||||
|
||||
p.ClusterAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.admin.privileges",
|
||||
DefaultValue: strings.Join(util.ClusterAdminPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level admin privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.ClusterAdminPrivileges.Init(base.mgr)
|
||||
|
||||
p.DBReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.readonly.privileges",
|
||||
DefaultValue: strings.Join(util.DatabaseReadOnlyPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level readonly privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.DBReadOnlyPrivileges.Init(base.mgr)
|
||||
|
||||
p.DBReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.readwrite.privileges",
|
||||
DefaultValue: strings.Join(util.DatabaseReadWritePrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level readwrite privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.DBReadWritePrivileges.Init(base.mgr)
|
||||
|
||||
p.DBAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.admin.privileges",
|
||||
DefaultValue: strings.Join(util.DatabaseAdminPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level admin privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.DBAdminPrivileges.Init(base.mgr)
|
||||
|
||||
p.CollectionReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.readonly.privileges",
|
||||
DefaultValue: strings.Join(util.CollectionReadOnlyPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level readonly privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.CollectionReadOnlyPrivileges.Init(base.mgr)
|
||||
|
||||
p.CollectionReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.readwrite.privileges",
|
||||
DefaultValue: strings.Join(util.CollectionReadWritePrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level readwrite privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.CollectionReadWritePrivileges.Init(base.mgr)
|
||||
|
||||
p.CollectionAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.admin.privileges",
|
||||
DefaultValue: strings.Join(util.CollectionAdminPrivilegeGroup, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level admin privileges",
|
||||
Export: true,
|
||||
}
|
||||
p.CollectionAdminPrivileges.Init(base.mgr)
|
||||
}
|
||||
@ -18,8 +18,10 @@ package rbac
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
|
||||
@ -46,10 +48,10 @@ func (s *PrivilegeGroupTestSuite) TestBuiltinPrivilegeGroup() {
|
||||
ctx := GetContext(context.Background(), "root:123456")
|
||||
|
||||
// Test empty RBAC content
|
||||
resp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{})
|
||||
backupResp, err := s.Cluster.Proxy.BackupRBAC(ctx, &milvuspb.BackupRBACMetaRequest{})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(resp.GetStatus()))
|
||||
s.Equal("", resp.GetRBACMeta().String())
|
||||
s.True(merr.Ok(backupResp.GetStatus()))
|
||||
s.Equal("", backupResp.GetRBACMeta().String())
|
||||
|
||||
// Generate some RBAC content
|
||||
roleName := "test_role"
|
||||
@ -59,24 +61,130 @@ func (s *PrivilegeGroupTestSuite) TestBuiltinPrivilegeGroup() {
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(createRoleResp))
|
||||
|
||||
s.operatePrivilege(ctx, roleName, "ReadOnly", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "ReadWrite", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "Admin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
resp, _ := s.operatePrivilege(ctx, roleName, "ReadOnly", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilege(ctx, roleName, "ReadWrite", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilege(ctx, roleName, "Admin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
for _, builtinGroup := range lo.Keys(util.BuiltinPrivilegeGroups) {
|
||||
fmt.Println("!!! builtinGroup: ", builtinGroup)
|
||||
resp, _ = s.operatePrivilege(ctx, roleName, builtinGroup, commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
}
|
||||
|
||||
s.validateGrants(ctx, roleName, commonpb.ObjectType_Global.String(), 1)
|
||||
s.validateGrants(ctx, roleName, commonpb.ObjectType_Collection.String(), 2)
|
||||
}
|
||||
|
||||
/*
|
||||
create group1: query, search
|
||||
grant insert to role -> role: insert
|
||||
grant group1 to role -> role: insert, group1(query, search)
|
||||
create group2: query, delete
|
||||
grant group2 to role -> role: insert, group1(query, search), group2(query, delete)
|
||||
add query, load to group1 -> group1: query, search, load -> role: insert, group1(query, search, load), group2(query, delete)
|
||||
remove query from group1 -> group1: search, load -> role: insert, group1(search, load), group2(query, delete), role still have query privilege because of group2 granted.
|
||||
*/
|
||||
func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
func (s *PrivilegeGroupTestSuite) TestInvalidPrivilegeGroup() {
|
||||
ctx := GetContext(context.Background(), "root:123456")
|
||||
|
||||
createResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(createResp))
|
||||
|
||||
// create group %$ will fail
|
||||
createResp, _ = s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: "%$",
|
||||
})
|
||||
s.False(merr.Ok(createResp))
|
||||
|
||||
createResp, _ = s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: "a&",
|
||||
})
|
||||
s.False(merr.Ok(createResp))
|
||||
|
||||
createResp, _ = s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: strings.Repeat("a", 300),
|
||||
})
|
||||
s.False(merr.Ok(createResp))
|
||||
|
||||
// drop group %$ will fail
|
||||
dropResp, _ := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: "%$",
|
||||
})
|
||||
s.False(merr.Ok(dropResp))
|
||||
|
||||
dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(dropResp))
|
||||
|
||||
dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(dropResp))
|
||||
|
||||
operateResp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(operateResp))
|
||||
|
||||
operateResp, err = s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
Privileges: []*milvuspb.PrivilegeEntity{{Name: "123"}},
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(operateResp))
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) TestGrantV2BuiltinPrivilegeGroup() {
|
||||
ctx := GetContext(context.Background(), "root:123456")
|
||||
|
||||
roleName := "test_role"
|
||||
createRoleResp, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{
|
||||
Entity: &milvuspb.RoleEntity{Name: roleName},
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(createRoleResp))
|
||||
|
||||
resp, _ := s.operatePrivilegeV2(ctx, roleName, "ClusterReadOnly", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "ClusterReadWrite", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "ClusterAdmin", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseReadOnly", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseReadWrite", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseAdmin", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionReadOnly", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionReadWrite", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionAdmin", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "ClusterAdmin", "db1", util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "ClusterAdmin", "db1", "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "ClusterAdmin", util.AnyWord, "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseAdmin", "db1", util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseAdmin", "db1", "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "DatabaseAdmin", util.AnyWord, "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionAdmin", "db1", util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionAdmin", "db1", "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, roleName, "CollectionAdmin", util.AnyWord, "col1", milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) TestGrantV2CustomPrivilegeGroup() {
|
||||
ctx := GetContext(context.Background(), "root:123456")
|
||||
|
||||
// Helper function to operate on privilege groups
|
||||
@ -123,12 +231,14 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(createRoleResp))
|
||||
s.operatePrivilege(ctx, role, "Insert", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 1)
|
||||
resp, _ := s.operatePrivilegeV2(ctx, role, "Insert", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 1)
|
||||
|
||||
// grant group1 to role -> role: insert, group1(query, search)
|
||||
s.operatePrivilege(ctx, role, "group1", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 2)
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "group1", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 2)
|
||||
|
||||
// create group2: query, delete
|
||||
createResp2, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
@ -144,8 +254,9 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
validatePrivilegeGroup("group2", 2)
|
||||
|
||||
// grant group2 to role -> role: insert, group1(query, search), group2(query, delete)
|
||||
s.operatePrivilege(ctx, role, "group2", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3)
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "group2", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 3)
|
||||
|
||||
// add query, load to group1 -> group1: query, search, load -> role: insert, group1(query, search, load), group2(query, delete)
|
||||
operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{
|
||||
@ -153,14 +264,22 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
{Name: "Load"},
|
||||
})
|
||||
validatePrivilegeGroup("group1", 3)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 3)
|
||||
|
||||
// add different object type privileges to group1 is not allowed
|
||||
resp, _ = s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup,
|
||||
Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}},
|
||||
})
|
||||
s.Error(merr.Error(resp))
|
||||
|
||||
// remove query from group1 -> group1: search, load -> role: insert, group1(search, load), group2(query, delete), role still have query privilege because of group2 granted.
|
||||
operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup, []*milvuspb.PrivilegeEntity{
|
||||
{Name: "Query"},
|
||||
})
|
||||
validatePrivilegeGroup("group1", 2)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Collection.String(), 3)
|
||||
s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 3)
|
||||
|
||||
// Drop the group during any role usage will cause error
|
||||
dropResp, _ := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
@ -169,9 +288,12 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
s.Error(merr.Error(dropResp))
|
||||
|
||||
// Revoke privilege group and privileges
|
||||
s.operatePrivilege(ctx, role, "group1", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.operatePrivilege(ctx, role, "group2", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.operatePrivilege(ctx, role, "Insert", commonpb.ObjectType_Collection.String(), milvuspb.OperatePrivilegeType_Revoke)
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "group1", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "group2", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.True(merr.Ok(resp))
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "Insert", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
// Drop the privilege group after revoking the privilege will succeed
|
||||
dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
@ -187,9 +309,22 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
s.True(merr.Ok(dropResp))
|
||||
|
||||
// Validate the group was dropped
|
||||
resp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{})
|
||||
listResp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{})
|
||||
s.NoError(err)
|
||||
s.Equal(0, len(resp.PrivilegeGroups))
|
||||
s.Equal(len(util.BuiltinPrivilegeGroups), len(listResp.PrivilegeGroups))
|
||||
|
||||
// validate edge cases
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, util.AnyWord, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, util.AnyWord, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "group3", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
s.False(merr.Ok(resp))
|
||||
|
||||
resp, _ = s.operatePrivilegeV2(ctx, role, "%$", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
s.False(merr.Ok(resp))
|
||||
|
||||
// Drop the role
|
||||
dropRoleResp, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{
|
||||
@ -199,42 +334,7 @@ func (s *PrivilegeGroupTestSuite) TestCustomPrivilegeGroup() {
|
||||
s.True(merr.Ok(dropRoleResp))
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) TestInvalidPrivilegeGroup() {
|
||||
ctx := GetContext(context.Background(), "root:123456")
|
||||
|
||||
createResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(createResp))
|
||||
|
||||
dropResp, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(dropResp))
|
||||
|
||||
dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(dropResp))
|
||||
|
||||
operateResp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(operateResp))
|
||||
|
||||
operateResp, err = s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
Privileges: []*milvuspb.PrivilegeEntity{{Name: "123"}},
|
||||
})
|
||||
s.NoError(err)
|
||||
s.False(merr.Ok(operateResp))
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, privilege, objectType string, operateType milvuspb.OperatePrivilegeType) {
|
||||
func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, privilege, objectType string, operateType milvuspb.OperatePrivilegeType) (*commonpb.Status, error) {
|
||||
resp, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{
|
||||
Type: operateType,
|
||||
Entity: &milvuspb.GrantEntity{
|
||||
@ -248,8 +348,25 @@ func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, pr
|
||||
},
|
||||
},
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(resp))
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) operatePrivilegeV2(ctx context.Context, role, privilege, dbName, collectionName string, operateType milvuspb.OperatePrivilegeType) (*commonpb.Status, error) {
|
||||
resp, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{
|
||||
Type: operateType,
|
||||
Entity: &milvuspb.GrantEntity{
|
||||
Role: &milvuspb.RoleEntity{Name: role},
|
||||
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
|
||||
ObjectName: collectionName,
|
||||
DbName: dbName,
|
||||
Grantor: &milvuspb.GrantorEntity{
|
||||
User: &milvuspb.UserEntity{Name: util.UserRoot},
|
||||
Privilege: &milvuspb.PrivilegeEntity{Name: privilege},
|
||||
},
|
||||
},
|
||||
Version: "v2",
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *PrivilegeGroupTestSuite) validateGrants(ctx context.Context, roleName, objectType string, expectedCount int) {
|
||||
@ -261,7 +378,6 @@ func (s *PrivilegeGroupTestSuite) validateGrants(ctx context.Context, roleName,
|
||||
DbName: util.AnyWord,
|
||||
},
|
||||
})
|
||||
fmt.Println("!!!validateGrants: ", resp)
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(resp.GetStatus()))
|
||||
s.Len(resp.GetEntities(), expectedCount)
|
||||
|
||||
@ -78,11 +78,15 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
||||
Type: operateType,
|
||||
Entity: &milvuspb.GrantEntity{
|
||||
Role: &milvuspb.RoleEntity{Name: role},
|
||||
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Collection.String()},
|
||||
ObjectName: objectName,
|
||||
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
|
||||
ObjectName: collectionName,
|
||||
DbName: dbName,
|
||||
Grantor: &milvuspb.GrantorEntity{User: &milvuspb.UserEntity{Name: util.UserRoot}, Privilege: &milvuspb.PrivilegeEntity{Name: privilege}},
|
||||
Grantor: &milvuspb.GrantorEntity{
|
||||
User: &milvuspb.UserEntity{Name: util.UserRoot},
|
||||
Privilege: &milvuspb.PrivilegeEntity{Name: privilege},
|
||||
},
|
||||
},
|
||||
Version: "v2",
|
||||
})
|
||||
s.NoError(err)
|
||||
s.True(merr.Ok(resp))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user