首页 > 其他分享 >cerbos golang 内部policy check 处理简单说明

cerbos golang 内部policy check 处理简单说明

时间:2023-12-06 13:01:03浏览次数:40  
标签:return err nil ctx golang cerbos result policy input

主要是想尝试实现一个rust 的check 方法,所以先研究下golang 的内部实现

  • CheckResources
func (cs *CerbosService) CheckResources(ctx context.Context, req *requestv1.CheckResourcesRequest) (*responsev1.CheckResourcesResponse, error) {
    log := logging.ReqScopeLog(ctx)
    if err := cs.checkNumResourcesLimit(len(req.Resources)); err != nil {
        log.Error("Request too large", zap.Error(err))
        return nil, err
    }
 
   // 数据提取处理,主要是一些其他数据,比较jwt 认证的
    auxData, err := cs.auxData.Extract(ctx, req.AuxData)
    if err != nil {
        log.Error("Failed to extract auxData", zap.Error(err))
        return nil, status.Error(codes.InvalidArgument, "invalid auxData")
    }
    inputs := make([]*enginev1.CheckInput, len(req.Resources))
    for i, res := range req.Resources {
        if err := cs.checkNumActionsLimit(len(res.Actions)); err != nil {
            log.Error("Request too large", zap.Error(err))
            return nil, err
        }
 
        inputs[i] = &enginev1.CheckInput{
            RequestId: req.RequestId,
            Actions:   res.Actions,
            Principal: req.Principal,
            Resource:  res.Resource,
            AuxData:   auxData,
        }
    }
   // 基于engine 提供的check 方法,此方法也是我们需要注意的
    outputs, err := cs.eng.Check(logging.ToContext(ctx, log), inputs)
    if err != nil {
        log.Error("Policy check failed", zap.Error(err))
        if errors.Is(err, compile.PolicyCompilationErr{}) {
            return nil, status.Errorf(codes.FailedPrecondition, "Check failed due to invalid policy")
        }
        return nil, status.Errorf(codes.Internal, "Policy check failed")
    }
 
    result := &responsev1.CheckResourcesResponse{
        RequestId: req.RequestId,
        Results:   make([]*responsev1.CheckResourcesResponse_ResultEntry, len(outputs)),
    }
   // 对于多个返回的check 结果,需要转换为标准格式的输出
    for i, out := range outputs {
        resource := inputs[i].Resource
        entry := &responsev1.CheckResourcesResponse_ResultEntry{
            Resource: &responsev1.CheckResourcesResponse_ResultEntry_Resource{
                Id:            resource.Id,
                Kind:          resource.Kind,
                PolicyVersion: resource.PolicyVersion,
                Scope:         resource.Scope,
            },
            ValidationErrors: out.ValidationErrors,
            Actions:          make(map[string]effectv1.Effect, len(out.Actions)),
        }
 
        if req.IncludeMeta {
            entry.Meta = &responsev1.CheckResourcesResponse_ResultEntry_Meta{
                EffectiveDerivedRoles: out.EffectiveDerivedRoles,
                Actions:               make(map[string]*responsev1.CheckResourcesResponse_ResultEntry_Meta_EffectMeta, len(out.Actions)),
            }
        }
 
        if len(out.Outputs) > 0 {
            entry.Outputs = out.Outputs
        }
 
        for action, actionEffect := range out.Actions {
            entry.Actions[action] = actionEffect.Effect
            if req.IncludeMeta {
                entry.Meta.Actions[action] = &responsev1.CheckResourcesResponse_ResultEntry_Meta_EffectMeta{
                    MatchedPolicy: actionEffect.Policy,
                    MatchedScope:  actionEffect.Scope,
                }
            }
        }
 
        result.Results[i] = entry
    }
 
    return result, nil
}
  • engine check 处理
func (engine *Engine) Check(ctx context.Context, inputs []*enginev1.CheckInput, opts ...CheckOpt) ([]*enginev1.CheckOutput, error) {
    outputs, err := metrics.RecordDuration2(metrics.EngineCheckLatency(), func() (outputs []*enginev1.CheckOutput, err error) {
        ctx, span := tracing.StartSpan(ctx, "engine.Check")
        defer span.End()
 
        checkOpts := newCheckOptions(ctx, engine.conf, opts...)
        // 包含了串行以及并行的check
        // if the number of inputs is less than the threshold, do a serial execution as it is usually faster.
        // ditto if the worker pool is not initialized
        if len(inputs) < parallelismThreshold || len(engine.workerPool) == 0 {
            outputs, err = engine.checkSerial(ctx, inputs, checkOpts)
        } else {
            outputs, err = engine.checkParallel(ctx, inputs, checkOpts)
        }
 
        if err != nil {
            tracing.MarkFailed(span, http.StatusBadRequest, err)
        }
 
        return outputs, err
    })
    metrics.EngineCheckBatchSize().Record(context.Background(), int64(len(inputs)))
 
    return engine.logCheckDecision(ctx, inputs, outputs, err)
}
  • checkSerial 以及checkParallel 核心的计算(evaluate)
