mirror of https://github.com/milvus-io/milvus.git
fix: rbac custom group privilege level check (#39164)
related: https://github.com/milvus-io/milvus/issues/39086 Signed-off-by: shaoting-huang <shaoting.huang@zilliz.com>pull/39212/head
parent
5f94954bb4
commit
5c5948cb70
|
@ -5439,10 +5439,6 @@ func (node *Proxy) validateOperatePrivilegeV2Params(req *milvuspb.OperatePrivile
|
|||
if err := ValidatePrivilege(req.Grantor.Privilege.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
// validate built-in privilege group params
|
||||
if err := ValidateBuiltInPrivilegeGroup(req.Grantor.Privilege.Name, req.DbName, req.CollectionName); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Type != milvuspb.OperatePrivilegeType_Grant && req.Type != milvuspb.OperatePrivilegeType_Revoke {
|
||||
return merr.WrapErrParameterInvalidMsg("the type in the request not grant or revoke")
|
||||
}
|
||||
|
|
|
@ -1120,31 +1120,6 @@ func ValidatePrivilege(entity string) error {
|
|||
return validateName(entity, "Privilege")
|
||||
}
|
||||
|
||||
func ValidateBuiltInPrivilegeGroup(entity string, dbName string, collectionName string) error {
|
||||
if !util.IsBuiltinPrivilegeGroup(entity) {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(entity, milvuspb.PrivilegeLevel_Cluster.String()):
|
||||
if !util.IsAnyWord(dbName) || !util.IsAnyWord(collectionName) {
|
||||
return merr.WrapErrParameterInvalidMsg("dbName and collectionName should be * for the cluster level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
case strings.HasPrefix(entity, milvuspb.PrivilegeLevel_Database.String()):
|
||||
if collectionName != "" && collectionName != util.AnyWord {
|
||||
return merr.WrapErrParameterInvalidMsg("collectionName should be * for the database level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
case strings.HasPrefix(entity, milvuspb.PrivilegeLevel_Collection.String()):
|
||||
if util.IsAnyWord(dbName) && !util.IsAnyWord(collectionName) && collectionName != "" {
|
||||
return merr.WrapErrParameterInvalidMsg("please specify database name for the collection level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetCurUserFromContext(ctx context.Context) (string, error) {
|
||||
return contextutil.GetCurUserFromContext(ctx)
|
||||
}
|
||||
|
|
|
@ -1525,7 +1525,7 @@ func (mt *MetaTable) RestoreRBAC(ctx context.Context, tenant string, meta *milvu
|
|||
return mt.catalog.RestoreRBAC(ctx, tenant, meta)
|
||||
}
|
||||
|
||||
// check if the privielge group name is defined by users
|
||||
// check if the privilege group name is defined by users
|
||||
func (mt *MetaTable) IsCustomPrivilegeGroup(ctx context.Context, groupName string) (bool, error) {
|
||||
privGroups, err := mt.catalog.ListPrivilegeGroups(ctx)
|
||||
if err != nil {
|
||||
|
@ -1641,7 +1641,7 @@ func (mt *MetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string
|
|||
if group.GroupName == p.Name {
|
||||
privileges = append(privileges, group.Privileges...)
|
||||
} else {
|
||||
return merr.WrapErrParameterInvalidMsg("there is no privilege name or privielge group name [%s] defined in system to operate", p.Name)
|
||||
return merr.WrapErrParameterInvalidMsg("there is no privilege name or privilege group name [%s] defined in system to operate", p.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, in *milvu
|
|||
}
|
||||
grants := []*milvuspb.GrantEntity{in.Entity}
|
||||
|
||||
allGroups, err := core.getPrivilegeGroups(ctx)
|
||||
allGroups, err := core.getDefaultAndCustomPrivilegeGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -275,12 +275,12 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in *
|
|||
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{}{}
|
||||
// check if privileges are the same privilege level
|
||||
privilegeLevels := lo.SliceToMap(newPrivs, func(p *milvuspb.PrivilegeEntity) (string, struct{}) {
|
||||
return util.GetPrivilegeLevel(p.Name), struct{}{}
|
||||
})
|
||||
if len(objectTypes) > 1 {
|
||||
return nil, errors.New("privileges are not the same object type")
|
||||
if len(privilegeLevels) > 1 {
|
||||
return nil, errors.New("privileges are not the same privilege level")
|
||||
}
|
||||
case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup:
|
||||
newPrivs, _ := lo.Difference(v, in.Privileges)
|
||||
|
|
|
@ -2595,6 +2595,10 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile
|
|||
ctxLog.Error("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
if err := c.validatePrivilegeGroupParams(ctx, privName, in.Entity.DbName, in.Entity.ObjectName); err != nil {
|
||||
ctxLog.Error("", zap.Error(err))
|
||||
return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil
|
||||
}
|
||||
// set up object type for metastore, to be compatible with v1 version
|
||||
in.Entity.Object.Name = util.GetObjectType(privName)
|
||||
default:
|
||||
|
@ -2656,6 +2660,42 @@ func (c *Core) operatePrivilegeCommonCheck(ctx context.Context, in *milvuspb.Ope
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) validatePrivilegeGroupParams(ctx context.Context, entity string, dbName string, collectionName string) error {
|
||||
allGroups, err := c.getDefaultAndCustomPrivilegeGroups(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups := lo.SliceToMap(allGroups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) {
|
||||
return group.GroupName, group.Privileges
|
||||
})
|
||||
privs, exists := groups[entity]
|
||||
if !exists || len(privs) == 0 {
|
||||
// it is a privilege, no need to check with other params
|
||||
return nil
|
||||
}
|
||||
// since all privileges are same level in a group, just check the first privilege
|
||||
level := util.GetPrivilegeLevel(privs[0].GetName())
|
||||
switch level {
|
||||
case milvuspb.PrivilegeLevel_Cluster.String():
|
||||
if !util.IsAnyWord(dbName) || !util.IsAnyWord(collectionName) {
|
||||
return merr.WrapErrParameterInvalidMsg("dbName and collectionName should be * for the cluster level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
case milvuspb.PrivilegeLevel_Database.String():
|
||||
if collectionName != "" && collectionName != util.AnyWord {
|
||||
return merr.WrapErrParameterInvalidMsg("collectionName should be * for the database level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
case milvuspb.PrivilegeLevel_Collection.String():
|
||||
if util.IsAnyWord(dbName) && !util.IsAnyWord(collectionName) && collectionName != "" {
|
||||
return merr.WrapErrParameterInvalidMsg("please specify database name for the collection level privilege: %s", entity)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("not found the privilege level")
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -2757,7 +2797,7 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest)
|
|||
}, nil
|
||||
}
|
||||
// expand privilege groups and turn to policies
|
||||
allGroups, err := c.getPrivilegeGroups(ctx)
|
||||
allGroups, err := c.getDefaultAndCustomPrivilegeGroups(ctx)
|
||||
if err != nil {
|
||||
errMsg := "fail to get privilege groups"
|
||||
ctxLog.Warn(errMsg, zap.Error(err))
|
||||
|
@ -3131,8 +3171,8 @@ func (c *Core) expandPrivilegeGroups(ctx context.Context, grants []*milvuspb.Gra
|
|||
}), nil
|
||||
}
|
||||
|
||||
// getPrivilegeGroups returns default privilege groups and user-defined privilege groups.
|
||||
func (c *Core) getPrivilegeGroups(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) {
|
||||
// getDefaultAndCustomPrivilegeGroups returns default privilege groups and user-defined privilege groups.
|
||||
func (c *Core) getDefaultAndCustomPrivilegeGroups(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) {
|
||||
allGroups, err := c.meta.ListPrivilegeGroups(ctx)
|
||||
allGroups = append(allGroups, Params.RbacConfig.GetDefaultPrivilegeGroups()...)
|
||||
if err != nil {
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/samber/lo"
|
||||
|
||||
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
|
||||
"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
|
||||
"github.com/milvus-io/milvus/pkg/common"
|
||||
"github.com/milvus-io/milvus/pkg/util/typeutil"
|
||||
)
|
||||
|
@ -292,6 +293,124 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// rbac v2 uses privilege level to group privileges rather than object type
|
||||
var (
|
||||
CollectionReadOnlyPrivileges = ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeQuery.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeSearch.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeIndexDetail.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeGetFlushState.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeGetLoadState.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeGetLoadingProgress.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeHasPartition.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeShowPartitions.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDescribeCollection.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDescribeAlias.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeGetStatistics.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeListAliases.String(),
|
||||
})
|
||||
|
||||
CollectionReadWritePrivileges = append(CollectionReadOnlyPrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeLoad.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeRelease.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeInsert.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDelete.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeUpsert.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeImport.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeFlush.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCompaction.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeLoadBalance.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateIndex.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropIndex.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreatePartition.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropPartition.String(),
|
||||
})...,
|
||||
)
|
||||
|
||||
CollectionAdminPrivileges = append(CollectionReadWritePrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateAlias.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropAlias.String(),
|
||||
})...,
|
||||
)
|
||||
|
||||
DatabaseReadOnlyPrivileges = ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeShowCollections.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDescribeDatabase.String(),
|
||||
})
|
||||
|
||||
DatabaseReadWritePrivileges = append(DatabaseReadOnlyPrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String(),
|
||||
})...,
|
||||
)
|
||||
|
||||
DatabaseAdminPrivileges = append(DatabaseReadWritePrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateCollection.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropCollection.String(),
|
||||
})...,
|
||||
)
|
||||
|
||||
ClusterReadOnlyPrivileges = ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeListDatabases.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeSelectOwnership.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeSelectUser.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDescribeResourceGroup.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeListResourceGroups.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeListPrivilegeGroups.String(),
|
||||
})
|
||||
|
||||
ClusterReadWritePrivileges = append(ClusterReadOnlyPrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeFlushAll.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeTransferNode.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeTransferReplica.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeUpdateResourceGroups.String(),
|
||||
})...,
|
||||
)
|
||||
|
||||
ClusterAdminPrivileges = append(ClusterReadWritePrivileges,
|
||||
ConvertPrivileges([]string{
|
||||
commonpb.ObjectPrivilege_PrivilegeBackupRBAC.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeRestoreRBAC.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropDatabase.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateOwnership.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropOwnership.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeManageOwnership.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreateResourceGroup.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropResourceGroup.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeUpdateUser.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeRenameCollection.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeCreatePrivilegeGroup.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeDropPrivilegeGroup.String(),
|
||||
commonpb.ObjectPrivilege_PrivilegeOperatePrivilegeGroup.String(),
|
||||
})...,
|
||||
)
|
||||
)
|
||||
|
||||
// ConvertPrivileges converts each privilege from metastore format to API format.
|
||||
func ConvertPrivileges(privileges []string) []string {
|
||||
return lo.Map(privileges, func(name string, _ int) string {
|
||||
return MetaStore2API(name)
|
||||
})
|
||||
}
|
||||
|
||||
func GetPrivilegeLevel(privilege string) string {
|
||||
if lo.Contains(ClusterAdminPrivileges, privilege) {
|
||||
return milvuspb.PrivilegeLevel_Cluster.String()
|
||||
}
|
||||
if lo.Contains(DatabaseAdminPrivileges, privilege) {
|
||||
return milvuspb.PrivilegeLevel_Database.String()
|
||||
}
|
||||
if lo.Contains(CollectionAdminPrivileges, privilege) {
|
||||
return milvuspb.PrivilegeLevel_Collection.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StringSet convert array to map for conveniently check if the array contains an element
|
||||
func StringSet(strings []string) map[string]struct{} {
|
||||
stringsMap := make(map[string]struct{})
|
||||
|
|
|
@ -22,26 +22,24 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRbacConfig_Init(t *testing.T) {
|
||||
func TestRbacConfig_DefaultPrivileges(t *testing.T) {
|
||||
params := ComponentParam{}
|
||||
params.Init(NewBaseTable(SkipRemote(true)))
|
||||
cfg := ¶ms.RbacConfig
|
||||
assert.Equal(t, len(cfg.GetDefaultPrivilegeGroupNames()), 9)
|
||||
assert.True(t, cfg.IsCollectionPrivilegeGroup("CollectionReadOnly"))
|
||||
assert.False(t, cfg.IsCollectionPrivilegeGroup("DatabaseReadOnly"))
|
||||
assert.Equal(t, cfg.Enabled.GetAsBool(), false)
|
||||
assert.Equal(t, cfg.ClusterReadOnlyPrivileges.GetAsStrings(), builtinPrivilegeGroups["ClusterReadOnly"])
|
||||
assert.Equal(t, cfg.ClusterReadWritePrivileges.GetAsStrings(), builtinPrivilegeGroups["ClusterReadWrite"])
|
||||
assert.Equal(t, cfg.ClusterAdminPrivileges.GetAsStrings(), builtinPrivilegeGroups["ClusterAdmin"])
|
||||
assert.Equal(t, cfg.DBReadOnlyPrivileges.GetAsStrings(), builtinPrivilegeGroups["DatabaseReadOnly"])
|
||||
assert.Equal(t, cfg.DBReadWritePrivileges.GetAsStrings(), builtinPrivilegeGroups["DatabaseReadWrite"])
|
||||
assert.Equal(t, cfg.DBAdminPrivileges.GetAsStrings(), builtinPrivilegeGroups["DatabaseAdmin"])
|
||||
assert.Equal(t, cfg.CollectionReadOnlyPrivileges.GetAsStrings(), builtinPrivilegeGroups["CollectionReadOnly"])
|
||||
assert.Equal(t, cfg.CollectionReadWritePrivileges.GetAsStrings(), builtinPrivilegeGroups["CollectionReadWrite"])
|
||||
assert.Equal(t, cfg.CollectionAdminPrivileges.GetAsStrings(), builtinPrivilegeGroups["CollectionAdmin"])
|
||||
assert.Equal(t, cfg.ClusterReadOnlyPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("ClusterReadOnly"))
|
||||
assert.Equal(t, cfg.ClusterReadWritePrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("ClusterReadWrite"))
|
||||
assert.Equal(t, cfg.ClusterAdminPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("ClusterAdmin"))
|
||||
assert.Equal(t, cfg.DBReadOnlyPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("DatabaseReadOnly"))
|
||||
assert.Equal(t, cfg.DBReadWritePrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("DatabaseReadWrite"))
|
||||
assert.Equal(t, cfg.DBAdminPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("DatabaseAdmin"))
|
||||
assert.Equal(t, cfg.CollectionReadOnlyPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("CollectionReadOnly"))
|
||||
assert.Equal(t, cfg.CollectionReadWritePrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("CollectionReadWrite"))
|
||||
assert.Equal(t, cfg.CollectionAdminPrivileges.GetAsStrings(), cfg.GetDefaultPrivilegeGroupPrivileges("CollectionAdmin"))
|
||||
}
|
||||
|
||||
func TestRbacConfig_Override(t *testing.T) {
|
||||
func TestRbacConfig_OverridePrivileges(t *testing.T) {
|
||||
params := ComponentParam{}
|
||||
params.Init(NewBaseTable(SkipRemote(true)))
|
||||
|
||||
|
|
|
@ -10,103 +10,6 @@ import (
|
|||
"github.com/milvus-io/milvus/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
builtinPrivilegeGroups = map[string][]string{
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String()): collectionReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadWrite.String()): collectionReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionAdmin.String()): collectionAdminPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String()): databaseReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadWrite.String()): databaseReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseAdmin.String()): databaseAdminPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String()): clusterReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadWrite.String()): clusterReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterAdmin.String()): clusterAdminPrivilegeGroup,
|
||||
}
|
||||
|
||||
collectionReadOnlyPrivilegeGroup = []string{
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeQuery.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSearch.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeIndexDetail.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetFlushState.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadState.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadingProgress.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeHasPartition.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowPartitions.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeCollection.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeAlias.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetStatistics.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListAliases.String()),
|
||||
}
|
||||
|
||||
collectionReadWritePrivilegeGroup = append(collectionReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoad.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRelease.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeInsert.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDelete.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpsert.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeImport.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlush.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCompaction.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoadBalance.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateIndex.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropIndex.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePartition.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPartition.String()),
|
||||
)
|
||||
|
||||
collectionAdminPrivilegeGroup = append(collectionReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateAlias.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropAlias.String()),
|
||||
)
|
||||
|
||||
databaseReadOnlyPrivilegeGroup = []string{
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowCollections.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeDatabase.String()),
|
||||
}
|
||||
|
||||
databaseReadWritePrivilegeGroup = append(databaseReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String()),
|
||||
)
|
||||
|
||||
databaseAdminPrivilegeGroup = append(databaseReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateCollection.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropCollection.String()),
|
||||
)
|
||||
|
||||
clusterReadOnlyPrivilegeGroup = []string{
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListDatabases.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectOwnership.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectUser.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeResourceGroup.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListResourceGroups.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListPrivilegeGroups.String()),
|
||||
}
|
||||
|
||||
clusterReadWritePrivilegeGroup = append(clusterReadOnlyPrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlushAll.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferNode.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferReplica.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateResourceGroups.String()),
|
||||
)
|
||||
|
||||
clusterAdminPrivilegeGroup = append(clusterReadWritePrivilegeGroup,
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeBackupRBAC.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRestoreRBAC.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropDatabase.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateOwnership.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropOwnership.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeManageOwnership.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateResourceGroup.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropResourceGroup.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRenameCollection.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePrivilegeGroup.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPrivilegeGroup.String()),
|
||||
util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeOperatePrivilegeGroup.String()),
|
||||
)
|
||||
)
|
||||
|
||||
type rbacConfig struct {
|
||||
Enabled ParamItem `refreshable:"false"`
|
||||
ClusterReadOnlyPrivileges ParamItem `refreshable:"false"`
|
||||
|
@ -134,7 +37,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.ClusterReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.readonly.privileges",
|
||||
DefaultValue: strings.Join(clusterReadOnlyPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.ClusterReadOnlyPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level readonly privileges",
|
||||
Export: true,
|
||||
|
@ -143,7 +46,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.ClusterReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.readwrite.privileges",
|
||||
DefaultValue: strings.Join(clusterReadWritePrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.ClusterReadWritePrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level readwrite privileges",
|
||||
Export: true,
|
||||
|
@ -152,7 +55,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.ClusterAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.cluster.admin.privileges",
|
||||
DefaultValue: strings.Join(clusterAdminPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.ClusterAdminPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Cluster level admin privileges",
|
||||
Export: true,
|
||||
|
@ -161,7 +64,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.DBReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.readonly.privileges",
|
||||
DefaultValue: strings.Join(databaseReadOnlyPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.DatabaseReadOnlyPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level readonly privileges",
|
||||
Export: true,
|
||||
|
@ -170,7 +73,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.DBReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.readwrite.privileges",
|
||||
DefaultValue: strings.Join(databaseReadWritePrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.DatabaseReadWritePrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level readwrite privileges",
|
||||
Export: true,
|
||||
|
@ -179,7 +82,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.DBAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.database.admin.privileges",
|
||||
DefaultValue: strings.Join(databaseAdminPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.DatabaseAdminPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Database level admin privileges",
|
||||
Export: true,
|
||||
|
@ -188,7 +91,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.CollectionReadOnlyPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.readonly.privileges",
|
||||
DefaultValue: strings.Join(collectionReadOnlyPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.CollectionReadOnlyPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level readonly privileges",
|
||||
Export: true,
|
||||
|
@ -197,7 +100,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.CollectionReadWritePrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.readwrite.privileges",
|
||||
DefaultValue: strings.Join(collectionReadWritePrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.CollectionReadWritePrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level readwrite privileges",
|
||||
Export: true,
|
||||
|
@ -206,7 +109,7 @@ func (p *rbacConfig) init(base *BaseTable) {
|
|||
|
||||
p.CollectionAdminPrivileges = ParamItem{
|
||||
Key: "common.security.rbac.collection.admin.privileges",
|
||||
DefaultValue: strings.Join(collectionAdminPrivilegeGroup, ","),
|
||||
DefaultValue: strings.Join(util.CollectionAdminPrivileges, ","),
|
||||
Version: "2.4.16",
|
||||
Doc: "Collection level admin privileges",
|
||||
Export: true,
|
||||
|
@ -219,15 +122,15 @@ func (p *rbacConfig) GetDefaultPrivilegeGroups() []*milvuspb.PrivilegeGroupInfo
|
|||
GroupName string
|
||||
Privileges func() []string
|
||||
}{
|
||||
{"ClusterReadOnly", p.ClusterReadOnlyPrivileges.GetAsStrings},
|
||||
{"ClusterReadWrite", p.ClusterReadWritePrivileges.GetAsStrings},
|
||||
{"ClusterAdmin", p.ClusterAdminPrivileges.GetAsStrings},
|
||||
{"DatabaseReadOnly", p.DBReadOnlyPrivileges.GetAsStrings},
|
||||
{"DatabaseReadWrite", p.DBReadWritePrivileges.GetAsStrings},
|
||||
{"DatabaseAdmin", p.DBAdminPrivileges.GetAsStrings},
|
||||
{"CollectionReadOnly", p.CollectionReadOnlyPrivileges.GetAsStrings},
|
||||
{"CollectionReadWrite", p.CollectionReadWritePrivileges.GetAsStrings},
|
||||
{"CollectionAdmin", p.CollectionAdminPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String()), p.ClusterReadOnlyPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadWrite.String()), p.ClusterReadWritePrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterAdmin.String()), p.ClusterAdminPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String()), p.DBReadOnlyPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadWrite.String()), p.DBReadWritePrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseAdmin.String()), p.DBAdminPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String()), p.CollectionReadOnlyPrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadWrite.String()), p.CollectionReadWritePrivileges.GetAsStrings},
|
||||
{util.MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionAdmin.String()), p.CollectionAdminPrivileges.GetAsStrings},
|
||||
}
|
||||
|
||||
builtinGroups := make([]*milvuspb.PrivilegeGroupInfo, 0, len(privilegeGroupConfigs))
|
||||
|
@ -245,21 +148,34 @@ func (p *rbacConfig) GetDefaultPrivilegeGroups() []*milvuspb.PrivilegeGroupInfo
|
|||
|
||||
func (p *rbacConfig) GetDefaultPrivilegeGroup(privName string) *milvuspb.PrivilegeGroupInfo {
|
||||
for _, group := range p.GetDefaultPrivilegeGroups() {
|
||||
if group.GroupName == privName {
|
||||
if group.GetGroupName() == privName {
|
||||
return group
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *rbacConfig) GetDefaultPrivilegeGroupPrivileges(groupName string) []string {
|
||||
group := p.GetDefaultPrivilegeGroup(groupName)
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
return lo.Map(group.GetPrivileges(), func(priv *milvuspb.PrivilegeEntity, _ int) string {
|
||||
return priv.GetName()
|
||||
})
|
||||
}
|
||||
|
||||
func (p *rbacConfig) GetDefaultPrivilegeGroupNames() []string {
|
||||
return lo.Keys(builtinPrivilegeGroups)
|
||||
return lo.Map(p.GetDefaultPrivilegeGroups(), func(group *milvuspb.PrivilegeGroupInfo, _ int) string {
|
||||
return group.GroupName
|
||||
})
|
||||
}
|
||||
|
||||
func (p *rbacConfig) IsCollectionPrivilegeGroup(privName string) bool {
|
||||
collectionPrivilegeGroups := lo.PickBy(builtinPrivilegeGroups, func(groupName string, _ []string) bool {
|
||||
return strings.Contains(groupName, "Collection")
|
||||
})
|
||||
_, exists := collectionPrivilegeGroups[privName]
|
||||
return exists
|
||||
for _, groupName := range p.GetDefaultPrivilegeGroupNames() {
|
||||
if strings.Contains(groupName, milvuspb.PrivilegeLevel_Collection.String()) && groupName == privName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -268,7 +268,8 @@ func (s *PrivilegeGroupTestSuite) TestGrantV2CustomPrivilegeGroup() {
|
|||
selectResp, _ = s.validateGrants(ctx, role, commonpb.ObjectType_Global.String(), util.AnyWord, util.AnyWord)
|
||||
s.Len(selectResp.GetEntities(), 2)
|
||||
|
||||
// add different object type privileges to group1 is not allowed
|
||||
// add different privilege group level privileges to group1 is not allowed
|
||||
s.Equal(milvuspb.PrivilegeLevel_Database.String(), util.GetPrivilegeLevel("CreateCollection"))
|
||||
resp, _ = s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: "group1",
|
||||
Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup,
|
||||
|
|
|
@ -104,9 +104,9 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
|||
createRole(roleName)
|
||||
|
||||
// grant collection level search privilege to role test_role
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Grant)
|
||||
|
||||
// create privielge group test_group
|
||||
// create privilege group test_group
|
||||
groupName := "test_group"
|
||||
createPrivGroupResp, err := s.Cluster.Proxy.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{
|
||||
GroupName: groupName,
|
||||
|
@ -114,6 +114,11 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
|||
s.NoError(err)
|
||||
s.True(merr.Ok(createPrivGroupResp))
|
||||
|
||||
collectionPrivileges := []*milvuspb.PrivilegeEntity{{Name: "Query"}, {Name: "Insert"}}
|
||||
for _, p := range collectionPrivileges {
|
||||
s.Equal(milvuspb.PrivilegeLevel_Collection.String(), util.GetPrivilegeLevel(p.Name))
|
||||
}
|
||||
|
||||
// add query and insert privilege to group test_group
|
||||
addPrivsToGroupResp, err := s.Cluster.Proxy.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{
|
||||
GroupName: groupName,
|
||||
|
@ -124,7 +129,7 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
|||
s.True(merr.Ok(addPrivsToGroupResp))
|
||||
|
||||
// grant privilege group test_group to role test_role
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Grant)
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Grant)
|
||||
|
||||
userName := "test_user"
|
||||
passwd := "test_passwd"
|
||||
|
@ -166,10 +171,10 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
|||
s.False(merr.Ok(restoreRBACResp))
|
||||
|
||||
// revoke privilege search from role test_role before dropping the role
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Revoke)
|
||||
|
||||
// revoke privilege group test_group from role test_role before dropping the role
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Revoke)
|
||||
|
||||
// drop privilege group test_group
|
||||
dropPrivGroupResp, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
|
@ -206,9 +211,9 @@ func (s *RBACBackupTestSuite) TestBackup() {
|
|||
s.Equal(backupRBACResp2.GetRBACMeta().String(), backupRBACResp.GetRBACMeta().String())
|
||||
|
||||
// clean rbac meta
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
operatePrivilege(roleName, "Search", util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Revoke)
|
||||
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.AnyWord, milvuspb.OperatePrivilegeType_Revoke)
|
||||
operatePrivilege(roleName, groupName, util.AnyWord, util.DefaultDBName, milvuspb.OperatePrivilegeType_Revoke)
|
||||
|
||||
dropPrivGroupResp2, err := s.Cluster.Proxy.DropPrivilegeGroup(ctx, &milvuspb.DropPrivilegeGroupRequest{
|
||||
GroupName: groupName,
|
||||
|
|
|
@ -5384,7 +5384,7 @@ class TestUtilityNegativeRbacPrivilegeGroup(TestcaseBase):
|
|||
name = "privilege_group_1"
|
||||
self.utility_wrap.create_privilege_group(privilege_group=name)
|
||||
privilege_name = "invalid_privilege"
|
||||
error = {"err_code": 1100, "err_msg": f"there is no privilege name or privielge group name [{privilege_name}] "
|
||||
error = {"err_code": 1100, "err_msg": f"there is no privilege name or privilege group name [{privilege_name}] "
|
||||
f"defined in system to operate: invalid parameter"}
|
||||
self.utility_wrap.add_privileges_to_group(privilege_group="privilege_group_1", privileges=[privilege_name], check_task=CheckTasks.err_res, check_items=error)
|
||||
|
||||
|
@ -5483,7 +5483,7 @@ class TestUtilityNegativeRbacPrivilegeGroup(TestcaseBase):
|
|||
name = "privilege_group_1"
|
||||
self.utility_wrap.create_privilege_group(privilege_group=name)
|
||||
privilege_name = "invalid_privilege"
|
||||
error = {"err_code": 1100, "err_msg": f"there is no privilege name or privielge group name [{privilege_name}] "
|
||||
error = {"err_code": 1100, "err_msg": f"there is no privilege name or privilege group name [{privilege_name}] "
|
||||
f"defined in system to operate: invalid parameter"}
|
||||
self.utility_wrap.remove_privileges_from_group(privilege_group="privilege_group_1", privileges=[privilege_name],
|
||||
check_task=CheckTasks.err_res, check_items=error)
|
||||
|
|
Loading…
Reference in New Issue