From 5bb86216d2181937d45156ff3260262c02447483 Mon Sep 17 00:00:00 2001 From: shaoting-huang Date: Fri, 15 Nov 2024 18:26:07 +0800 Subject: [PATCH] RBAC grant v2 Signed-off-by: shaoting-huang --- go.mod | 2 +- go.sum | 4 +- .../distributed/proxy/httpserver/constant.go | 2 + .../proxy/httpserver/handler_v2.go | 33 +++ .../proxy/httpserver/request_v2.go | 7 + internal/proxy/privilege_interceptor.go | 4 +- internal/proxy/privilege_interceptor_test.go | 42 ++++ internal/rootcoord/meta_table.go | 28 ++- internal/rootcoord/mock_test.go | 8 +- internal/rootcoord/mocks/meta_table.go | 35 ++- internal/rootcoord/root_coord.go | 204 +++++++++++------- internal/rootcoord/root_coord_test.go | 6 +- pkg/go.mod | 2 +- pkg/go.sum | 4 +- pkg/util/constant.go | 10 + .../integration/rbac/privilege_group_test.go | 198 ++++++++++++++++- 16 files changed, 470 insertions(+), 119 deletions(-) diff --git a/go.mod b/go.mod index 0b6c7c89aae3a..d07fc74bbf0f4 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,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.3.4-0.20241114133823-d3506c6f465c + github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc 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 diff --git a/go.sum b/go.sum index c91589f7f5f43..480cd66671754 100644 --- a/go.sum +++ b/go.sum @@ -628,8 +628,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.3.4-0.20241114133823-d3506c6f465c h1:Ay5w6sTE1QxCydCqqW5N44EcJrMqaqbL5zcp2vclkOw= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241114133823-d3506c6f465c/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc h1:0D01wW+mjwYghHFVloFiiHOntkq/+JihBn3OyzM6qGM= +github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/pulsar-client-go v0.12.1 h1:O2JZp1tsYiO7C0MQ4hrUY/aJXnn2Gry6hpm7UodghmE= github.com/milvus-io/pulsar-client-go v0.12.1/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= diff --git a/internal/distributed/proxy/httpserver/constant.go b/internal/distributed/proxy/httpserver/constant.go index f500d59a202ab..2ab7b9c4054e2 100644 --- a/internal/distributed/proxy/httpserver/constant.go +++ b/internal/distributed/proxy/httpserver/constant.go @@ -43,6 +43,8 @@ const ( RevokeRoleAction = "revoke_role" GrantPrivilegeAction = "grant_privilege" RevokePrivilegeAction = "revoke_privilege" + GrantActionV2 = "grant_v2" + RevokeActionV2 = "revoke_v2" AlterAction = "alter" GetProgressAction = "get_progress" // deprecated, keep it for compatibility, use `/v2/vectordb/jobs/import/describe` instead AddPrivilegesToGroupAction = "add_privileges_to_group" diff --git a/internal/distributed/proxy/httpserver/handler_v2.go b/internal/distributed/proxy/httpserver/handler_v2.go index ec050c09547b7..1c9474dd01219 100644 --- a/internal/distributed/proxy/httpserver/handler_v2.go +++ b/internal/distributed/proxy/httpserver/handler_v2.go @@ -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+GrantActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.grantV2)))) + router.POST(RoleCategory+RevokeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.revokeV2)))) // privilege group router.POST(PrivilegeGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.createPrivilegeGroup)))) @@ -1791,6 +1793,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) } diff --git a/internal/distributed/proxy/httpserver/request_v2.go b/internal/distributed/proxy/httpserver/request_v2.go index 842730ef516a5..b1b5e91b22989 100644 --- a/internal/distributed/proxy/httpserver/request_v2.go +++ b/internal/distributed/proxy/httpserver/request_v2.go @@ -278,6 +278,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"` diff --git a/internal/proxy/privilege_interceptor.go b/internal/proxy/privilege_interceptor.go index fc78fc35383dd..9656cf244c9b6 100644 --- a/internal/proxy/privilege_interceptor.go +++ b/internal/proxy/privilege_interceptor.go @@ -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 diff --git a/internal/proxy/privilege_interceptor_test.go b/internal/proxy/privilege_interceptor_test.go index 92621d010eac7..e8093e0985d7b 100644 --- a/internal/proxy/privilege_interceptor_test.go +++ b/internal/proxy/privilege_interceptor_test.go @@ -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) + }) +} diff --git a/internal/rootcoord/meta_table.go b/internal/rootcoord/meta_table.go index 96f3184b402ad..0bc0bbbe332f0 100644 --- a/internal/rootcoord/meta_table.go +++ b/internal/rootcoord/meta_table.go @@ -100,7 +100,7 @@ type IMetaTable interface { BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBACMeta, error) RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error IsCustomPrivilegeGroup(groupName string) (bool, error) - CreatePrivilegeGroup(groupName string) error + CreatePrivilegeGroup(groupName string, options ...bool) error DropPrivilegeGroup(groupName string) error ListPrivilegeGroups() ([]*milvuspb.PrivilegeGroupInfo, error) OperatePrivilegeGroup(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error @@ -1477,22 +1477,28 @@ func (mt *MetaTable) IsCustomPrivilegeGroup(groupName string) (bool, error) { return false, nil } -func (mt *MetaTable) CreatePrivilegeGroup(groupName string) error { +func (mt *MetaTable) CreatePrivilegeGroup(groupName string, options ...bool) error { if funcutil.IsEmptyString(groupName) { return fmt.Errorf("the privilege group name is empty") } + skipValidation := false + if len(options) > 0 { + skipValidation = options[0] + } mt.permissionLock.Lock() defer mt.permissionLock.Unlock() - definedByUsers, err := mt.IsCustomPrivilegeGroup(groupName) - if err != nil { - return err - } - if definedByUsers { - return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by users", groupName) - } - if util.IsPrivilegeNameDefined(groupName) { - return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by built in privileges or privilege groups in system", groupName) + if !skipValidation { + definedByUsers, err := mt.IsCustomPrivilegeGroup(groupName) + if err != nil { + return err + } + if definedByUsers { + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by users", groupName) + } + if util.IsPrivilegeNameDefined(groupName) { + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by built in privileges or privilege groups in system", groupName) + } } data := &milvuspb.PrivilegeGroupInfo{ GroupName: groupName, diff --git a/internal/rootcoord/mock_test.go b/internal/rootcoord/mock_test.go index b4b4369f379dd..42c6f04687234 100644 --- a/internal/rootcoord/mock_test.go +++ b/internal/rootcoord/mock_test.go @@ -97,7 +97,7 @@ type mockMetaTable struct { ListPolicyFunc func(tenant string) ([]string, error) ListUserRoleFunc func(tenant string) ([]string, error) DescribeDatabaseFunc func(ctx context.Context, dbName string) (*model.Database, error) - CreatePrivilegeGroupFunc func(groupName string) error + CreatePrivilegeGroupFunc func(groupName string, options ...bool) error DropPrivilegeGroupFunc func(groupName string) error ListPrivilegeGroupsFunc func() ([]*milvuspb.PrivilegeGroupInfo, error) OperatePrivilegeGroupFunc func(groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error @@ -256,8 +256,8 @@ func (m mockMetaTable) ListUserRole(tenant string) ([]string, error) { return m.ListUserRoleFunc(tenant) } -func (m mockMetaTable) CreatePrivilegeGroup(groupName string) error { - return m.CreatePrivilegeGroupFunc(groupName) +func (m mockMetaTable) CreatePrivilegeGroup(groupName string, options ...bool) error { + return m.CreatePrivilegeGroupFunc(groupName, options...) } func (m mockMetaTable) DropPrivilegeGroup(groupName string) error { @@ -552,7 +552,7 @@ func withInvalidMeta() Opt { meta.DescribeDatabaseFunc = func(ctx context.Context, dbName string) (*model.Database, error) { return nil, errors.New("error mock DescribeDatabase") } - meta.CreatePrivilegeGroupFunc = func(groupName string) error { + meta.CreatePrivilegeGroupFunc = func(groupName string, options ...bool) error { return errors.New("error mock CreatePrivilegeGroup") } meta.DropPrivilegeGroupFunc = func(groupName string) error { diff --git a/internal/rootcoord/mocks/meta_table.go b/internal/rootcoord/mocks/meta_table.go index cbd99fef426c6..757fee93f57b2 100644 --- a/internal/rootcoord/mocks/meta_table.go +++ b/internal/rootcoord/mocks/meta_table.go @@ -620,17 +620,24 @@ func (_c *IMetaTable_CreateDatabase_Call) RunAndReturn(run func(context.Context, return _c } -// CreatePrivilegeGroup provides a mock function with given fields: groupName -func (_m *IMetaTable) CreatePrivilegeGroup(groupName string) error { - ret := _m.Called(groupName) +// CreatePrivilegeGroup provides a mock function with given fields: groupName, options +func (_m *IMetaTable) CreatePrivilegeGroup(groupName string, options ...bool) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, groupName) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) if len(ret) == 0 { panic("no return value specified for CreatePrivilegeGroup") } var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(groupName) + if rf, ok := ret.Get(0).(func(string, ...bool) error); ok { + r0 = rf(groupName, options...) } else { r0 = ret.Error(0) } @@ -645,13 +652,21 @@ type IMetaTable_CreatePrivilegeGroup_Call struct { // CreatePrivilegeGroup is a helper method to define mock.On call // - groupName string -func (_e *IMetaTable_Expecter) CreatePrivilegeGroup(groupName interface{}) *IMetaTable_CreatePrivilegeGroup_Call { - return &IMetaTable_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", groupName)} +// - options ...bool +func (_e *IMetaTable_Expecter) CreatePrivilegeGroup(groupName interface{}, options ...interface{}) *IMetaTable_CreatePrivilegeGroup_Call { + return &IMetaTable_CreatePrivilegeGroup_Call{Call: _e.mock.On("CreatePrivilegeGroup", + append([]interface{}{groupName}, options...)...)} } -func (_c *IMetaTable_CreatePrivilegeGroup_Call) Run(run func(groupName string)) *IMetaTable_CreatePrivilegeGroup_Call { +func (_c *IMetaTable_CreatePrivilegeGroup_Call) Run(run func(groupName string, options ...bool)) *IMetaTable_CreatePrivilegeGroup_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + variadicArgs := make([]bool, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(bool) + } + } + run(args[0].(string), variadicArgs...) }) return _c } @@ -661,7 +676,7 @@ func (_c *IMetaTable_CreatePrivilegeGroup_Call) Return(_a0 error) *IMetaTable_Cr return _c } -func (_c *IMetaTable_CreatePrivilegeGroup_Call) RunAndReturn(run func(string) error) *IMetaTable_CreatePrivilegeGroup_Call { +func (_c *IMetaTable_CreatePrivilegeGroup_Call) RunAndReturn(run func(string, ...bool) error) *IMetaTable_CreatePrivilegeGroup_Call { _c.Call.Return(run) return _c } diff --git a/internal/rootcoord/root_coord.go b/internal/rootcoord/root_coord.go index 9ebfa67846bcf..091bfd30378d1 100644 --- a/internal/rootcoord/root_coord.go +++ b/internal/rootcoord/root_coord.go @@ -21,6 +21,7 @@ import ( "fmt" "math/rand" "os" + "strings" "sync" "time" @@ -576,12 +577,7 @@ func (c *Core) initRbac() error { } if Params.RoleCfg.Enabled.GetAsBool() { - if err := c.initBuiltinRoles(); err != nil { - return err - } - } - if err := c.initBuiltinPrivilegeGroups(); err != nil { - return err + return c.initBuiltinRoles() } return nil } @@ -2552,44 +2548,64 @@ 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) { - return nil - } +func (c *Core) isValidPrivilege(privilegeName string, object string) error { // 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) + 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 object[%s]", entity.Privilege.Name, object) + return fmt.Errorf("not found the privilege name[%s] in object[%s]", privilegeName, object) +} + +func (c *Core) isValidPrivilegeV2(privilegeName, dbName, collectionName string) error { + 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 + } } // OperatePrivilege operate the privilege, including grant and revoke @@ -2606,37 +2622,29 @@ 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)) - 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)) + if err := c.operatePrivilegeCommonCheck(ctx, in); err != nil { return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil } - // set up object name if it is global object type - if in.Entity.Object.Name == commonpb.ObjectType_Global.String() { - in.Entity.ObjectName = util.AnyWord + switch in.Version { + case "v2": + if err := c.isValidPrivilegeV2(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(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 + if in.Entity.Object.Name == commonpb.ObjectType_Global.String() { + in.Entity.ObjectName = util.AnyWord + } } + // set up privilege name for metastore privName := in.Entity.Grantor.Privilege.Name redoTask := newBaseRedoTask(c.stepExecutor) @@ -2707,6 +2715,44 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile return merr.Success(), nil } +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.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") + } + if util.IsAnyWord(entity.Privilege.Name) { + return nil + } + if err := c.isValidObject(in.Entity.Object); err != nil { + return errors.New("the object entity in the request is nil or invalid") + } + return nil +} + func (c *Core) getMetastorePrivilegeName(privName string) (string, error) { // if it is built-in privilege, return the privilege name directly if util.IsPrivilegeNameDefined(privName) { @@ -3248,43 +3294,43 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr 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(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 { privName := grant.Grantor.Privilege.Name if privGroup, exists := groups[privName]; !exists { - metaName, err := c.getMetastorePrivilegeName(privName) + newGrant, err := createGrantEntity(grant, privName) if err != nil { return nil, err } - newGrants = append(newGrants, &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, - }) + newGrants = append(newGrants, newGrant) } else { for _, priv := range privGroup { - metaName, err := c.getMetastorePrivilegeName(priv.Name) + newGrant, err := createGrantEntity(grant, priv.Name) if err != nil { return nil, err } - newGrants = append(newGrants, &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, - }) + newGrants = append(newGrants, newGrant) } } } diff --git a/internal/rootcoord/root_coord_test.go b/internal/rootcoord/root_coord_test.go index 632c928c0d28f..0ba20823e3336 100644 --- a/internal/rootcoord/root_coord_test.go +++ b/internal/rootcoord/root_coord_test.go @@ -1792,7 +1792,7 @@ func TestRootCoord_RBACError(t *testing.T) { mockMeta.ListPrivilegeGroupsFunc = func() ([]*milvuspb.PrivilegeGroupInfo, error) { return nil, errors.New("mock error") } - mockMeta.CreatePrivilegeGroupFunc = func(groupName string) error { + mockMeta.CreatePrivilegeGroupFunc = func(groupName string, options ...bool) error { return errors.New("mock error") } mockMeta.GetPrivilegeGroupRolesFunc = func(groupName string) ([]*milvuspb.RoleEntity, error) { @@ -1976,8 +1976,6 @@ func TestCore_InitRBAC(t *testing.T) { c := newTestCore(withHealthyCode(), withMeta(meta)) meta.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(nil).Twice() meta.EXPECT().OperatePrivilege(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice() - meta.EXPECT().CreatePrivilegeGroup(mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) - meta.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) Params.Save(Params.RoleCfg.Enabled.Key, "false") Params.Save(Params.ProxyCfg.EnablePublicPrivilege.Key, "true") @@ -1997,8 +1995,6 @@ func TestCore_InitRBAC(t *testing.T) { c := newTestCore(withHealthyCode(), withMeta(meta)) meta.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(nil).Times(3) meta.EXPECT().OperatePrivilege(mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - meta.EXPECT().CreatePrivilegeGroup(mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) - meta.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) Params.Save(Params.RoleCfg.Enabled.Key, "true") Params.Save(Params.RoleCfg.Roles.Key, builtinRoles) diff --git a/pkg/go.mod b/pkg/go.mod index 60b97e39a1a53..7cd5dbfc2efec 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -14,7 +14,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.17.7 - github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241114133823-d3506c6f465c + github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc 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 diff --git a/pkg/go.sum b/pkg/go.sum index c1f60d82a59e7..c954d3b0f96e6 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -488,8 +488,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.3.4-0.20241114133823-d3506c6f465c h1:Ay5w6sTE1QxCydCqqW5N44EcJrMqaqbL5zcp2vclkOw= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241114133823-d3506c6f465c/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc h1:0D01wW+mjwYghHFVloFiiHOntkq/+JihBn3OyzM6qGM= +github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241117094023-0b114b4abcbc/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/pulsar-client-go v0.12.1 h1:O2JZp1tsYiO7C0MQ4hrUY/aJXnn2Gry6hpm7UodghmE= github.com/milvus-io/pulsar-client-go v0.12.1/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= diff --git a/pkg/util/constant.go b/pkg/util/constant.go index 920a50171f70c..ca5b75ba33bd7 100644 --- a/pkg/util/constant.go +++ b/pkg/util/constant.go @@ -22,6 +22,7 @@ import ( "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" + "github.com/samber/lo" ) // Meta Prefix consts @@ -466,3 +467,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 "" +} diff --git a/tests/integration/rbac/privilege_group_test.go b/tests/integration/rbac/privilege_group_test.go index 2a3f814df9e49..30d4e565fce72 100644 --- a/tests/integration/rbac/privilege_group_test.go +++ b/tests/integration/rbac/privilege_group_test.go @@ -17,7 +17,6 @@ package rbac import ( "context" - "fmt" "testing" "github.com/stretchr/testify/suite" @@ -244,6 +243,184 @@ func (s *PrivilegeGroupTestSuite) TestInvalidPrivilegeGroup() { 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 + operatePrivilegeGroup := func(groupName string, operateType milvuspb.OperatePrivilegeGroupType, privileges []*milvuspb.PrivilegeEntity) { + resp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Type: operateType, + Privileges: privileges, + }) + s.NoError(err) + s.True(merr.Ok(resp)) + } + + // Helper function to list privilege groups and return the target group and its privileges + validatePrivilegeGroup := func(groupName string, privileges int) []*milvuspb.PrivilegeEntity { + resp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) + s.NoError(err) + for _, privGroup := range resp.PrivilegeGroups { + if privGroup.GroupName == groupName { + s.Equal(privileges, len(privGroup.Privileges)) + return privGroup.Privileges + } + } + return nil + } + + // create group1: query, search + createResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "group1", + }) + s.NoError(err) + s.True(merr.Ok(createResp)) + validatePrivilegeGroup("group1", 0) + operatePrivilegeGroup("group1", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + {Name: "Search"}, + }) + validatePrivilegeGroup("group1", 2) + + // grant insert to role -> role: insert + role := "role1" + createRoleResp, err := s.Cluster.Proxy.CreateRole(ctx, &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{Name: role}, + }) + s.NoError(err) + s.True(merr.Ok(createRoleResp)) + 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) + 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{ + GroupName: "group2", + }) + s.NoError(err) + s.True(merr.Ok(createResp2)) + validatePrivilegeGroup("group2", 0) + operatePrivilegeGroup("group2", milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, []*milvuspb.PrivilegeEntity{ + {Name: "Query"}, + {Name: "Delete"}, + }) + validatePrivilegeGroup("group2", 2) + + // grant group2 to role -> role: insert, group1(query, search), group2(query, delete) + 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{ + {Name: "Query"}, + {Name: "Load"}, + }) + validatePrivilegeGroup("group1", 3) + s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), 3) + + // 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_Global.String(), 3) + + // Drop the group during any role usage will cause error + dropResp, _ := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group1", + }) + s.Error(merr.Error(dropResp)) + + // Revoke privilege group and privileges + 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{ + GroupName: "group1", + }) + s.NoError(err) + s.True(merr.Ok(dropResp)) + + dropResp, err = s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "group2", + }) + s.NoError(err) + s.True(merr.Ok(dropResp)) + + // Validate the group was dropped + listResp, err := s.Cluster.Proxy.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) + s.NoError(err) + s.Equal(0, len(listResp.PrivilegeGroups)) + + // Drop the role + dropRoleResp, err := s.Cluster.Proxy.DropRole(ctx, &milvuspb.DropRoleRequest{ + RoleName: role, + }) + s.NoError(err) + s.True(merr.Ok(dropRoleResp)) +} + func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, privilege, objectType string, operateType milvuspb.OperatePrivilegeType) { resp, err := s.Cluster.Proxy.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{ Type: operateType, @@ -262,6 +439,24 @@ func (s *PrivilegeGroupTestSuite) operatePrivilege(ctx context.Context, role, pr s.True(merr.Ok(resp)) } +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) { resp, err := s.Cluster.Proxy.SelectGrant(ctx, &milvuspb.SelectGrantRequest{ Entity: &milvuspb.GrantEntity{ @@ -271,7 +466,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)