func (engine *Engine) evaluate(ctx context.Context, input *enginev1.CheckInput, checkOpts *CheckOptions) (*enginev1.CheckOutput, error) {
    ctx, span := tracing.StartSpan(ctx, "engine.Evaluate")
    defer span.End()
 
    span.SetAttributes(tracing.RequestID(input.RequestId), tracing.ReqResourceID(input.Resource.Id))
 
    // exit early if the context is cancelled
    if err := ctx.Err(); err != nil {
        tracing.MarkFailed(span, http.StatusRequestTimeout, err)
        return nil, err
    }
 
    output := &enginev1.CheckOutput{
        RequestId:  input.RequestId,
        ResourceId: input.Resource.Id,
        Actions:    make(map[string]*enginev1.CheckOutput_ActionEffect, len(input.Actions)),
    }
    // 先构建上下文
    ec, err := engine.buildEvaluationCtx(ctx, checkOpts.evalParams, input)
    if err != nil {
        return nil, err
    }
 
    tctx := tracer.Start(checkOpts.tracerSink)
 
    // evaluate the policies,执行策略
    result, err := ec.evaluate(ctx, tctx, input)
    if err != nil {
        logging.FromContext(ctx).Error("Failed to evaluate policies", zap.Error(err))
        return nil, fmt.Errorf("failed to evaluate policies: %w", err)
    }
 
    // update the output
    for _, action := range input.Actions {
        output.Actions[action] = &enginev1.CheckOutput_ActionEffect{
            Effect: defaultEffect,
            Policy: noPolicyMatch,
        }
 
        if einfo, ok := result.effects[action]; ok {
            ae := output.Actions[action]
            ae.Effect = einfo.Effect
            ae.Policy = einfo.Policy
            ae.Scope = einfo.Scope
        }
    }
 
    output.EffectiveDerivedRoles = result.effectiveDerivedRoles
    output.ValidationErrors = result.validationErrors
    output.Outputs = result.outputs
 
    return output, nil
}
  • buildEvaluationCtx 处理
func (engine *Engine) buildEvaluationCtx(ctx context.Context, eparams evalParams, input *enginev1.CheckInput) (*evaluationCtx, error) {
    ec := &evaluationCtx{}
 
    principal 以及resource policy 的处理,后边实际执行需要使用具体的实现进行evaluate
 
    // get the principal policy check
    ppName, ppVersion, ppScope := engine.policyAttr(input.Principal.Id, input.Principal.PolicyVersion, input.Principal.Scope)
    ppCheck, err := engine.getPrincipalPolicyEvaluator(ctx, eparams, ppName, ppVersion, ppScope)
    if err != nil {
        return nil, fmt.Errorf("failed to get check for [%s.%s]: %w", ppName, ppVersion, err)
    }
    ec.addCheck(ppCheck)
 
    // get the resource policy check
    rpName, rpVersion, rpScope := engine.policyAttr(input.Resource.Kind, input.Resource.PolicyVersion, input.Resource.Scope)
    rpCheck, err := engine.getResourcePolicyEvaluator(ctx, eparams, rpName, rpVersion, rpScope)
    if err != nil {
        return nil, fmt.Errorf("failed to get check for [%s.%s]: %w", rpName, rpVersion, err)
    }
    ec.addCheck(rpCheck)
 
    return ec, nil
}
  • evaluate实际执行
