Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: [2.4] RBAC built in privilege groups and grant v2 #37787

Open
wants to merge 1 commit into
base: 2.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions configs/milvus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,30 @@ common:
# like the old password verification when updating the credential
superUsers:
defaultRootPassword: Milvus # default password for root user
rbac:
overrideBuiltInPrivilgeGroups:
enabled: false # Whether to override build-in privilege groups
cluster:
readonly:
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups # Cluster level readonly privileges
readwrite:
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,FlushAll,TransferNode,TransferReplica,UpdateResourceGroups # Cluster level readwrite privileges
admin:
privileges: ListDatabases,SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,FlushAll,TransferNode,TransferReplica,UpdateResourceGroups,BackupRBAC,RestoreRBAC,CreateDatabase,DropDatabase,CreateOwnership,DropOwnership,ManageOwnership,CreateResourceGroup,DropResourceGroup,UpdateUser # Cluster level admin privileges
database:
readonly:
privileges: ShowCollections,DescribeDatabase # Database level readonly privileges
readwrite:
privileges: ShowCollections,DescribeDatabase,AlterDatabase # Database level readwrite privileges
admin:
privileges: ShowCollections,DescribeDatabase,AlterDatabase,CreateCollection,DropCollection # Database level admin privileges
collection:
readonly:
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases # Collection level readonly privileges
readwrite:
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateIndex,DropIndex,CreatePartition,DropPartition # Collection level readwrite privileges
admin:
privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,DescribeCollection,DescribeAlias,GetStatistics,ListAliases,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateIndex,DropIndex,CreatePartition,DropPartition,CreateAlias,DropAlias # Collection level admin privileges
tlsMode: 0
session:
ttl: 30 # ttl value when session granting a lease to register service
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/klauspost/compress v1.17.9
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1
github.com/minio/minio-go/v7 v7.0.73
github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81
github.com/prometheus/client_golang v1.14.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu
github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8=
github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4=
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17 h1:ANkXdUKKpIPPQkw9pkV9ku9AEtSaPyua9XzdMTUxjCs=
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.17/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1 h1:Xp4zOR85XFFtM7Eif945BeSmDf30hbdijbeNSuy92Bg=
github.com/milvus-io/milvus-proto/go-api/v2 v2.4.18-0.20241120092224-a1c2ac2fd2c1/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs=
github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70 h1:Z+sp64fmAOxAG7mU0dfVOXvAXlwRB0c8a96rIM5HevI=
github.com/milvus-io/milvus-storage/go v0.0.0-20231227072638-ebd0b8e56d70/go.mod h1:GPETMcTZq1gLY1WA6Na5kiNAKnq8SEMMiVKUZrM3sho=
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=
Expand Down
2 changes: 2 additions & 0 deletions internal/distributed/proxy/httpserver/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const (
RevokeRoleAction = "revoke_role"
GrantPrivilegeAction = "grant_privilege"
RevokePrivilegeAction = "revoke_privilege"
GrantPrivilegeActionV2 = "grant_privilege_v2"
RevokePrivilegeActionV2 = "revoke_privilege_v2"
AlterAction = "alter"
GetProgressAction = "get_progress" // deprecated, keep it for compatibility, use `/v2/vectordb/jobs/import/describe` instead
AddPrivilegesToGroupAction = "add_privileges_to_group"
Expand Down
33 changes: 33 additions & 0 deletions internal/distributed/proxy/httpserver/handler_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func (h *HandlersV2) RegisterRoutesToV2(router gin.IRouter) {
router.POST(RoleCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.dropRole))))
router.POST(RoleCategory+GrantPrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.addPrivilegeToRole))))
router.POST(RoleCategory+RevokePrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.removePrivilegeFromRole))))
router.POST(RoleCategory+GrantPrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.grantV2))))
router.POST(RoleCategory+RevokePrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.revokeV2))))

// privilege group
router.POST(PrivilegeGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.createPrivilegeGroup))))
Expand Down Expand Up @@ -1702,6 +1704,37 @@ func (h *HandlersV2) operatePrivilegeToRole(ctx context.Context, c *gin.Context,
return resp, err
}

