diff --git a/configs/milvus.yaml b/configs/milvus.yaml index 88817e4de03a6..e1851ad23f5f1 100644 --- a/configs/milvus.yaml +++ b/configs/milvus.yaml @@ -19,7 +19,7 @@ etcd: # Endpoints used to access etcd service. You can change this parameter as the endpoints of your own etcd cluster. # Environment variable: ETCD_ENDPOINTS # etcd preferentially acquires valid address from environment variable ETCD_ENDPOINTS when Milvus is started. - endpoints: localhost:2379 + endpoints: etcd:2379 # Root prefix of the key to where Milvus stores data in etcd. # It is recommended to change this parameter before starting Milvus for the first time. # To share an etcd instance among multiple Milvus instances, consider changing this to a different value for each Milvus instance before you start them. @@ -98,7 +98,7 @@ minio: # minio.address and minio.port together generate the valid access to MinIO or S3 service. # MinIO preferentially acquires the valid IP address from the environment variable MINIO_ADDRESS when Milvus is started. # Default value applies when MinIO or S3 is running on the same network with Milvus. - address: localhost + address: minio:9000 port: 9000 # Port of MinIO or S3 service. # Access key ID that MinIO or S3 issues to user for authorized access. # Environment variable: MINIO_ACCESS_KEY_ID or minio.accessKeyID @@ -184,7 +184,7 @@ pulsar: # pulsar.address and pulsar.port together generate the valid access to Pulsar. # Pulsar preferentially acquires the valid IP address from the environment variable PULSAR_ADDRESS when Milvus is started. # Default value applies when Pulsar is running on the same network with Milvus. - address: localhost + address: pulsar://pulsar:6650 port: 6650 # Port of Pulsar service. webport: 80 # Web port of of Pulsar service. If you connect direcly without proxy, should use 8080. # The maximum size of each message in Pulsar. Unit: Byte. @@ -811,6 +811,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: SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups # Cluster level readonly privileges + readwrite: + privileges: SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,CreateOwnership,UpdateUser,DropOwnership,ManageOwnership,BackupRBAC,RestoreRBAC,CreateResourceGroup,UpdateResourceGroups,DropResourceGroup,TransferNode,TransferReplica # Cluster level readwrite privileges + admin: + privileges: SelectOwnership,SelectUser,DescribeResourceGroup,ListResourceGroups,CreateOwnership,UpdateUser,DropOwnership,ManageOwnership,BackupRBAC,RestoreRBAC,CreateResourceGroup,UpdateResourceGroups,DropResourceGroup,TransferNode,TransferReplica # Cluster level admin privileges + database: + readonly: + privileges: ListDatabases,DescribeDatabase # Database level readonly privileges + readwrite: + privileges: ListDatabases,DescribeDatabase,CreateDatabase,DropDatabase,AlterDatabase # Database level readwrite privileges + admin: + privileges: ListDatabases,DescribeDatabase,CreateDatabase,DropDatabase,AlterDatabase # Database level admin privileges + collection: + readonly: + privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,ShowCollections,ListAliases,DescribeCollection,DescribeAlias,GetStatistics # Collection level readonly privileges + readwrite: + privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,ShowCollections,ListAliases,DescribeCollection,DescribeAlias,GetStatistics,CreateIndex,DropIndex,CreatePartition,DropPartition,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateAlias,DropAlias,CreateCollection,DropCollection,FlushAll # Collection level readwrite privileges + admin: + privileges: Query,Search,IndexDetail,GetFlushState,GetLoadState,GetLoadingProgress,HasPartition,ShowPartitions,ShowCollections,ListAliases,DescribeCollection,DescribeAlias,GetStatistics,CreateIndex,DropIndex,CreatePartition,DropPartition,Load,Release,Insert,Delete,Upsert,Import,Flush,Compaction,LoadBalance,RenameCollection,CreateAlias,DropAlias,CreateCollection,DropCollection,FlushAll # Collection level admin privileges tlsMode: 0 session: ttl: 30 # ttl value when session granting a lease to register service diff --git a/go.mod b/go.mod index b5489c8c3fcf5..0b6c7c89aae3a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/klauspost/compress v1.17.9 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d - github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f + github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241114133823-d3506c6f465c github.com/minio/minio-go/v7 v7.0.73 github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81 github.com/prometheus/client_golang v1.14.0 diff --git a/go.sum b/go.sum index 3986e672b5f32..c91589f7f5f43 100644 --- a/go.sum +++ b/go.sum @@ -628,8 +628,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f h1:yLxT8NH0ixUOJMqJuk0xvGf0cKsr+N2xibyTat256PI= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +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/pulsar-client-go v0.12.1 h1:O2JZp1tsYiO7C0MQ4hrUY/aJXnn2Gry6hpm7UodghmE= github.com/milvus-io/pulsar-client-go v0.12.1/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= diff --git a/internal/rootcoord/root_coord.go b/internal/rootcoord/root_coord.go index 2c7d37e050021..cb922d64e9db2 100644 --- a/internal/rootcoord/root_coord.go +++ b/internal/rootcoord/root_coord.go @@ -576,7 +576,12 @@ func (c *Core) initRbac() error { } if Params.RoleCfg.Enabled.GetAsBool() { - return c.initBuiltinRoles() + if err := c.initBuiltinRoles(); err != nil { + return err + } + } + if err := c.initBuiltinPrivilegeGroups(); err != nil { + return err } return nil } @@ -624,6 +629,51 @@ func (c *Core) initPublicRolePrivilege() error { return nil } +func (c *Core) initBuiltinPrivilegeGroups() error { + // init built in privilege groups, override by config if rbac config enabled + for groupName, privileges := range util.BuiltinPrivilegeGroups { + if err := c.meta.CreatePrivilegeGroup(groupName); err != nil { + return err + } + if Params.RbacConfig.Enabled.GetAsBool() { + var confPrivs []string + switch groupName { + case "ClusterReadOnly": + confPrivs = Params.RbacConfig.ClusterReadOnlyPrivileges.GetAsStrings() + case "ClusterReadWrite": + confPrivs = Params.RbacConfig.ClusterReadWritePrivileges.GetAsStrings() + case "ClusterAdmin": + confPrivs = Params.RbacConfig.ClusterAdminPrivileges.GetAsStrings() + case "DatabaseReadOnly": + confPrivs = Params.RbacConfig.DBReadOnlyPrivileges.GetAsStrings() + case "DatabaseReadWrite": + confPrivs = Params.RbacConfig.DBReadWritePrivileges.GetAsStrings() + case "DatabaseAdmin": + confPrivs = Params.RbacConfig.DBAdminPrivileges.GetAsStrings() + case "CollectionReadOnly": + confPrivs = Params.RbacConfig.CollectionReadOnlyPrivileges.GetAsStrings() + case "CollectionReadWrite": + confPrivs = Params.RbacConfig.CollectionReadWritePrivileges.GetAsStrings() + case "CollectionAdmin": + confPrivs = Params.RbacConfig.CollectionAdminPrivileges.GetAsStrings() + default: + return nil + } + if len(confPrivs) > 0 { + privileges = confPrivs + } + } + + privs := lo.Map(privileges, func(name string, _ int) *milvuspb.PrivilegeEntity { + return &milvuspb.PrivilegeEntity{Name: name} + }) + if err := c.meta.OperatePrivilegeGroup(groupName, privs, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup); err != nil { + return err + } + } + return nil +} + func (c *Core) initBuiltinRoles() error { rolePrivilegesMap := Params.RoleCfg.Roles.GetAsRoleDetails() for role, privilegesJSON := range rolePrivilegesMap { @@ -2583,24 +2633,24 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil } - // set up privilege name for metastore - privName := in.Entity.Grantor.Privilege.Name - ctxLog.Debug("before PrivilegeNameForMetastore", zap.String("privilege", privName)) - if !util.IsAnyWord(privName) { - dbPrivName, err := c.getMetastorePrivilegeName(privName) - if err != nil { - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil - } - in.Entity.Grantor.Privilege.Name = dbPrivName - } - ctxLog.Debug("after PrivilegeNameForMetastore", zap.String("privilege", privName)) - + // set up object name if it is global object type if in.Entity.Object.Name == commonpb.ObjectType_Global.String() { in.Entity.ObjectName = util.AnyWord } + privName := in.Entity.Grantor.Privilege.Name + redoTask := newBaseRedoTask(c.stepExecutor) redoTask.AddSyncStep(NewSimpleStep("operate privilege meta data", func(ctx context.Context) ([]nestedStep, error) { + if !util.IsAnyWord(privName) { + // set up privilege name for metastore + dbPrivName, err := c.getMetastorePrivilegeName(privName) + if err != nil { + return nil, err + } + in.Entity.Grantor.Privilege.Name = dbPrivName + } + err := c.meta.OperatePrivilege(util.DefaultTenant, in.Entity, in.Type) if err != nil && !common.IsIgnorableError(err) { log.Warn("fail to operate the privilege", zap.Any("in", in), zap.Error(err)) @@ -2609,6 +2659,8 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile return nil, nil })) redoTask.AddAsyncStep(NewSimpleStep("operate privilege cache", func(ctx context.Context) ([]nestedStep, error) { + // set back to expand privilege group + in.Entity.Grantor.Privilege.Name = privName var opType int32 switch in.Type { case milvuspb.OperatePrivilegeType_Grant: @@ -2619,9 +2671,22 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile log.Warn("invalid operate type for the OperatePrivilege api", zap.Any("in", in)) return nil, nil } + grants := []*milvuspb.GrantEntity{in.Entity} + + allGroups, err := c.meta.ListPrivilegeGroups() + if err != nil { + return nil, err + } + groups := lo.SliceToMap(allGroups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { + return group.GroupName, group.Privileges + }) + expandGrants, err := c.expandPrivilegeGroups(grants, groups) + if err != nil { + return nil, err + } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: opType, - OpKey: funcutil.PolicyForPrivilege(in.Entity.Role.Name, in.Entity.Object.Name, in.Entity.ObjectName, in.Entity.Grantor.Privilege.Name, in.Entity.DbName), + OpKey: funcutil.PolicyForPrivileges(expandGrants), }); err != nil { log.Warn("fail to refresh policy info cache", zap.Any("in", in), zap.Error(err)) return nil, err @@ -3095,11 +3160,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 { @@ -3110,8 +3175,14 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr if err != nil { return nil, err } - currGrants := c.expandPrivilegeGroups(grants, currGroups) - newGrants := c.expandPrivilegeGroups(grants, newGroups) + currGrants, err := c.expandPrivilegeGroups(grants, currGroups) + if err != nil { + return nil, err + } + newGrants, err := c.expandPrivilegeGroups(grants, newGroups) + if err != nil { + return nil, err + } toRevoke := lo.Filter(currGrants, func(item *milvuspb.GrantEntity, _ int) bool { return !lo.ContainsBy(newGrants, func(newItem *milvuspb.GrantEntity) bool { @@ -3175,20 +3246,42 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr return merr.Success(), nil } -func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[string][]*milvuspb.PrivilegeEntity) []*milvuspb.GrantEntity { +func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[string][]*milvuspb.PrivilegeEntity) ([]*milvuspb.GrantEntity, error) { newGrants := []*milvuspb.GrantEntity{} for _, grant := range grants { - if groups[grant.Grantor.Privilege.Name] == nil { - newGrants = append(newGrants, grant) + privName := grant.Grantor.Privilege.Name + if privGroup, exists := groups[privName]; !exists { + metaName, err := c.getMetastorePrivilegeName(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, + }) } else { - for _, priv := range groups[grant.Grantor.Privilege.Name] { + for _, priv := range privGroup { + metaName, err := c.getMetastorePrivilegeName(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: priv, + User: grant.Grantor.User, + Privilege: &milvuspb.PrivilegeEntity{ + Name: metaName, + }, }, DbName: grant.DbName, }) @@ -3198,5 +3291,5 @@ func (c *Core) expandPrivilegeGroups(grants []*milvuspb.GrantEntity, groups map[ // uniq by role + object + object name + grantor user + privilege name + db name return lo.UniqBy(newGrants, func(g *milvuspb.GrantEntity) string { return fmt.Sprintf("%s-%s-%s-%s-%s-%s", g.Role, g.Object, g.ObjectName, g.Grantor.User, g.Grantor.Privilege.Name, g.DbName) - }) + }), nil } diff --git a/internal/rootcoord/root_coord_test.go b/internal/rootcoord/root_coord_test.go index e8a9dc5d1a8cc..d2a98c6b14b09 100644 --- a/internal/rootcoord/root_coord_test.go +++ b/internal/rootcoord/root_coord_test.go @@ -1976,6 +1976,8 @@ func TestCore_InitRBAC(t *testing.T) { c := newTestCore(withHealthyCode(), withMeta(meta)) meta.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(nil).Twice() meta.EXPECT().OperatePrivilege(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice() + meta.EXPECT().CreatePrivilegeGroup(mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) + meta.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) Params.Save(Params.RoleCfg.Enabled.Key, "false") Params.Save(Params.ProxyCfg.EnablePublicPrivilege.Key, "true") @@ -1995,6 +1997,8 @@ func TestCore_InitRBAC(t *testing.T) { c := newTestCore(withHealthyCode(), withMeta(meta)) meta.EXPECT().CreateRole(mock.Anything, mock.Anything).Return(nil).Times(3) meta.EXPECT().OperatePrivilege(mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + meta.EXPECT().CreatePrivilegeGroup(mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) + meta.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) Params.Save(Params.RoleCfg.Enabled.Key, "true") Params.Save(Params.RoleCfg.Roles.Key, builtinRoles) @@ -2009,6 +2013,25 @@ func TestCore_InitRBAC(t *testing.T) { err := c.initRbac() assert.NoError(t, err) }) + + t.Run("init default privilege groups", func(t *testing.T) { + clusterReadWrite := `SelectOwnership,SelectUser,DescribeResourceGroup` + meta := mockrootcoord.NewIMetaTable(t) + c := newTestCore(withHealthyCode(), withMeta(meta)) + meta.EXPECT().CreatePrivilegeGroup(mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) + meta.EXPECT().OperatePrivilegeGroup(mock.Anything, mock.Anything, mock.Anything).Return(nil).Times(len(util.BuiltinPrivilegeGroups)) + + Params.Save(Params.RbacConfig.Enabled.Key, "true") + Params.Save(Params.RbacConfig.ClusterReadWritePrivileges.Key, clusterReadWrite) + + defer func() { + Params.Reset(Params.RbacConfig.Enabled.Key) + Params.Reset(Params.RbacConfig.ClusterReadWritePrivileges.Key) + }() + + err := c.initBuiltinPrivilegeGroups() + assert.NoError(t, err) + }) } func TestCore_BackupRBAC(t *testing.T) { diff --git a/pkg/go.mod b/pkg/go.mod index 11207c2d64338..60b97e39a1a53 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -14,7 +14,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.17.7 - github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f + github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241114133823-d3506c6f465c github.com/nats-io/nats-server/v2 v2.10.12 github.com/nats-io/nats.go v1.34.1 github.com/panjf2000/ants/v2 v2.7.2 diff --git a/pkg/go.sum b/pkg/go.sum index f532bcadeb848..c1f60d82a59e7 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -488,8 +488,8 @@ github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119 h1:9VXijWu github.com/milvus-io/cgosymbolizer v0.0.0-20240722103217-b7dee0e50119/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b h1:TfeY0NxYxZzUfIfYe5qYDBzt4ZYRqzUjTR6CvUzjat8= github.com/milvus-io/gorocksdb v0.0.0-20220624081344-8c5f4212846b/go.mod h1:iwW+9cWfIzzDseEBCCeDSN5SD16Tidvy8cwQ7ZY8Qj4= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f h1:yLxT8NH0ixUOJMqJuk0xvGf0cKsr+N2xibyTat256PI= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.4-0.20241111062829-6de3d96f664f/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +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/pulsar-client-go v0.12.1 h1:O2JZp1tsYiO7C0MQ4hrUY/aJXnn2Gry6hpm7UodghmE= github.com/milvus-io/pulsar-client-go v0.12.1/go.mod h1:dkutuH4oS2pXiGm+Ti7fQZ4MRjrMPZ8IJeEGAWMeckk= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= diff --git a/pkg/util/constant.go b/pkg/util/constant.go index d68ac7fce276a..74399f3761946 100644 --- a/pkg/util/constant.go +++ b/pkg/util/constant.go @@ -283,6 +283,94 @@ var ( commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String(), commonpb.ObjectPrivilege_PrivilegeFlush.String(), } + + BuiltinPrivilegeGroups = map[string][]string{ + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadOnly.String()): CollectionReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionReadWrite.String()): CollectionReadWritePrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupCollectionAdmin.String()): CollectionAdminPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadOnly.String()): DatabaseReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseReadWrite.String()): DatabaseReadWritePrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupDatabaseAdmin.String()): DatabaseAdminPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadOnly.String()): ClusterReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterReadWrite.String()): ClusterReadWritePrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGroupClusterAdmin.String()): ClusterAdminPrivilegeGroup, + } + + CollectionReadOnlyPrivilegeGroup = []string{ + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeQuery.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSearch.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeIndexDetail.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetFlushState.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadState.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetLoadingProgress.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeHasPartition.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowPartitions.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeShowCollections.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListAliases.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeCollection.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeAlias.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeGetStatistics.String()), + } + + CollectionReadWritePrivilegeGroup = append(CollectionReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateIndex.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropIndex.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreatePartition.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropPartition.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoad.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRelease.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeInsert.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDelete.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpsert.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeImport.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlush.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCompaction.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeLoadBalance.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRenameCollection.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateAlias.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropAlias.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateCollection.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropCollection.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeFlushAll.String()), + ) + + CollectionAdminPrivilegeGroup = CollectionReadWritePrivilegeGroup + + DatabaseReadOnlyPrivilegeGroup = []string{ + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListDatabases.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeDatabase.String()), + } + + DatabaseReadWritePrivilegeGroup = append(DatabaseReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateDatabase.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropDatabase.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeAlterDatabase.String()), + ) + + DatabaseAdminPrivilegeGroup = DatabaseReadWritePrivilegeGroup + + ClusterReadOnlyPrivilegeGroup = []string{ + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectOwnership.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeSelectUser.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDescribeResourceGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeListResourceGroups.String()), + } + + ClusterReadWritePrivilegeGroup = append(ClusterReadOnlyPrivilegeGroup, + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateOwnership.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateUser.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropOwnership.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeManageOwnership.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeBackupRBAC.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeRestoreRBAC.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeCreateResourceGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeUpdateResourceGroups.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeDropResourceGroup.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferNode.String()), + MetaStore2API(commonpb.ObjectPrivilege_PrivilegeTransferReplica.String()), + ) + + ClusterAdminPrivilegeGroup = ClusterReadWritePrivilegeGroup ) // StringSet convert array to map for conveniently check if the array contains an element @@ -344,6 +432,12 @@ func IsPrivilegeNameDefined(name string) bool { return PrivilegeNameForMetastore(name) != "" } +func IsBuiltinPrivilegeGroup(name string) bool { + dbPrivilege := PrivilegeGroupWord + name + _, ok := commonpb.ObjectPrivilege_value[dbPrivilege] + return ok +} + func PrivilegeGroupNameForMetastore(name string) string { return PrivilegeGroupWord + name } diff --git a/pkg/util/paramtable/component_param.go b/pkg/util/paramtable/component_param.go index a1f0ed6897642..afee3c983c664 100644 --- a/pkg/util/paramtable/component_param.go +++ b/pkg/util/paramtable/component_param.go @@ -80,6 +80,7 @@ type ComponentParam struct { HTTPCfg httpConfig LogCfg logConfig RoleCfg roleConfig + RbacConfig rbacConfig StreamingCfg streamingConfig RootCoordGrpcServerCfg GrpcServerConfig @@ -134,6 +135,7 @@ func (p *ComponentParam) init(bt *BaseTable) { p.HTTPCfg.init(bt) p.LogCfg.init(bt) p.RoleCfg.init(bt) + p.RbacConfig.init(bt) p.GpuConfig.init(bt) p.KnowhereConfig.init(bt) diff --git a/pkg/util/paramtable/rbac_config_test.go b/pkg/util/paramtable/rbac_config_test.go new file mode 100644 index 0000000000000..803fceabe2896 --- /dev/null +++ b/pkg/util/paramtable/rbac_config_test.go @@ -0,0 +1,41 @@ +// Licensed to the LF AI & Data foundation under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paramtable + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/milvus-io/milvus/pkg/util" +) + +func TestRbacConfig_Init(t *testing.T) { + params := ComponentParam{} + params.Init(NewBaseTable(SkipRemote(true))) + cfg := ¶ms.RbacConfig + assert.Equal(t, cfg.Enabled.GetAsBool(), false) + assert.Equal(t, cfg.ClusterReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterReadOnly"]) + assert.Equal(t, cfg.ClusterReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterReadWrite"]) + assert.Equal(t, cfg.ClusterAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["ClusterAdmin"]) + assert.Equal(t, cfg.DBReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseReadOnly"]) + assert.Equal(t, cfg.DBReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseReadWrite"]) + assert.Equal(t, cfg.DBAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["DatabaseAdmin"]) + assert.Equal(t, cfg.CollectionReadOnlyPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionReadOnly"]) + assert.Equal(t, cfg.CollectionReadWritePrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionReadWrite"]) + assert.Equal(t, cfg.CollectionAdminPrivileges.GetAsStrings(), util.BuiltinPrivilegeGroups["CollectionAdmin"]) +} diff --git a/pkg/util/paramtable/rbac_param.go b/pkg/util/paramtable/rbac_param.go new file mode 100644 index 0000000000000..3d2f6d36d9c4c --- /dev/null +++ b/pkg/util/paramtable/rbac_param.go @@ -0,0 +1,114 @@ +package paramtable + +import ( + "strings" + + "github.com/milvus-io/milvus/pkg/util" +) + +type rbacConfig struct { + Enabled ParamItem `refreshable:"false"` + ClusterReadOnlyPrivileges ParamItem `refreshable:"false"` + ClusterReadWritePrivileges ParamItem `refreshable:"false"` + ClusterAdminPrivileges ParamItem `refreshable:"false"` + + DBReadOnlyPrivileges ParamItem `refreshable:"false"` + DBReadWritePrivileges ParamItem `refreshable:"false"` + DBAdminPrivileges ParamItem `refreshable:"false"` + + CollectionReadOnlyPrivileges ParamItem `refreshable:"false"` + CollectionReadWritePrivileges ParamItem `refreshable:"false"` + CollectionAdminPrivileges ParamItem `refreshable:"false"` +} + +func (p *rbacConfig) init(base *BaseTable) { + p.Enabled = ParamItem{ + Key: "common.security.rbac.overrideBuiltInPrivilgeGroups.enabled", + DefaultValue: "false", + Version: "2.3.4", + Doc: "Whether to override build-in privilege groups", + Export: true, + } + p.Enabled.Init(base.mgr) + + p.ClusterReadOnlyPrivileges = ParamItem{ + Key: "common.security.rbac.cluster.readonly.privileges", + DefaultValue: strings.Join(util.ClusterReadOnlyPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Cluster level readonly privileges", + Export: true, + } + p.ClusterReadOnlyPrivileges.Init(base.mgr) + + p.ClusterReadWritePrivileges = ParamItem{ + Key: "common.security.rbac.cluster.readwrite.privileges", + DefaultValue: strings.Join(util.ClusterReadWritePrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Cluster level readwrite privileges", + Export: true, + } + p.ClusterReadWritePrivileges.Init(base.mgr) + + p.ClusterAdminPrivileges = ParamItem{ + Key: "common.security.rbac.cluster.admin.privileges", + DefaultValue: strings.Join(util.ClusterAdminPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Cluster level admin privileges", + Export: true, + } + p.ClusterAdminPrivileges.Init(base.mgr) + + p.DBReadOnlyPrivileges = ParamItem{ + Key: "common.security.rbac.database.readonly.privileges", + DefaultValue: strings.Join(util.DatabaseReadOnlyPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Database level readonly privileges", + Export: true, + } + p.DBReadOnlyPrivileges.Init(base.mgr) + + p.DBReadWritePrivileges = ParamItem{ + Key: "common.security.rbac.database.readwrite.privileges", + DefaultValue: strings.Join(util.DatabaseReadWritePrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Database level readwrite privileges", + Export: true, + } + p.DBReadWritePrivileges.Init(base.mgr) + + p.DBAdminPrivileges = ParamItem{ + Key: "common.security.rbac.database.admin.privileges", + DefaultValue: strings.Join(util.DatabaseAdminPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Database level admin privileges", + Export: true, + } + p.DBAdminPrivileges.Init(base.mgr) + + p.CollectionReadOnlyPrivileges = ParamItem{ + Key: "common.security.rbac.collection.readonly.privileges", + DefaultValue: strings.Join(util.CollectionReadOnlyPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Collection level readonly privileges", + Export: true, + } + p.CollectionReadOnlyPrivileges.Init(base.mgr) + + p.CollectionReadWritePrivileges = ParamItem{ + Key: "common.security.rbac.collection.readwrite.privileges", + DefaultValue: strings.Join(util.CollectionReadWritePrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Collection level readwrite privileges", + Export: true, + } + p.CollectionReadWritePrivileges.Init(base.mgr) + + p.CollectionAdminPrivileges = ParamItem{ + Key: "common.security.rbac.collection.admin.privileges", + DefaultValue: strings.Join(util.CollectionAdminPrivilegeGroup, ","), + Version: "2.3.4", + Doc: "Collection level admin privileges", + Export: true, + } + p.CollectionAdminPrivileges.Init(base.mgr) +}