mirror of
https://gitee.com/milvus-io/milvus.git
synced 2025-12-07 09:38:39 +08:00
enhance: RBAC new grant/revoke privilege (#37785)
issue: https://github.com/milvus-io/milvus/issues/37031 also fix issues: https://github.com/milvus-io/milvus/issues/37843, https://github.com/milvus-io/milvus/issues/37842, https://github.com/milvus-io/milvus/issues/37887 Signed-off-by: shaoting-huang <shaoting.huang@zilliz.com>
This commit is contained in:
parent
5f3601a6a5
commit
19572f5b06
2
go.mod
2
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.20241120015424-93892e628c69
|
||||
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
@ -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.20241120015424-93892e628c69 h1:Qt0Bv2Fum3EX3OlkuQYHJINBzeU4oEuHy2lXSfB/gZw=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241120015424-93892e628c69/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=
|
||||
|
||||
@ -59,6 +59,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"
|
||||
|
||||
@ -147,6 +147,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))))
|
||||
@ -1810,6 +1812,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)
|
||||
}
|
||||
|
||||
@ -1454,7 +1454,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()
|
||||
@ -1527,6 +1527,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),
|
||||
})
|
||||
|
||||
@ -294,6 +294,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"`
|
||||
|
||||
@ -5137,6 +5137,65 @@ 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)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OperatePrivilegeV2")
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@ -5332,6 +5332,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()
|
||||
@ -6576,8 +6657,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{}
|
||||
@ -6605,8 +6689,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{}
|
||||
@ -6663,8 +6750,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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2709,6 +2709,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
|
||||
@ -4377,6 +4379,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")
|
||||
}
|
||||
|
||||
@ -156,6 +156,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")
|
||||
|
||||
@ -99,6 +99,7 @@ type mockMetaTable struct {
|
||||
DescribeDatabaseFunc func(ctx context.Context, dbName string) (*model.Database, error)
|
||||
CreatePrivilegeGroupFunc func(ctx context.Context, groupName string) error
|
||||
DropPrivilegeGroupFunc func(ctx context.Context, groupName string) error
|
||||
IsCustomPrivilegeGroupFunc func(ctx context.Context, groupName string) (bool, error)
|
||||
ListPrivilegeGroupsFunc func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error)
|
||||
OperatePrivilegeGroupFunc func(ctx context.Context, groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error
|
||||
GetPrivilegeGroupRolesFunc func(ctx context.Context, groupName string) ([]*milvuspb.RoleEntity, error)
|
||||
@ -268,6 +269,10 @@ func (m mockMetaTable) ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.Pri
|
||||
return m.ListPrivilegeGroupsFunc(ctx)
|
||||
}
|
||||
|
||||
func (m mockMetaTable) IsCustomPrivilegeGroup(ctx context.Context, groupName string) (bool, error) {
|
||||
return m.IsCustomPrivilegeGroupFunc(ctx, groupName)
|
||||
}
|
||||
|
||||
func (m mockMetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error {
|
||||
return m.OperatePrivilegeGroupFunc(ctx, groupName, privileges, operateType)
|
||||
}
|
||||
@ -561,6 +566,9 @@ func withInvalidMeta() Opt {
|
||||
meta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) {
|
||||
return nil, errors.New("error mock ListPrivilegeGroups")
|
||||
}
|
||||
meta.IsCustomPrivilegeGroupFunc = func(ctx context.Context, groupName string) (bool, error) {
|
||||
return false, errors.New("error mock IsCustomPrivilegeGroup")
|
||||
}
|
||||
meta.OperatePrivilegeGroupFunc = func(ctx context.Context, groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error {
|
||||
return errors.New("error mock OperatePrivilegeGroup")
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -2554,44 +2555,72 @@ func (c *Core) isValidObject(entity *milvuspb.ObjectEntity) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) isValidGrantor(ctx context.Context, 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(ctx, 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(ctx, 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(ctx, 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(ctx, 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
|
||||
@ -2608,37 +2637,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(ctx, 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(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
|
||||
|
||||
redoTask := newBaseRedoTask(c.stepExecutor)
|
||||
@ -2709,6 +2730,41 @@ 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.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(ctx, 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) {
|
||||
@ -3105,6 +3161,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,
|
||||
@ -3151,6 +3219,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
|
||||
@ -3162,11 +3238,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 {
|
||||
@ -3250,43 +3326,43 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr
|
||||
|
||||
func (c *Core) expandPrivilegeGroups(ctx context.Context, 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(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 {
|
||||
privName := grant.Grantor.Privilege.Name
|
||||
if privGroup, exists := groups[privName]; !exists {
|
||||
metaName, err := c.getMetastorePrivilegeName(ctx, 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(ctx, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(ctx context.Context, groupName string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
mockMeta.SelectUserFunc = func(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -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.20241120015424-93892e628c69
|
||||
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
|
||||
|
||||
@ -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.20241120015424-93892e628c69 h1:Qt0Bv2Fum3EX3OlkuQYHJINBzeU4oEuHy2lXSfB/gZw=
|
||||
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241120015424-93892e628c69/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=
|
||||
|
||||
@ -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"
|
||||
@ -466,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 ""
|
||||
}
|
||||
|
||||
@ -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,34 +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))
|
||||
|
||||
s.operatePrivilege(ctx, roleName, "ClusterReadOnly", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "ClusterReadWrite", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "ClusterAdmin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "DatabaseReadOnly", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "DatabaseReadWrite", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "DatabaseAdmin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "CollectionReadOnly", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "CollectionReadWrite", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
s.operatePrivilege(ctx, roleName, "CollectionAdmin", commonpb.ObjectType_Global.String(), milvuspb.OperatePrivilegeType_Grant)
|
||||
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(), 10)
|
||||
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
|
||||
@ -133,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{
|
||||
@ -154,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{
|
||||
@ -163,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{
|
||||
@ -179,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{
|
||||
@ -197,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{
|
||||
@ -209,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{
|
||||
@ -258,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) {
|
||||
@ -271,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