enhance: add 3 builtin roles (#28961)

issue: #28960 [milvus-proto
#212](https://github.com/milvus-io/milvus-proto/issues/212)

add new configuration: builtinRoles
user can define roles in config file: `milvus.yaml`

there is an example:
1. db_ro, only have read privileges, include load
2. db_rw, read and write privileges, include create/drop/rename
collection
3. db_admin, not only read and write privileges, but also user
administration

Signed-off-by: PowderLi <min.li@zilliz.com>
pull/29293/head
PowderLi 2023-12-18 14:28:41 +08:00 committed by GitHub
parent a602171d06
commit bcd6865b29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 305 additions and 5 deletions

2
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/klauspost/compress v1.16.7 github.com/klauspost/compress v1.16.7
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f
github.com/milvus-io/milvus/pkg v0.0.1 github.com/milvus-io/milvus/pkg v0.0.1
github.com/minio/minio-go/v7 v7.0.61 github.com/minio/minio-go/v7 v7.0.61
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0

2
go.sum
View File

@ -583,6 +583,8 @@ github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZz
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= 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.20231114080011-9a495865219e h1:IH1WAXwEF8vbwahPdupi4zzRNWViT4B7fZzIjtRLpG4= github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e h1:IH1WAXwEF8vbwahPdupi4zzRNWViT4B7fZzIjtRLpG4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f h1:0cAMN9OsgBxlEUY8i1e1ocrBZ/cpu/Kdguz4JWz9fUc=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek=
github.com/milvus-io/milvus-storage/go v0.0.0-20231109072809-1cd7b0866092 h1:UYJ7JB+QlMOoFHNdd8mUa3/lV63t9dnBX7ILXmEEWPY= github.com/milvus-io/milvus-storage/go v0.0.0-20231109072809-1cd7b0866092 h1:UYJ7JB+QlMOoFHNdd8mUa3/lV63t9dnBX7ILXmEEWPY=
github.com/milvus-io/milvus-storage/go v0.0.0-20231109072809-1cd7b0866092/go.mod h1:GPETMcTZq1gLY1WA6Na5kiNAKnq8SEMMiVKUZrM3sho= github.com/milvus-io/milvus-storage/go v0.0.0-20231109072809-1cd7b0866092/go.mod h1:GPETMcTZq1gLY1WA6Na5kiNAKnq8SEMMiVKUZrM3sho=
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A= github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=

View File

@ -821,6 +821,11 @@ func (s *Server) CreateIndex(ctx context.Context, request *milvuspb.CreateIndexR
return s.proxy.CreateIndex(ctx, request) return s.proxy.CreateIndex(ctx, request)
} }
func (s *Server) AlterIndex(ctx context.Context, request *milvuspb.AlterIndexRequest) (*commonpb.Status, error) {
// Todo
return nil, nil
}
// DropIndex notifies Proxy to drop index // DropIndex notifies Proxy to drop index
func (s *Server) DropIndex(ctx context.Context, request *milvuspb.DropIndexRequest) (*commonpb.Status, error) { func (s *Server) DropIndex(ctx context.Context, request *milvuspb.DropIndexRequest) (*commonpb.Status, error) {
return s.proxy.DropIndex(ctx, request) return s.proxy.DropIndex(ctx, request)
@ -865,6 +870,11 @@ func (s *Server) Search(ctx context.Context, request *milvuspb.SearchRequest) (*
return s.proxy.Search(ctx, request) return s.proxy.Search(ctx, request)
} }
func (s *Server) SearchV2(ctx context.Context, request *milvuspb.SearchRequestV2) (*milvuspb.SearchResults, error) {
// Todo
return nil, nil
}
func (s *Server) Flush(ctx context.Context, request *milvuspb.FlushRequest) (*milvuspb.FlushResponse, error) { func (s *Server) Flush(ctx context.Context, request *milvuspb.FlushRequest) (*milvuspb.FlushResponse, error) {
return s.proxy.Flush(ctx, request) return s.proxy.Flush(ctx, request)
} }

View File

@ -639,6 +639,11 @@ func (_c *MockProxy_CreateIndex_Call) RunAndReturn(run func(context.Context, *mi
return _c return _c
} }
func (_m *MockProxy) AlterIndex(_a0 context.Context, _a1 *milvuspb.AlterIndexRequest) (*commonpb.Status, error) {
// Todo
return nil, nil
}
// CreatePartition provides a mock function with given fields: _a0, _a1 // CreatePartition provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) CreatePartition(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest) (*commonpb.Status, error) { func (_m *MockProxy) CreatePartition(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
@ -4555,6 +4560,11 @@ func (_c *MockProxy_Search_Call) RunAndReturn(run func(context.Context, *milvusp
return _c return _c
} }
func (_m *MockProxy) SearchV2(_a0 context.Context, _a1 *milvuspb.SearchRequestV2) (*milvuspb.SearchResults, error) {
// Todo
return nil, nil
}
// SelectGrant provides a mock function with given fields: _a0, _a1 // SelectGrant provides a mock function with given fields: _a0, _a1
func (_m *MockProxy) SelectGrant(_a0 context.Context, _a1 *milvuspb.SelectGrantRequest) (*milvuspb.SelectGrantResponse, error) { func (_m *MockProxy) SelectGrant(_a0 context.Context, _a1 *milvuspb.SelectGrantRequest) (*milvuspb.SelectGrantResponse, error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)

View File

@ -1814,6 +1814,11 @@ func (node *Proxy) CreateIndex(ctx context.Context, request *milvuspb.CreateInde
return cit.result, nil return cit.result, nil
} }
func (node *Proxy) AlterIndex(ctx context.Context, request *milvuspb.AlterIndexRequest) (*commonpb.Status, error) {
// Todo
return nil, nil
}
// DescribeIndex get the meta information of index, such as index state, index id and etc. // DescribeIndex get the meta information of index, such as index state, index id and etc.
func (node *Proxy) DescribeIndex(ctx context.Context, request *milvuspb.DescribeIndexRequest) (*milvuspb.DescribeIndexResponse, error) { func (node *Proxy) DescribeIndex(ctx context.Context, request *milvuspb.DescribeIndexRequest) (*milvuspb.DescribeIndexResponse, error) {
if err := merr.CheckHealthy(node.GetStateCode()); err != nil { if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
@ -2649,6 +2654,11 @@ func (node *Proxy) Search(ctx context.Context, request *milvuspb.SearchRequest)
return qt.result, nil return qt.result, nil
} }
func (node *Proxy) SearchV2(ctx context.Context, request *milvuspb.SearchRequestV2) (*milvuspb.SearchResults, error) {
// Todo
return nil, nil
}
func (node *Proxy) getVectorPlaceholderGroupForSearchByPks(ctx context.Context, request *milvuspb.SearchRequest) ([]byte, error) { func (node *Proxy) getVectorPlaceholderGroupForSearchByPks(ctx context.Context, request *milvuspb.SearchRequest) ([]byte, error) {
placeholderGroup := &commonpb.PlaceholderGroup{} placeholderGroup := &commonpb.PlaceholderGroup{}
err := proto.Unmarshal(request.PlaceholderGroup, placeholderGroup) err := proto.Unmarshal(request.PlaceholderGroup, placeholderGroup)
@ -4223,7 +4233,7 @@ func (node *Proxy) DropRole(ctx context.Context, req *milvuspb.DropRoleRequest)
return merr.Status(err), nil return merr.Status(err), nil
} }
if IsDefaultRole(req.RoleName) { if IsDefaultRole(req.RoleName) {
err := merr.WrapErrPrivilegeNotPermitted("the role[%s] is a default role, which can't be droped", req.GetRoleName()) err := merr.WrapErrPrivilegeNotPermitted("the role[%s] is a default role, which can't be dropped", req.GetRoleName())
return merr.Status(err), nil return merr.Status(err), nil
} }
result, err := node.rootCoord.DropRole(ctx, req) result, err := node.rootCoord.DropRole(ctx, req)

View File

@ -168,7 +168,7 @@ func PrivilegeInterceptor(ctx context.Context, req interface{}) (context.Context
} }
log.Info("permission deny", zap.Strings("roles", roleNames)) log.Info("permission deny", zap.Strings("roles", roleNames))
return ctx, status.Error(codes.PermissionDenied, fmt.Sprintf("%s: permission deny", objectPrivilege)) return ctx, status.Error(codes.PermissionDenied, fmt.Sprintf("%s: permission deny to %s", objectPrivilege, username))
} }
// isCurUserObject Determine whether it is an Object of type User that operates on its own user information, // isCurUserObject Determine whether it is an Object of type User that operates on its own user information,

