congqixia f5f053f1d2
enhance: Refactor privilege management by extracting privilege cache into separate package (#44762)
Related to #44761

This commit refactors the privilege management system in the proxy
component by:

1. **Separation of Concerns**: Extracts privilege-related functionality
from MetaCache into a dedicated `internal/proxy/privilege` package,
improving code organization and maintainability.

2. **New Package Structure**: Creates `internal/proxy/privilege/` with:
   - `cache.go`: Core privilege cache implementation (PrivilegeCache)
   - `result_cache.go`: Privilege enforcement result caching
   - `model.go`: Casbin model and policy enforcement functions
   - `meta_cache_adapter.go`: Casbin adapter for MetaCache integration
   - Corresponding test files and mock implementations

3. **MetaCache Simplification**: Removes privilege and credential
management methods from MetaCache interface and implementation:
   - Removed: GetCredentialInfo, RemoveCredential, UpdateCredential
- Removed: GetPrivilegeInfo, GetUserRole, RefreshPolicyInfo,
InitPolicyInfo
   - Deleted: meta_cache_adapter.go, privilege_cache.go and their tests

4. **Updated References**: Updates all callsites to use the new
privilegeCache global:
- Authentication interceptor now uses privilegeCache for password
verification
- Credential cache operations (InvalidateCredentialCache,
UpdateCredentialCache, UpdateCredential) now use privilegeCache
- Policy refresh operations (RefreshPolicyInfoCache) now use
privilegeCache
- Privilege interceptor uses new privilege.GetEnforcer() and privilege
result cache

5. **Improved API**: Renames cache functions for clarity:
   - GetPrivilegeCache → GetResultCache
   - SetPrivilegeCache → SetResultCache
   - CleanPrivilegeCache → CleanResultCache

This refactoring makes the codebase more modular, separates privilege
management concerns from general metadata caching, and provides a
clearer API for privilege enforcement operations.

---------

Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>
2025-10-13 11:15:58 +08:00

140 lines
4.1 KiB
Go

package privilege
import (
"log"
"strings"
"sync"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/samber/lo"
"go.uber.org/zap"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
"github.com/milvus-io/milvus/pkg/v2/util"
"github.com/milvus-io/milvus/pkg/v2/util/funcutil"
"github.com/milvus-io/milvus/pkg/v2/util/paramtable"
)
const (
// sub -> role name, like admin, public
// obj -> contact object with object name, like Global-*, Collection-col1
// act -> privilege, like CreateCollection, DescribeCollection
ModelStr = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && globMatch(r.obj, p.obj) && globMatch(r.act, p.act) || r.sub == "admin" || (r.sub == p.sub && dbMatch(r.obj, p.obj) && privilegeGroupContains(r.act, p.act, r.obj, p.obj))
`
)
var (
enforcer *casbin.SyncedEnforcer
initOnce sync.Once
initPrivilegeGroupsOnce sync.Once
)
func GetPolicyModel(modelString string) model.Model {
m, err := model.NewModelFromString(modelString)
if err != nil {
log.Panic("NewModelFromString fail", zap.String("model", ModelStr), zap.Error(err))
}
return m
}
func InitPrivilegeGroups() {
initPrivilegeGroupsOnce.Do(func() {
roGroup := paramtable.Get().CommonCfg.ReadOnlyPrivileges.GetAsStrings()
if len(roGroup) == 0 {
roGroup = util.ReadOnlyPrivilegeGroup
}
roPrivileges = lo.SliceToMap(roGroup, func(item string) (string, struct{}) { return item, struct{}{} })
rwGroup := paramtable.Get().CommonCfg.ReadWritePrivileges.GetAsStrings()
if len(rwGroup) == 0 {
rwGroup = util.ReadWritePrivilegeGroup
}
rwPrivileges = lo.SliceToMap(rwGroup, func(item string) (string, struct{}) { return item, struct{}{} })
adminGroup := paramtable.Get().CommonCfg.AdminPrivileges.GetAsStrings()
if len(adminGroup) == 0 {
adminGroup = util.AdminPrivilegeGroup
}
adminPrivileges = lo.SliceToMap(adminGroup, func(item string) (string, struct{}) { return item, struct{}{} })
})
}
func GetEnforcer() *casbin.SyncedEnforcer {
initOnce.Do(func() {
e, err := casbin.NewSyncedEnforcer()
if err != nil {
log.Panic("failed to create casbin enforcer", zap.Error(err))
}
casbinModel := GetPolicyModel(ModelStr)
adapter := NewMetaCacheCasbinAdapter(func() PrivilegeCache { return GetPrivilegeCache() })
e.InitWithModelAndAdapter(casbinModel, adapter)
e.AddFunction("dbMatch", DBMatchFunc)
e.AddFunction("privilegeGroupContains", PrivilegeGroupContains)
enforcer = e
})
return enforcer
}
var roPrivileges, rwPrivileges, adminPrivileges map[string]struct{}
func DBMatchFunc(args ...interface{}) (interface{}, error) {
name1 := args[0].(string)
name2 := args[1].(string)
db1, _ := funcutil.SplitObjectName(name1[strings.Index(name1, "-")+1:])
db2, _ := funcutil.SplitObjectName(name2[strings.Index(name2, "-")+1:])
return db1 == db2, nil
}
func PrivilegeGroupContains(args ...interface{}) (interface{}, error) {
requestPrivilege := args[0].(string)
policyPrivilege := args[1].(string)
requestObj := args[2].(string)
policyObj := args[3].(string)
switch policyPrivilege {
case commonpb.ObjectPrivilege_PrivilegeAll.String():
return true, nil
case commonpb.ObjectPrivilege_PrivilegeGroupReadOnly.String():
// read only belong to collection object
if !collMatch(requestObj, policyObj) {
return false, nil
}
_, ok := roPrivileges[requestPrivilege]
return ok, nil
case commonpb.ObjectPrivilege_PrivilegeGroupReadWrite.String():
// read write belong to collection object
if !collMatch(requestObj, policyObj) {
return false, nil
}
_, ok := rwPrivileges[requestPrivilege]
return ok, nil
case commonpb.ObjectPrivilege_PrivilegeGroupAdmin.String():
// admin belong to global object
_, ok := adminPrivileges[requestPrivilege]
return ok, nil
default:
return false, nil
}
}
func collMatch(requestObj, policyObj string) bool {
_, coll1 := funcutil.SplitObjectName(requestObj[strings.Index(requestObj, "-")+1:])
_, coll2 := funcutil.SplitObjectName(policyObj[strings.Index(policyObj, "-")+1:])
return coll1 == util.AnyWord || coll2 == util.AnyWord || coll1 == coll2
}