func (h *HandlersV2) operatePrivilegeToRoleV2(ctx context.Context, c *gin.Context, httpReq *GrantV2Req, operateType milvuspb.OperatePrivilegeType) (interface{}, error) {
req := &milvuspb.OperatePrivilegeRequest{
Entity: &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: httpReq.RoleName},
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
ObjectName: httpReq.CollectionName,
DbName: httpReq.DbName,
Grantor: &milvuspb.GrantorEntity{
Privilege: &milvuspb.PrivilegeEntity{Name: httpReq.Privilege},
},
},
Type: operateType,
Version: "v2",
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilege", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.OperatePrivilege(reqCtx, req.(*milvuspb.OperatePrivilegeRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}

func (h *HandlersV2) grantV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Grant)
}

func (h *HandlersV2) revokeV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Revoke)
}

func (h *HandlersV2) addPrivilegeToRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRole(ctx, c, anyReq.(*GrantReq), milvuspb.OperatePrivilegeType_Grant, dbName)
}
Expand Down
8 changes: 7 additions & 1 deletion internal/distributed/proxy/httpserver/handler_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1106,7 +1106,7 @@ func TestMethodPost(t *testing.T) {
mp.EXPECT().UpdateCredential(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
mp.EXPECT().OperateUserRole(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
mp.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
mp.EXPECT().OperatePrivilege(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
mp.EXPECT().OperatePrivilege(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Times(4)
mp.EXPECT().CreateIndex(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Twice()
mp.EXPECT().CreateIndex(mock.Anything, mock.Anything).Return(commonErrorStatus, nil).Once()
mp.EXPECT().CreateAlias(mock.Anything, mock.Anything).Return(commonSuccessStatus, nil).Once()
Expand Down Expand Up @@ -1179,6 +1179,12 @@ func TestMethodPost(t *testing.T) {
queryTestCases = append(queryTestCases, rawTestCase{
path: versionalV2(RoleCategory, RevokePrivilegeAction),
})
queryTestCases = append(queryTestCases, rawTestCase{
path: versionalV2(RoleCategory, GrantPrivilegeActionV2),
})
queryTestCases = append(queryTestCases, rawTestCase{
path: versionalV2(RoleCategory, RevokePrivilegeActionV2),
})
queryTestCases = append(queryTestCases, rawTestCase{
path: versionalV2(IndexCategory, CreateAction),
})
Expand Down
7 changes: 7 additions & 0 deletions internal/distributed/proxy/httpserver/request_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ type PrivilegeGroupReq struct {
Privileges []string `json:"privileges"`
}

type GrantV2Req struct {
RoleName string `json:"roleName" binding:"required"`
DbName string `json:"dbName"`
CollectionName string `json:"collectionName"`
Privilege string `json:"privilege" binding:"required"`
}

type GrantReq struct {
RoleName string `json:"roleName" binding:"required"`
ObjectType string `json:"objectType" binding:"required"`
Expand Down
55 changes: 55 additions & 0 deletions internal/mocks/mock_proxy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 96 additions & 6 deletions internal/proxy/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5210,6 +5210,87 @@ func (node *Proxy) validPrivilegeParams(req *milvuspb.OperatePrivilegeRequest) e
return nil
}

func (node *Proxy) validOperatePrivilegeV2Params(req *milvuspb.OperatePrivilegeV2Request) error {
if req.Role == nil {
return fmt.Errorf("the role in the request is nil")
}
if err := ValidateRoleName(req.Role.Name); err != nil {
return err
}
if err := ValidatePrivilege(req.Grantor.Privilege.Name); err != nil {
return err
}
if req.Type != milvuspb.OperatePrivilegeType_Grant && req.Type != milvuspb.OperatePrivilegeType_Revoke {
return fmt.Errorf("the type in the request not grant or revoke")
}
if err := ValidateObjectName(req.DbName); err != nil {
return err
}
if err := ValidateObjectName(req.CollectionName); err != nil {
return err
}
return nil
}

func (node *Proxy) OperatePrivilegeV2(ctx context.Context, req *milvuspb.OperatePrivilegeV2Request) (*commonpb.Status, error) {
ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-OperatePrivilegeV2")
defer sp.End()

log := log.Ctx(ctx)

log.Info("OperatePrivilegeV2",
zap.Any("req", req))
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if err := node.validOperatePrivilegeV2Params(req); err != nil {
return merr.Status(err), nil
}
curUser, err := GetCurUserFromContext(ctx)
if err != nil {
return merr.Status(err), nil
}
req.Grantor.User = &milvuspb.UserEntity{Name: curUser}
request := &milvuspb.OperatePrivilegeRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_OperatePrivilege},
Entity: &milvuspb.GrantEntity{
Role: req.Role,
Object: &milvuspb.ObjectEntity{Name: commonpb.ObjectType_Global.String()},
ObjectName: req.CollectionName,
DbName: req.DbName,
Grantor: req.Grantor,
},
Type: req.Type,
Version: "v2",
}
req.Grantor.User = &milvuspb.UserEntity{Name: curUser}
result, err := node.rootCoord.OperatePrivilege(ctx, request)
if err != nil {
log.Warn("fail to operate privilege", zap.Error(err))
return merr.Status(err), nil
}
relatedPrivileges := util.RelatedPrivileges[util.PrivilegeNameForMetastore(req.Grantor.Privilege.Name)]
if len(relatedPrivileges) != 0 {
for _, relatedPrivilege := range relatedPrivileges {
relatedReq := proto.Clone(request).(*milvuspb.OperatePrivilegeRequest)
relatedReq.Entity.Grantor.Privilege.Name = util.PrivilegeNameForAPI(relatedPrivilege)
result, err = node.rootCoord.OperatePrivilege(ctx, relatedReq)
if err != nil {
log.Warn("fail to operate related privilege", zap.String("related_privilege", relatedPrivilege), zap.Error(err))
return merr.Status(err), nil
}
if !merr.Ok(result) {
log.Warn("fail to operate related privilege", zap.String("related_privilege", relatedPrivilege), zap.Any("result", result))
return result, nil
}
}
}
if merr.Ok(result) {
SendReplicateMessagePack(ctx, node.replicateMsgStream, req)
}
return result, nil
}

func (node *Proxy) OperatePrivilege(ctx context.Context, req *milvuspb.OperatePrivilegeRequest) (*commonpb.Status, error) {
ctx, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "Proxy-OperatePrivilege")
defer sp.End()
Expand Down Expand Up @@ -6399,8 +6480,11 @@ func (node *Proxy) CreatePrivilegeGroup(ctx context.Context, req *milvuspb.Creat
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if req.GroupName == "" {
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
log.Warn("CreatePrivilegeGroup failed",
zap.Error(err),
)
return getErrResponse(err, "CreatePrivilegeGroup", "", ""), nil
}
if req.Base == nil {
req.Base = &commonpb.MsgBase{}
Expand Down Expand Up @@ -6428,8 +6512,11 @@ func (node *Proxy) DropPrivilegeGroup(ctx context.Context, req *milvuspb.DropPri
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if req.GroupName == "" {
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
log.Warn("DropPrivilegeGroup failed",
zap.Error(err),
)
return getErrResponse(err, "DropPrivilegeGroup", "", ""), nil
}
if req.Base == nil {
req.Base = &commonpb.MsgBase{}
Expand Down Expand Up @@ -6486,8 +6573,11 @@ func (node *Proxy) OperatePrivilegeGroup(ctx context.Context, req *milvuspb.Oper
if err := merr.CheckHealthy(node.GetStateCode()); err != nil {
return merr.Status(err), nil
}
if req.GroupName == "" {
return merr.Status(fmt.Errorf("the group name in the drop privilege group request is nil")), nil
if err := ValidatePrivilegeGroupName(req.GroupName); err != nil {
log.Warn("OperatePrivilegeGroup failed",
zap.Error(err),
)
return getErrResponse(err, "OperatePrivilegeGroup", "", ""), nil
}
for _, priv := range req.GetPrivileges() {
if err := ValidatePrivilege(priv.Name); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/proxy/privilege_interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions internal/proxy/privilege_interceptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Loading