mirror of
https://gitee.com/milvus-io/milvus.git
synced 2025-12-06 17:18:35 +08:00
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>
126 lines
4.5 KiB
Go
126 lines
4.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
|
|
"github.com/milvus-io/milvus/internal/proxy/privilege"
|
|
"github.com/milvus-io/milvus/internal/util/hookutil"
|
|
"github.com/milvus-io/milvus/pkg/v2/log"
|
|
"github.com/milvus-io/milvus/pkg/v2/metrics"
|
|
"github.com/milvus-io/milvus/pkg/v2/util"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/crypto"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/merr"
|
|
)
|
|
|
|
func parseMD(rawToken string) (username, password string) {
|
|
secrets := strings.SplitN(rawToken, util.CredentialSeperator, 2)
|
|
if len(secrets) < 2 {
|
|
log.Warn("invalid token format, length of secrets less than 2")
|
|
return
|
|
}
|
|
username = secrets[0]
|
|
password = secrets[1]
|
|
return
|
|
}
|
|
|
|
func validSourceID(ctx context.Context, authorization []string) bool {
|
|
if len(authorization) < 1 {
|
|
// log.Warn("key not found in header", zap.String("key", util.HeaderSourceID))
|
|
return false
|
|
}
|
|
// token format: base64<sourceID>
|
|
token := authorization[0]
|
|
sourceID, err := crypto.Base64Decode(token)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return sourceID == util.MemberCredID
|
|
}
|
|
|
|
func GrpcAuthInterceptor(authFunc grpc_auth.AuthFunc) grpc.UnaryServerInterceptor {
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
var newCtx context.Context
|
|
var err error
|
|
if overrideSrv, ok := info.Server.(grpc_auth.ServiceAuthFuncOverride); ok {
|
|
newCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod)
|
|
} else {
|
|
newCtx, err = authFunc(ctx)
|
|
}
|
|
if err != nil {
|
|
hookutil.GetExtension().ReportRefused(context.Background(), req, &milvuspb.BoolResponse{
|
|
Status: merr.Status(err),
|
|
}, err, info.FullMethod)
|
|
return nil, err
|
|
}
|
|
return handler(newCtx, req)
|
|
}
|
|
}
|
|
|
|
// AuthenticationInterceptor verify based on kv pair <"authorization": "token"> in header
|
|
func AuthenticationInterceptor(ctx context.Context) (context.Context, error) {
|
|
// The keys within metadata.MD are normalized to lowercase.
|
|
// See: https://godoc.org/google.golang.org/grpc/metadata#New
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
if !ok {
|
|
return nil, merr.WrapErrIoKeyNotFound("metadata", "auth check failure, due to occurs inner error: missing metadata")
|
|
}
|
|
if globalMetaCache == nil {
|
|
return nil, merr.WrapErrServiceUnavailable("internal: Milvus Proxy is not ready yet. please wait")
|
|
}
|
|
// check:
|
|
// 1. if rpc call from a member (like index/query/data component)
|
|
// 2. if rpc call from sdk
|
|
if Params.CommonCfg.AuthorizationEnabled.GetAsBool() {
|
|
if !validSourceID(ctx, md[strings.ToLower(util.HeaderSourceID)]) {
|
|
authStrArr := md[strings.ToLower(util.HeaderAuthorize)]
|
|
|
|
if len(authStrArr) < 1 {
|
|
log.Warn("key not found in header")
|
|
return nil, status.Error(codes.Unauthenticated, "missing authorization in header")
|
|
}
|
|
|
|
// token format: base64<username:password>
|
|
// token := strings.TrimPrefix(authorization[0], "Bearer ")
|
|
token := authStrArr[0]
|
|
rawToken, err := crypto.Base64Decode(token)
|
|
if err != nil {
|
|
log.Warn("fail to decode the token", zap.Error(err))
|
|
return nil, status.Error(codes.Unauthenticated, "invalid token format")
|
|
}
|
|
|
|
if !strings.Contains(rawToken, util.CredentialSeperator) {
|
|
user, err := VerifyAPIKey(rawToken)
|
|
if err != nil {
|
|
log.Warn("fail to verify apikey", zap.Error(err))
|
|
return nil, status.Error(codes.Unauthenticated, "auth check failure, please check api key is correct")
|
|
}
|
|
metrics.UserRPCCounter.WithLabelValues(user).Inc()
|
|
userToken := fmt.Sprintf("%s%s%s", user, util.CredentialSeperator, util.PasswordHolder)
|
|
md[strings.ToLower(util.HeaderAuthorize)] = []string{crypto.Base64Encode(userToken)}
|
|
md[util.HeaderToken] = []string{rawToken}
|
|
ctx = metadata.NewIncomingContext(ctx, md)
|
|
} else {
|
|
// username+password authentication
|
|
username, password := parseMD(rawToken)
|
|
if !passwordVerify(ctx, username, password, privilege.GetPrivilegeCache()) {
|
|
log.Warn("fail to verify password", zap.String("username", username))
|
|
// NOTE: don't use the merr, because it will cause the wrong retry behavior in the sdk
|
|
return nil, status.Error(codes.Unauthenticated, "auth check failure, please check username and password are correct")
|
|
}
|
|
metrics.UserRPCCounter.WithLabelValues(username).Inc()
|
|
}
|
|
}
|
|
}
|
|
return ctx, nil
|
|
}
|