func (ec *evaluationCtx) evaluate(ctx context.Context, tctx tracer.Context, input *enginev1.CheckInput) (*evaluationResult, error) {
    ctx, span := tracing.StartSpan(ctx, "engine.EvalCtxEvaluate")
    defer span.End()
 
    resp := &evaluationResult{}
    if ec.numChecks == 0 {
        tracing.MarkFailed(span, http.StatusNotFound, errNoPoliciesMatched)
 
        resp.setDefaultsForUnmatchedActions(tctx, input)
        return resp, nil
    }
 
    for i := 0; i < ec.numChecks; i++ {
        c := ec.checks[i]
 
        result, err := c.Evaluate(ctx, tctx, input)
        if err != nil {
            logging.FromContext(ctx).Error("Failed to evaluate policy", zap.Error(err))
            tracing.MarkFailed(span, http.StatusInternalServerError, err)
 
            return nil, fmt.Errorf("failed to execute policy: %w", err)
        }
 
        incomplete := resp.merge(result)
        if !incomplete {
            return resp, nil
        }
    }
 
    tracing.MarkFailed(span, http.StatusNotFound, errNoPoliciesMatched)
    resp.setDefaultsForUnmatchedActions(tctx, input)
 
    return resp, nil
}

对于Evaluate目前有三种实现,如下。包含了resource 以及principal

 

  • principalPolicyEvaluator 的处理
    内部处理都基于了google 的cel-go 包
 
func (ppe *principalPolicyEvaluator) Evaluate(ctx context.Context, tctx tracer.Context, input *enginev1.CheckInput) (*PolicyEvalResult, error) {
    _, span := tracing.StartSpan(ctx, "principal_policy.Evaluate")
    span.SetAttributes(tracing.PolicyFQN(ppe.policy.Meta.Fqn))
    defer span.End()
 
    policyKey := namer.PolicyKeyFromFQN(ppe.policy.Meta.Fqn)
    evalCtx := newEvalContext(ppe.evalParams, checkInputToRequest(input))
    result := newEvalResult(input.Actions)
 
    pctx := tctx.StartPolicy(ppe.policy.Meta.Fqn)
    for _, p := range ppe.policy.Policies {
        actionsToResolve := result.unresolvedActions()
        if len(actionsToResolve) == 0 {
            return result, nil
        }
 
        sctx := pctx.StartScope(p.Scope)
        // evaluate the variables of this policy
        variables, err := evalCtx.evaluateVariables(sctx.StartVariables(), p.OrderedVariables)
        if err != nil {
            sctx.Failed(err, "Failed to evaluate variables")
            return nil, fmt.Errorf("failed to evaluate variables: %w", err)
        }
 
        for resource, resourceRules := range p.ResourceRules {
            rctx := sctx.StartResource(resource)
            if !util.MatchesGlob(resource, input.Resource.Kind) {
                rctx.Skipped(nil, "Did not match input resource kind")
                continue
            }
 
            for _, rule := range resourceRules.ActionRules {
                matchedActions := util.FilterGlob(rule.Action, actionsToResolve)
                ruleActivated := false
                for _, action := range matchedActions {
                    actx := rctx.StartAction(action)
                    ok, err := evalCtx.satisfiesCondition(actx.StartCondition(), rule.Condition, variables)
                    if err != nil {
                        actx.Skipped(err, "Error evaluating condition")
                        continue
                    }
 
                    if !ok {
                        actx.Skipped(nil, "Condition not satisfied")
                        continue
                    }
 
                    result.setEffect(action, EffectInfo{Effect: rule.Effect, Policy: policyKey, Scope: p.Scope})
                    actx.AppliedEffect(rule.Effect, "")
                    ruleActivated = true
                }
 
                // evaluate output expression if the rule was activated
                if ruleActivated && rule.Output != nil {
                    octx := rctx.StartOutput(rule.Name)
 
                    output := &enginev1.OutputEntry{
                        Src: namer.RuleFQN(ppe.policy.Meta, p.Scope, rule.Name),
                        Val: evalCtx.evaluateProtobufValueCELExpr(rule.Output.Checked, variables),
                    }
                    result.Outputs = append(result.Outputs, output)
 
                    octx.ComputedOutput(output)
                }
            }
        }
    }
 
    result.setDefaultEffect(pctx, EffectInfo{Effect: effectv1.Effect_EFFECT_NO_MATCH})
    return result, nil
}
  • resourcePolicyEvaluator 的处理
    内部处理都基于了google 的cel-go 包
 