View File

@ -613,6 +613,43 @@ func (c *Core) initRbac() error {
return errors.Wrap(err, "failed to grant collection privilege") return errors.Wrap(err, "failed to grant collection privilege")
} }
} }
if Params.RoleCfg.Enabled.GetAsBool() {
return c.initBuiltinRoles()
}
return nil
}
func (c *Core) initBuiltinRoles() error {
rolePrivilegesMap := Params.RoleCfg.Roles.GetAsRoleDetails()
for role, privilegesJSON := range rolePrivilegesMap {
err := c.meta.CreateRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: role})
if err != nil && !common.IsIgnorableError(err) {
log.Error("create a builtin role fail", zap.Any("error", err), zap.String("roleName", role))
return errors.Wrapf(err, "failed to create a builtin role: %s", role)
}
for _, privilege := range privilegesJSON[util.RoleConfigPrivileges] {
privilegeName := privilege[util.RoleConfigPrivilege]
if !util.IsAnyWord(privilege[util.RoleConfigPrivilege]) {
privilegeName = util.PrivilegeNameForMetastore(privilege[util.RoleConfigPrivilege])
}
err := c.meta.OperatePrivilege(util.DefaultTenant, &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: role},
Object: &milvuspb.ObjectEntity{Name: privilege[util.RoleConfigObjectType]},
ObjectName: privilege[util.RoleConfigObjectName],
DbName: privilege[util.RoleConfigDBName],
Grantor: &milvuspb.GrantorEntity{
User: &milvuspb.UserEntity{Name: util.UserRoot},
Privilege: &milvuspb.PrivilegeEntity{Name: privilegeName},
},
}, milvuspb.OperatePrivilegeType_Grant)
if err != nil && !common.IsIgnorableError(err) {
log.Error("grant privilege to builtin role fail", zap.Any("error", err), zap.String("roleName", role), zap.Any("privilege", privilege))
return errors.Wrapf(err, "failed to grant privilege: <%s, %s, %s> of db: %s to role: %s", privilege[util.RoleConfigObjectType], privilege[util.RoleConfigObjectName], privilege[util.RoleConfigPrivilege], privilege[util.RoleConfigDBName], role)
}
}
util.BuiltinRoles = append(util.BuiltinRoles, role)
log.Info("init a builtin role successfully", zap.String("roleName", role))
}
return nil return nil
} }
@ -2268,6 +2305,10 @@ func (c *Core) DropRole(ctx context.Context, in *milvuspb.DropRoleRequest) (*com
if err := merr.CheckHealthy(c.GetStateCode()); err != nil { if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
return merr.Status(err), nil return merr.Status(err), nil
} }
for util.IsBuiltinRole(in.GetRoleName()) {
err := merr.WrapErrPrivilegeNotPermitted("the role[%s] is a builtin role, which can't be dropped", in.GetRoleName())
return merr.Status(err), nil
}
if _, err := c.meta.SelectRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: in.RoleName}, false); err != nil { if _, err := c.meta.SelectRole(util.DefaultTenant, &milvuspb.RoleEntity{Name: in.RoleName}, false); err != nil {
errMsg := "not found the role, maybe the role isn't existed or internal system error" errMsg := "not found the role, maybe the role isn't existed or internal system error"
ctxLog.Warn(errMsg, zap.Error(err)) ctxLog.Warn(errMsg, zap.Error(err))

View File

@ -49,6 +49,7 @@ import (
"github.com/milvus-io/milvus/internal/util/importutil" "github.com/milvus-io/milvus/internal/util/importutil"
"github.com/milvus-io/milvus/internal/util/sessionutil" "github.com/milvus-io/milvus/internal/util/sessionutil"
"github.com/milvus-io/milvus/pkg/common" "github.com/milvus-io/milvus/pkg/common"
"github.com/milvus-io/milvus/pkg/util"
"github.com/milvus-io/milvus/pkg/util/etcd" "github.com/milvus-io/milvus/pkg/util/etcd"
"github.com/milvus-io/milvus/pkg/util/funcutil" "github.com/milvus-io/milvus/pkg/util/funcutil"
"github.com/milvus-io/milvus/pkg/util/merr" "github.com/milvus-io/milvus/pkg/util/merr"
@ -2087,6 +2088,51 @@ func TestRootCoord_RBACError(t *testing.T) {
}) })
} }
func TestRootCoord_BuiltinRoles(t *testing.T) {
roleDbAdmin := "db_admin"
paramtable.Init()
paramtable.Get().Save(paramtable.Get().RoleCfg.Enabled.Key, "true")
paramtable.Get().Save(paramtable.Get().RoleCfg.Roles.Key, `{"`+roleDbAdmin+`": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}}`)
t.Run("init builtin roles success", func(t *testing.T) {
c := newTestCore(withHealthyCode(), withInvalidMeta())
mockMeta := c.meta.(*mockMetaTable)
mockMeta.CreateRoleFunc = func(tenant string, entity *milvuspb.RoleEntity) error {
return nil
}
mockMeta.OperatePrivilegeFunc = func(tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
return nil
}
err := c.initBuiltinRoles()
assert.Equal(t, nil, err)
assert.True(t, util.IsBuiltinRole(roleDbAdmin))
assert.False(t, util.IsBuiltinRole(util.RoleAdmin))
resp, err := c.DropRole(context.Background(), &milvuspb.DropRoleRequest{RoleName: roleDbAdmin})
assert.Equal(t, nil, err)
assert.Equal(t, int32(1401), resp.Code) // merr.ErrPrivilegeNotPermitted
})
t.Run("init builtin roles fail to create role", func(t *testing.T) {
c := newTestCore(withHealthyCode(), withInvalidMeta())
mockMeta := c.meta.(*mockMetaTable)
mockMeta.CreateRoleFunc = func(tenant string, entity *milvuspb.RoleEntity) error {
return merr.ErrPrivilegeNotPermitted
}
err := c.initBuiltinRoles()
assert.Error(t, err)
})
t.Run("init builtin roles fail to operate privileg", func(t *testing.T) {
c := newTestCore(withHealthyCode(), withInvalidMeta())
mockMeta := c.meta.(*mockMetaTable)
mockMeta.CreateRoleFunc = func(tenant string, entity *milvuspb.RoleEntity) error {
return nil
}
mockMeta.OperatePrivilegeFunc = func(tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
return merr.ErrPrivilegeNotPermitted
}
err := c.initBuiltinRoles()
assert.Error(t, err)
})
}
func TestCore_Stop(t *testing.T) { func TestCore_Stop(t *testing.T) {
t.Run("abnormal stop before component is ready", func(t *testing.T) { t.Run("abnormal stop before component is ready", func(t *testing.T) {
c := &Core{} c := &Core{}

View File

@ -13,7 +13,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/klauspost/compress v1.16.5 github.com/klauspost/compress v1.16.5
github.com/lingdor/stackerror v0.0.0-20191119040541-976d8885ed76 github.com/lingdor/stackerror v0.0.0-20191119040541-976d8885ed76
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f
github.com/nats-io/nats-server/v2 v2.9.17 github.com/nats-io/nats-server/v2 v2.9.17
github.com/nats-io/nats.go v1.24.0 github.com/nats-io/nats.go v1.24.0
github.com/panjf2000/ants/v2 v2.7.2 github.com/panjf2000/ants/v2 v2.7.2

View File

@ -481,6 +481,8 @@ github.com/milvus-io/milvus-proto/go-api/v2 v2.3.2-0.20231008032233-5d64d443769d
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.2-0.20231008032233-5d64d443769d/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= github.com/milvus-io/milvus-proto/go-api/v2 v2.3.2-0.20231008032233-5d64d443769d/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e h1:IH1WAXwEF8vbwahPdupi4zzRNWViT4B7fZzIjtRLpG4= github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e h1:IH1WAXwEF8vbwahPdupi4zzRNWViT4B7fZzIjtRLpG4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231114080011-9a495865219e/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f h1:0cAMN9OsgBxlEUY8i1e1ocrBZ/cpu/Kdguz4JWz9fUc=
github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20231208092431-02cbad30332f/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek=
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A= github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=
github.com/milvus-io/pulsar-client-go v0.6.10/go.mod h1:lQqCkgwDF8YFYjKA+zOheTk1tev2B+bKj5j7+nm8M1w= github.com/milvus-io/pulsar-client-go v0.6.10/go.mod h1:lQqCkgwDF8YFYjKA+zOheTk1tev2B+bKj5j7+nm8M1w=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=

View File

@ -61,6 +61,12 @@ const (
IdentifierKey = "identifier" IdentifierKey = "identifier"
HeaderDBName = "dbName" HeaderDBName = "dbName"
RoleConfigPrivileges = "privileges"
RoleConfigObjectType = "object_type"
RoleConfigObjectName = "object_name"
RoleConfigDBName = "db_name"
RoleConfigPrivilege = "privilege"
) )
const ( const (
@ -70,6 +76,7 @@ const (
var ( var (
DefaultRoles = []string{RoleAdmin, RolePublic} DefaultRoles = []string{RoleAdmin, RolePublic}
BuiltinRoles = []string{}
ObjectPrivileges = map[string][]string{ ObjectPrivileges = map[string][]string{
commonpb.ObjectType_Collection.String(): { commonpb.ObjectType_Collection.String(): {
@ -118,6 +125,12 @@ var (
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropDatabase.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropDatabase.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListDatabases.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListDatabases.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePartition.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPartition.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowPartitions.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeHasPartition.String()),
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetFlushState.String()),
}, },
commonpb.ObjectType_User.String(): { commonpb.ObjectType_User.String(): {
MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()), MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()),
@ -169,3 +182,12 @@ func PrivilegeNameForMetastore(name string) string {
func IsAnyWord(word string) bool { func IsAnyWord(word string) bool {
return word == AnyWord return word == AnyWord
} }
func IsBuiltinRole(roleName string) bool {
for _, builtinRole := range BuiltinRoles {
if builtinRole == roleName {
return true
}
}
return false
}

View File

@ -35,6 +35,7 @@ import (
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb" "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-proto/go-api/v2/milvuspb"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb" "github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
"github.com/milvus-io/milvus/pkg/util"
"github.com/milvus-io/milvus/pkg/util/typeutil" "github.com/milvus-io/milvus/pkg/util/typeutil"
) )
@ -93,6 +94,34 @@ func MapToJSON(m map[string]string) []byte {
return bs return bs
} }
func JSONToRoleDetails(mStr string) (map[string](map[string]([](map[string]string))), error) {
buffer := make(map[string](map[string]([](map[string]string))), 0)
err := json.Unmarshal([]byte(mStr), &buffer)
if err != nil {
return nil, fmt.Errorf("unmarshal `builtinRoles.Roles` failed, %w", err)
}
ret := make(map[string](map[string]([](map[string]string))), 0)
for role, privilegesJSON := range buffer {
ret[role] = make(map[string]([](map[string]string)), 0)
privilegesArray := make([]map[string]string, 0)
for _, privileges := range privilegesJSON[util.RoleConfigPrivileges] {
privilegesArray = append(privilegesArray, map[string]string{
util.RoleConfigObjectType: privileges[util.RoleConfigObjectType],
util.RoleConfigObjectName: privileges[util.RoleConfigObjectName],
util.RoleConfigPrivilege: privileges[util.RoleConfigPrivilege],
util.RoleConfigDBName: privileges[util.RoleConfigDBName],
})
}
ret[role]["privileges"] = privilegesArray
}
return ret, nil
}
func RoleDetailsToJSON(m map[string](map[string]([](map[string]string)))) []byte {
bs, _ := json.Marshal(m)
return bs
}
const ( const (
// PulsarMaxMessageSizeKey is the key of config item // PulsarMaxMessageSizeKey is the key of config item
PulsarMaxMessageSizeKey = "maxMessageSize" PulsarMaxMessageSizeKey = "maxMessageSize"

View File

@ -33,6 +33,7 @@ import (
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb" "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-proto/go-api/v2/milvuspb"
"github.com/milvus-io/milvus/pkg/util"
) )
func Test_CheckGrpcReady(t *testing.T) { func Test_CheckGrpcReady(t *testing.T) {
@ -89,6 +90,25 @@ func Test_ParseIndexParamsMap(t *testing.T) {
assert.NotEqual(t, err, nil) assert.NotEqual(t, err, nil)
} }
func Test_ParseBuiltinRolesMap(t *testing.T) {
t.Run("correct format", func(t *testing.T) {
builtinRoles := `{"db_admin": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}}`
rolePrivilegesMap, err := JSONToRoleDetails(builtinRoles)
assert.Nil(t, err)
for role, privilegesJSON := range rolePrivilegesMap {
assert.Contains(t, []string{"db_admin", "db_rw", "db_ro"}, role)
for _, privileges := range privilegesJSON[util.RoleConfigPrivileges] {
assert.Equal(t, privileges[util.RoleConfigObjectType], "Global")
}
}
})
t.Run("wrong format", func(t *testing.T) {
builtinRoles := `{"db_admin": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}`
_, err := JSONToRoleDetails(builtinRoles)
assert.NotNil(t, err)
})
}
func TestGetAttrByKeyFromRepeatedKV(t *testing.T) { func TestGetAttrByKeyFromRepeatedKV(t *testing.T) {
kvs := []*commonpb.KeyValuePair{ kvs := []*commonpb.KeyValuePair{
{Key: "Key1", Value: "Value1"}, {Key: "Key1", Value: "Value1"},

View File

@ -20,7 +20,7 @@ func Test_GetPrivilegeExtObj(t *testing.T) {
assert.Equal(t, commonpb.ObjectPrivilege_PrivilegeLoad, privilegeExt.ObjectPrivilege) assert.Equal(t, commonpb.ObjectPrivilege_PrivilegeLoad, privilegeExt.ObjectPrivilege)
assert.Equal(t, int32(3), privilegeExt.ObjectNameIndex) assert.Equal(t, int32(3), privilegeExt.ObjectNameIndex)
request2 := &milvuspb.CreatePartitionRequest{} request2 := &milvuspb.GetPartitionStatisticsRequest{}
_, err = GetPrivilegeExtObj(request2) _, err = GetPrivilegeExtObj(request2)
assert.Error(t, err) assert.Error(t, err)
} }

View File

@ -69,6 +69,7 @@ type ComponentParam struct {
IndexNodeCfg indexNodeConfig IndexNodeCfg indexNodeConfig
HTTPCfg httpConfig HTTPCfg httpConfig
LogCfg logConfig LogCfg logConfig
RoleCfg roleConfig
RootCoordGrpcServerCfg GrpcServerConfig RootCoordGrpcServerCfg GrpcServerConfig
ProxyGrpcServerCfg GrpcServerConfig ProxyGrpcServerCfg GrpcServerConfig
@ -116,6 +117,7 @@ func (p *ComponentParam) init(bt *BaseTable) {
p.IndexNodeCfg.init(bt) p.IndexNodeCfg.init(bt)
p.HTTPCfg.init(bt) p.HTTPCfg.init(bt)
p.LogCfg.init(bt) p.LogCfg.init(bt)
p.RoleCfg.init(bt)
p.RootCoordGrpcServerCfg.Init("rootCoord", bt) p.RootCoordGrpcServerCfg.Init("rootCoord", bt)
p.ProxyGrpcServerCfg.Init("proxy", bt) p.ProxyGrpcServerCfg.Init("proxy", bt)

View File

@ -141,6 +141,10 @@ func (pi *ParamItem) GetAsJSONMap() map[string]string {
return getAndConvert(pi.GetValue(), funcutil.JSONToMap, nil) return getAndConvert(pi.GetValue(), funcutil.JSONToMap, nil)
} }
func (pi *ParamItem) GetAsRoleDetails() map[string](map[string]([](map[string]string))) {
return getAndConvert(pi.GetValue(), funcutil.JSONToRoleDetails, nil)
}
type CompositeParamItem struct { type CompositeParamItem struct {
Items []*ParamItem Items []*ParamItem
Format func(map[string]string) string Format func(map[string]string) string

View File

@ -0,0 +1,45 @@
package paramtable
import (
"github.com/milvus-io/milvus/pkg/config"
"github.com/milvus-io/milvus/pkg/util/funcutil"
)
type roleConfig struct {
Enabled ParamItem `refreshable:"false"`
Roles ParamItem `refreshable:"false"`
}
func (p *roleConfig) init(base *BaseTable) {
p.Enabled = ParamItem{
Key: "builtinRoles.enable",
DefaultValue: "false",
Version: "2.3.4",
Doc: "Whether to init builtin roles",
Export: true,
}
p.Enabled.Init(base.mgr)
p.Roles = ParamItem{
Key: "builtinRoles.roles",
DefaultValue: `{}`,
Version: "2.3.4",
Doc: "what builtin roles should be init",
Export: true,
}
p.Roles.Init(base.mgr)
p.panicIfNotValid(base.mgr)
}
func (p *roleConfig) panicIfNotValid(mgr *config.Manager) {
if p.Enabled.GetAsBool() {
m := p.Roles.GetAsRoleDetails()
if m == nil {
panic("builtinRoles.roles not invalid, should be json format")
}
j := funcutil.RoleDetailsToJSON(m)
mgr.SetConfig("builtinRoles.roles", string(j))
}
}

View File

@ -0,0 +1,57 @@
package paramtable
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus/pkg/config"
)
func TestRoleConfig_Init(t *testing.T) {
params := ComponentParam{}
params.Init(NewBaseTable(SkipRemote(true)))
cfg := &params.RoleCfg
assert.Equal(t, cfg.Enabled.GetAsBool(), false)
assert.Equal(t, cfg.Roles.GetValue(), "{}")
assert.Equal(t, len(cfg.Roles.GetAsJSONMap()), 0)
}
func TestRoleConfig_Invalid(t *testing.T) {
t.Run("valid roles", func(t *testing.T) {
mgr := config.NewManager()
mgr.SetConfig("builtinRoles.enable", "true")
mgr.SetConfig("builtinRoles.roles", `{"db_admin": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}}`)
p := &roleConfig{
Enabled: ParamItem{
Key: "builtinRoles.enable",
},
Roles: ParamItem{
Key: "builtinRoles.roles",
},
}
p.Enabled.Init(mgr)
p.Roles.Init(mgr)
assert.NotPanics(t, func() {
p.panicIfNotValid(mgr)
})
})
t.Run("invalid roles", func(t *testing.T) {
mgr := config.NewManager()
mgr.SetConfig("builtinRoles.enable", "true")
mgr.SetConfig("builtinRoles.roles", `{"db_admin": {"privileges": {"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}}}`)
p := &roleConfig{
Enabled: ParamItem{
Key: "builtinRoles.enable",
},
Roles: ParamItem{
Key: "builtinRoles.roles",
},
}
p.Enabled.Init(mgr)
p.Roles.Init(mgr)
assert.Panics(t, func() {
p.panicIfNotValid(mgr)
})
})
}