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

feat: User-defined output from policy evaluation #1594

Merged
Merged
4 changes: 3 additions & 1 deletion api/genpb/cerbos/audit/v1/hashpb_helpers.pb.go

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

538 changes: 270 additions & 268 deletions api/genpb/cerbos/engine/v1/engine.pb.go

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion api/genpb/cerbos/engine/v1/engine.pb.validate.go

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

57 changes: 45 additions & 12 deletions api/genpb/cerbos/engine/v1/engine_vtproto.pb.go

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

4 changes: 3 additions & 1 deletion api/genpb/cerbos/engine/v1/hashpb_helpers.pb.go

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

4 changes: 3 additions & 1 deletion api/genpb/cerbos/private/v1/hashpb_helpers.pb.go

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

4 changes: 3 additions & 1 deletion api/genpb/cerbos/response/v1/hashpb_helpers.pb.go

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

2 changes: 1 addition & 1 deletion api/public/cerbos/engine/v1/engine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ message OutputEntry {
description: "Rule that matched to produce this output."
example: "\"resource.expense.v1/acme#rule-001\""
}];
string val = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
google.protobuf.Value val = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Dynamic output, determined by user defined rule output."
example: "\"some_string\""
}];
Expand Down
21 changes: 7 additions & 14 deletions internal/engine/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"github.com/google/cel-go/cel"
"go.uber.org/multierr"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/protobuf/types/known/structpb"
)

var ErrPolicyNotExecutable = errors.New("policy not executable")
Expand Down Expand Up @@ -150,24 +151,20 @@
}

// evaluate each rule until all actions have a result
for i, rule := range p.Rules {
for _, rule := range p.Rules {
rctx := sctx.StartRule(rule.Name)

if rule.Output != nil {
octx := rctx.StartOutput(rule.Name)
output, err := rpe.evalParams.evaluateStrCELExpr(rule.Output.Checked, variables, input)
if err != nil {
octx.Skipped(err, "Error evaluating output")
}

Check warning on line 162 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L158-L162

Added lines #L158 - L162 were not covered by tests

ruleName := rule.Name
if ruleName == "" {
ruleName = fmt.Sprintf("rule-%03d", i)
}
result.Outputs = append(result.Outputs, &enginev1.OutputEntry{
Src: fmt.Sprintf("%s#%s", rpe.policy.Meta.Fqn, ruleName),
Val: output,
Src: namer.RuleFQN(rpe.policy.Meta, p.Scope, rule.Name),
Val: structpb.NewStringValue(output),
Sambigeara marked this conversation as resolved.
Show resolved Hide resolved
})

Check warning on line 167 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L164-L167

Added lines #L164 - L167 were not covered by tests
}

if !internal.SetIntersects(rule.Roles, effectiveRoles) && !internal.SetIntersects(rule.DerivedRoles, effectiveDerivedRoles) {
Expand Down Expand Up @@ -238,7 +235,7 @@
continue
}

for i, rule := range resourceRules.ActionRules {
for _, rule := range resourceRules.ActionRules {
matchedActions := util.FilterGlob(rule.Action, actionsToResolve)
for _, action := range matchedActions {
actx := rctx.StartAction(action)
Expand All @@ -257,20 +254,16 @@
}

if rule.Output != nil {
octx := rctx.StartOutput(rule.Name)
output, err := ppe.evalParams.evaluateStrCELExpr(rule.Output.Checked, variables, input)
if err != nil {
octx.Skipped(err, "Error evaluating output")
}

Check warning on line 261 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L257-L261

Added lines #L257 - L261 were not covered by tests

ruleName := rule.Name
if ruleName == "" {
ruleName = fmt.Sprintf("rule-%03d", i)
}
result.Outputs = append(result.Outputs, &enginev1.OutputEntry{
Src: fmt.Sprintf("%s#%s", ppe.policy.Meta.Fqn, ruleName),
Val: output,
Src: namer.RuleFQN(ppe.policy.Meta, p.Scope, rule.Name),
Val: structpb.NewStringValue(output),
})

Check warning on line 266 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L263-L266

Added lines #L263 - L266 were not covered by tests
}
}
}
Expand Down Expand Up @@ -396,22 +389,22 @@
return boolVal, nil
}

func (ep evalParams) evaluateStrCELExpr(expr *exprpb.CheckedExpr, variables map[string]any, input *enginev1.CheckInput) (string, error) {
val, err := ep.evaluateCELExpr(expr, variables, input)
if err != nil {
return "", err
}

Check warning on line 396 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L392-L396

Added lines #L392 - L396 were not covered by tests

if val == nil {
return "", nil
}

Check warning on line 400 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L398-L400

Added lines #L398 - L400 were not covered by tests

strVal, ok := val.(string)
if !ok {
return "", nil
}

Check warning on line 405 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L402-L405

Added lines #L402 - L405 were not covered by tests

return strVal, nil

Check warning on line 407 in internal/engine/evaluator.go

View check run for this annotation

Codecov / codecov/patch

internal/engine/evaluator.go#L407

Added line #L407 was not covered by tests
}

func (ep evalParams) evaluateCELExpr(expr *exprpb.CheckedExpr, variables map[string]any, input *enginev1.CheckInput) (any, error) {
Expand Down
17 changes: 17 additions & 0 deletions internal/namer/namer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"strings"

policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1"
runtimev1 "github.com/cerbos/cerbos/api/genpb/cerbos/runtime/v1"
"github.com/cerbos/cerbos/internal/util"
)

Expand Down Expand Up @@ -217,6 +218,22 @@
return fmt.Sprintf("%s_rule-%03d", resource, idx)
}