func (rpe *resourcePolicyEvaluator) Evaluate(ctx context.Context, tctx tracer.Context, input *enginev1.CheckInput) (*PolicyEvalResult, error) {
    _, span := tracing.StartSpan(ctx, "resource_policy.Evaluate")
    span.SetAttributes(tracing.PolicyFQN(rpe.policy.Meta.Fqn))
    defer span.End()
 
    policyKey := namer.PolicyKeyFromFQN(rpe.policy.Meta.Fqn)
    request := checkInputToRequest(input)
    result := newEvalResult(input.Actions)
    effectiveRoles := internal.ToSet(input.Principal.Roles)
 
    pctx := tctx.StartPolicy(rpe.policy.Meta.Fqn)
 
    // validate the input
    vr, err := rpe.schemaMgr.ValidateCheckInput(ctx, rpe.policy.Schemas, input)
    if err != nil {
        pctx.Failed(err, "Error during validation")
 
        return nil, fmt.Errorf("failed to validate input: %w", err)
    }
 
    if len(vr.Errors) > 0 {
        result.ValidationErrors = vr.Errors.SchemaErrors()
 
        pctx.Failed(vr.Errors, "Validation errors")
 
        if vr.Reject {
            for _, action := range input.Actions {
                actx := pctx.StartAction(action)
 
                result.setEffect(action, EffectInfo{Effect: effectv1.Effect_EFFECT_DENY, Policy: policyKey})
 
                actx.AppliedEffect(effectv1.Effect_EFFECT_DENY, "Rejected due to validation failures")
            }
            return result, nil
        }
    }
 
    // evaluate policies in the set
    for _, p := range rpe.policy.Policies {
        // Get the actions that are yet to be resolved. This is to implement first-match-wins semantics.
        // Within the context of a single policy, later rules can potentially override the result for an action (unless it was DENY).
        actionsToResolve := result.unresolvedActions()
        if len(actionsToResolve) == 0 {
            return result, nil
        }
 
        sctx := pctx.StartScope(p.Scope)
 
        evalCtx := newEvalContext(rpe.evalParams, request)
 
        // calculate the set of effective derived roles
        effectiveDerivedRoles := make(internal.StringSet, len(p.DerivedRoles))
        for drName, dr := range p.DerivedRoles {
            dctx := sctx.StartDerivedRole(drName)
            if !internal.SetIntersects(dr.ParentRoles, effectiveRoles) {
                dctx.Skipped(nil, "No matching roles")
                continue
            }
 
            // evaluate variables of this derived roles set
            drVariables, err := evalCtx.evaluateVariables(dctx.StartVariables(), dr.OrderedVariables)
            if err != nil {
                dctx.Skipped(err, "Error evaluating variables")
                continue
            }
 
            ok, err := evalCtx.satisfiesCondition(dctx.StartCondition(), dr.Condition, drVariables)
            if err != nil {
                dctx.Skipped(err, "Error evaluating condition")
                continue
            }
 
            if !ok {
                dctx.Skipped(nil, "Condition not satisfied")
                continue
            }
 
            effectiveDerivedRoles[drName] = struct{}{}
            result.EffectiveDerivedRoles[drName] = struct{}{}
 
            dctx.Activated()
        }
 
        evalCtx = evalCtx.withEffectiveDerivedRoles(effectiveDerivedRoles)
 
        // evaluate the variables of this policy
        variables, err := evalCtx.evaluateVariables(sctx.StartVariables(), p.OrderedVariables)
        if err != nil {
            sctx.Failed(err, "Failed to evaluate variables")
            return nil, fmt.Errorf("failed to evaluate variables: %w", err)
        }
 
        // evaluate each rule until all actions have a result
        for _, rule := range p.Rules {
            rctx := sctx.StartRule(rule.Name)
 
            if !internal.SetIntersects(rule.Roles, effectiveRoles) && !internal.SetIntersects(rule.DerivedRoles, evalCtx.effectiveDerivedRoles) {
                rctx.Skipped(nil, "No matching roles or derived roles")
                continue
            }
 
            ruleActivated := false
            for actionGlob := range rule.Actions {
                matchedActions := util.FilterGlob(actionGlob, actionsToResolve)
                for _, action := range matchedActions {
                    actx := rctx.StartAction(action)
                    ok, err := evalCtx.satisfiesCondition(actx.StartCondition(), rule.Condition, variables)
                    if err != nil {
                        actx.Skipped(err, "Error evaluating condition")
                        continue
                    }
 
                    if !ok {
                        actx.Skipped(nil, "Condition not satisfied")
                        continue
                    }
 
                    result.setEffect(action, EffectInfo{Effect: rule.Effect, Policy: policyKey, Scope: p.Scope})
                    actx.AppliedEffect(rule.Effect, "")
                    ruleActivated = true
                }
            }
 
            // evaluate output expression if the rule was activated
            if ruleActivated && rule.Output != nil {
                octx := rctx.StartOutput(rule.Name)
 
                output := &enginev1.OutputEntry{
                    Src: namer.RuleFQN(rpe.policy.Meta, p.Scope, rule.Name),
                    Val: evalCtx.evaluateProtobufValueCELExpr(rule.Output.Checked, variables),
                }
                result.Outputs = append(result.Outputs, output)
 
                octx.ComputedOutput(output)
            }
        }
    }
 
    // set the default effect for actions that were not matched
    result.setDefaultEffect(pctx, EffectInfo{Effect: effectv1.Effect_EFFECT_DENY, Policy: policyKey})
 
    return result, nil
}

说明

cerbos 内部check 的处理还是比较复杂的,而且属于cerbos 的核心部分

参考资料

https://cerbos.dev/
https://cerbos.dev/product-cerbos-hub
https://github.com/cerbos/cerbos
https://github.com/google/cel-spec

标签:return,err,nil,ctx,golang,cerbos,result,policy,input
From: https://www.cnblogs.com/rongfengliang/p/17879265.html

相关文章

  • golang的蓝牙通信库
    github.com/tinygo-org/bluetooth:TinyGo是一个Go语言编译器,它专注于微控制器和小型计算机系统。这个库是TinyGo项目的一部分,用于支持蓝牙低功耗(BLE)设备。github.com/go-ble/ble:这是一个轻量级的Golang库,旨在简化与蓝牙低功耗设备的交互。它支持多种平台,并提供了一种简单的A......
  • cerbos lite webassembly 处理简单说明
    上次简单说明了下cerboslite对于webassemblypolicy集成的说明,通过查看liteclient简单说明下参考处理ci/cd集成处理这个也比较符合官方hub的ci/cd机制,核心是通过git的repo管理,集成ci/cd构建webassembly文件,webassembly核心exporter的方法主要是图片右下方的,之后......
  • golang之媒体处理
    [视频]获取视频封面图:1)如果是使用oss的话,可以添加指定的后缀生成指定图片  获取视频长度:1)若是oss上的视频,则可以使用阿里云的IMM中的提取视频信息的服务注意这里获取需要使用到签名之后获取对应的数据 这里使用基于阿里云oss包: github.com/aliyun/aliyun-o......
  • go-carbon v2.2.14 发布,轻量级、语义化、对开发者友好的 Golang 时间处理库
    carbon是一个轻量级、语义化、对开发者友好的golang时间处理库,支持链式调用。目前已被awesome-go收录,如果您觉得不错,请给个star吧github.com/golang-module/carbongitee.com/golang-module/carbon安装使用Golang版本大于等于1.16//使用github库goget-ugithu......
  • Golang使用kcp
    安装goget-ugithub.com/xtaci/kcp-goimport("fmt""github.com/xtaci/kcp-go""golang.org/x/net/ipv4""golang.org/x/net/ipv6""net")//KCP服务器funcserver(){//创建一个UDP连接u......
  • cerbos hub 流程参考
    内容来自官方文档,主要是学习下cerboshub是如何进行policy的集成的参考流程说明目前关于webassembly部分官方也没有相关详细的介绍,但是其他部分基本都有相关比较详细的说明参考资料https://docs.cerbos.dev/cerbos-hub/https://docs.cerbos.dev/cerbos-hub/decision-point......
  • Golang中如何自定义时间类型进行xml、json的序列化/反序列化
    在日常开发工作中,我们进行会遇到将struct序列化json字符串以及将json字符串反序列化为struct的场景,大家也对此十分熟悉。最近工作中,遇到了需要将struct序列化xml字符串以及将xml字符串反序列化为struct的场景,对于普通类型的字段,比如int、string等类型,直接......
  • Golang学习笔记-定时任务
    指定具体时间执行packagemainimport( "fmt" "time")funcmain(){ //指定执行时间为2023-11-2900:00:00 executionTime:=time.Date(2023,time.November,29,0,0,0,0,time.UTC) //当前时间 now:=time.Now().UTC() //计算距离执行时间的持续时间 d......
  • golang常用包详解之: errgroup
    前言:并发编程在现代软件开发中变得越来越重要。Go语言通过goroutine和channel等语言特性为并发编程提供了非常强大的支持,但是在实际开发中,如何有效管理多个goroutine并处理它们可能产生的错误是一个挑战。这时,Go语言的官方库中的errgroup包就能发挥作用。正文:1.errgroup包概述errg......
  • Golang-常见数据结构实现原理
    chan 1.chan数据结构 src/runtime/chan.go:hchan定义了channel的数据结构:typehchanstruct{qcountuint//当前队列中剩余元素个数dataqsizuint//环形队列长度,即可以存放的元素个数bufunsafe.Pointer//环形队列指针......