// RuleFQN returns the FQN for the resource rule or principal resource action rule with scope granularity.
func RuleFQN(rpsMeta any, scope, ruleName string) string {
var policyFqn string

switch m := rpsMeta.(type) {
case *runtimev1.RunnableResourcePolicySet_Metadata:
policyFqn = ResourcePolicyFQN(m.Resource, m.Version, scope)
case *runtimev1.RunnablePrincipalPolicySet_Metadata:
policyFqn = PrincipalPolicyFQN(m.Principal, m.Version, scope)
default:
panic(fmt.Errorf("unknown runnable policy set meta type %T", m))

Check warning on line 231 in internal/namer/namer.go

View check run for this annotation

Codecov / codecov/patch

internal/namer/namer.go#L222-L231

Added lines #L222 - L231 were not covered by tests
}

return fmt.Sprintf("%s#%s", policyFqn, ruleName)

Check warning on line 234 in internal/namer/namer.go

View check run for this annotation

Codecov / codecov/patch

internal/namer/namer.go#L234

Added line #L234 was not covered by tests
}

type PolicyCoords struct {
Kind string
Name string
Expand Down
70 changes: 35 additions & 35 deletions internal/test/testdata/compile/any_role.yaml.golden
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
{
"fqn": "cerbos.resource.leave_request.v20210210",
"resourcePolicy": {
"meta": {
"fqn": "cerbos.resource.leave_request.v20210210",
"resource": "leave_request",
"version": "20210210"
"fqn": "cerbos.resource.leave_request.v20210210",
"resourcePolicy": {
"meta": {
"fqn": "cerbos.resource.leave_request.v20210210",
"resource": "leave_request",
"version": "20210210"
},
"policies": [
"policies": [
{
"derivedRoles": {
"tester": {
"name": "tester",
"parentRoles": {
"*": {}
"derivedRoles": {
"tester": {
"name": "tester",
"parentRoles": {
"*": {}
}
}
},
"rules": [
"rules": [
{
"name": "wildcard",
"actions": {
"*": {}
"name": "wildcard",
"actions": {
"*": {}
},
"roles": {
"*": {}
"roles": {
"*": {}
},
"effect": "EFFECT_ALLOW"
"effect": "EFFECT_ALLOW"
},
{
"name": "rule-002",
"actions": {
"create": {}
"name": "rule-002",
"actions": {
"create": {}
},
"derivedRoles": {
"tester": {}
"derivedRoles": {
"tester": {}
},
"effect": "EFFECT_ALLOW"
"effect": "EFFECT_ALLOW"
}
],
"schemas": {
"principalSchema": {
"ref": "cerbos:///complex_object.json"
"schemas": {
"principalSchema": {
"ref": "cerbos:///complex_object.json"
},
"resourceSchema": {
"ref": "cerbos:///complex_object.json"
"resourceSchema": {
"ref": "cerbos:///complex_object.json"
}
}
}
],
"schemas": {
"principalSchema": {
"ref": "cerbos:///complex_object.json"
"schemas": {
"principalSchema": {
"ref": "cerbos:///complex_object.json"
},
"resourceSchema": {
"ref": "cerbos:///complex_object.json"
"resourceSchema": {
"ref": "cerbos:///complex_object.json"
}
}
}
Expand Down