ppl: bubble up values, bug fixes (#2213)

This commit is contained in:
Caleb Doxsey 2021-05-18 14:01:36 -06:00 committed by GitHub
parent e138054cb9
commit bdccd4f785
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 218 additions and 118 deletions

View file

@ -8,8 +8,6 @@ import (
"github.com/pomerium/pomerium/pkg/policy/parser"
)
type conditionalGenerator func(dst *ast.RuleSet, policyCriteria []parser.Criterion) (*ast.Rule, error)
func (g *Generator) generateAndRule(dst *ast.RuleSet, policyCriteria []parser.Criterion) (*ast.Rule, error) {
rule := g.NewRule("and")
@ -22,7 +20,7 @@ func (g *Generator) generateAndRule(dst *ast.RuleSet, policyCriteria []parser.Cr
return nil, err
}
g.fillViaAnd(rule, expressions)
g.fillViaAnd(rule, false, expressions)
dst.Add(rule)
return rule, nil
@ -37,15 +35,12 @@ func (g *Generator) generateNotRule(dst *ast.RuleSet, policyCriteria []parser.Cr
// NOT => (NOT A) AND (NOT B)
expressions, err := g.generateCriterionRules(dst, policyCriteria)
terms, err := g.generateCriterionRules(dst, policyCriteria)
if err != nil {
return nil, err
}
for _, expr := range expressions {
expr.Negated = true
}
g.fillViaAnd(rule, expressions)
g.fillViaAnd(rule, true, terms)
dst.Add(rule)
return rule, nil
@ -58,12 +53,12 @@ func (g *Generator) generateOrRule(dst *ast.RuleSet, policyCriteria []parser.Cri
return rule, nil
}
expressions, err := g.generateCriterionRules(dst, policyCriteria)
terms, err := g.generateCriterionRules(dst, policyCriteria)
if err != nil {
return nil, err
}
g.fillViaOr(rule, expressions)
g.fillViaOr(rule, false, terms)
dst.Add(rule)
return rule, nil
@ -78,22 +73,19 @@ func (g *Generator) generateNorRule(dst *ast.RuleSet, policyCriteria []parser.Cr
// NOR => (NOT A) OR (NOT B)
expressions, err := g.generateCriterionRules(dst, policyCriteria)
terms, err := g.generateCriterionRules(dst, policyCriteria)
if err != nil {
return nil, err
}
for _, expr := range expressions {
expr.Negated = true
}
g.fillViaOr(rule, expressions)
g.fillViaOr(rule, true, terms)
dst.Add(rule)
return rule, nil
}
func (g *Generator) generateCriterionRules(dst *ast.RuleSet, policyCriteria []parser.Criterion) ([]*ast.Expr, error) {
var expressions []*ast.Expr
func (g *Generator) generateCriterionRules(dst *ast.RuleSet, policyCriteria []parser.Criterion) ([]*ast.Term, error) {
var terms []*ast.Term
for _, policyCriterion := range policyCriteria {
criterion, ok := g.criteria[policyCriterion.Name]
if !ok {
@ -106,27 +98,40 @@ func (g *Generator) generateCriterionRules(dst *ast.RuleSet, policyCriteria []pa
*dst = dst.Merge(additionalRules)
dst.Add(mainRule)
expr := ast.NewExpr(ast.VarTerm(string(mainRule.Head.Name)))
expressions = append(expressions, expr)
terms = append(terms, ast.VarTerm(string(mainRule.Head.Name)))
}
return expressions, nil
return terms, nil
}
func (g *Generator) fillViaAnd(rule *ast.Rule, expressions []*ast.Expr) {
for _, expr := range expressions {
rule.Body = append(rule.Body, expr)
}
}
func (g *Generator) fillViaOr(rule *ast.Rule, expressions []*ast.Expr) {
func (g *Generator) fillViaAnd(rule *ast.Rule, negated bool, terms []*ast.Term) {
currentRule := rule
for i, expr := range expressions {
currentRule.Head.Value = ast.VarTerm("v1")
for i, term := range terms {
nm := fmt.Sprintf("v%d", i+1)
currentRule.Body = append(currentRule.Body, ast.Assign.Expr(ast.VarTerm(nm), term))
expr := ast.NewExpr(ast.VarTerm(nm))
if negated {
expr.Negated = true
}
currentRule.Body = append(currentRule.Body, expr)
}
}
func (g *Generator) fillViaOr(rule *ast.Rule, negated bool, terms []*ast.Term) {
currentRule := rule
for i, term := range terms {
if i > 0 {
currentRule.Else = &ast.Rule{
Head: &ast.Head{},
}
currentRule.Else = &ast.Rule{Head: &ast.Head{}}
currentRule = currentRule.Else
}
currentRule.Body = ast.Body{expr}
nm := fmt.Sprintf("v%d", i+1)
currentRule.Head.Value = ast.VarTerm(nm)
currentRule.Body = append(currentRule.Body, ast.Assign.Expr(ast.VarTerm(nm), term))
expr := ast.NewExpr(ast.VarTerm(nm))
if negated {
expr.Negated = true
}
currentRule.Body = append(currentRule.Body, expr)
}
}

View file

@ -3,6 +3,7 @@ package generator
import (
"fmt"
"sort"
"github.com/open-policy-agent/opa/ast"
@ -52,35 +53,55 @@ func (g *Generator) Generate(policy *parser.Policy) (*ast.Module, error) {
rules.Add(ast.MustParseRule(`default allow = false`))
rules.Add(ast.MustParseRule(`default deny = false`))
for _, policyRule := range policy.Rules {
rule := &ast.Rule{
Head: &ast.Head{Name: ast.Var(policyRule.Action)},
}
fields := []struct {
criteria []parser.Criterion
generator conditionalGenerator
}{
{policyRule.And, g.generateAndRule},
{policyRule.Or, g.generateOrRule},
{policyRule.Not, g.generateNotRule},
{policyRule.Nor, g.generateNorRule},
}
for _, field := range fields {
if len(field.criteria) == 0 {
for _, action := range []parser.Action{parser.ActionAllow, parser.ActionDeny} {
var terms []*ast.Term
for _, policyRule := range policy.Rules {
if policyRule.Action != action {
continue
}
subRule, err := field.generator(&rules, field.criteria)
if err != nil {
return nil, err
}
rule.Body = append(rule.Body, ast.NewExpr(ast.VarTerm(string(subRule.Head.Name))))
}
rules.Add(rule)
if len(policyRule.And) > 0 {
subRule, err := g.generateAndRule(&rules, policyRule.And)
if err != nil {
return nil, err
}
terms = append(terms, ast.VarTerm(string(subRule.Head.Name)))
}
if len(policyRule.Or) > 0 {
subRule, err := g.generateOrRule(&rules, policyRule.Or)
if err != nil {
return nil, err
}
terms = append(terms, ast.VarTerm(string(subRule.Head.Name)))
}
if len(policyRule.Not) > 0 {
subRule, err := g.generateNotRule(&rules, policyRule.Not)
if err != nil {
return nil, err
}
terms = append(terms, ast.VarTerm(string(subRule.Head.Name)))
}
if len(policyRule.Nor) > 0 {
subRule, err := g.generateNorRule(&rules, policyRule.Nor)
if err != nil {
return nil, err
}
terms = append(terms, ast.VarTerm(string(subRule.Head.Name)))
}
}
if len(terms) > 0 {
rule := &ast.Rule{
Head: &ast.Head{
Name: ast.Var(action),
Value: ast.VarTerm("v1"),
},
}
g.fillViaOr(rule, false, terms)
rules.Add(rule)
}
}
return &ast.Module{
mod := &ast.Module{
Package: &ast.Package{
Path: ast.Ref{
ast.StringTerm("policy.rego"),
@ -89,7 +110,21 @@ func (g *Generator) Generate(policy *parser.Policy) (*ast.Module, error) {
},
},
Rules: rules,
}, nil
}
// move functions to the end
sort.SliceStable(mod.Rules, func(i, j int) bool {
return len(mod.Rules[i].Head.Args) < len(mod.Rules[j].Head.Args)
})
i := 1
ast.WalkRules(mod, func(r *ast.Rule) bool {
r.SetLoc(ast.NewLocation([]byte(r.String()), "", i, 1))
i++
return false
})
return mod, nil
}
// NewRule creates a new rule with a dynamically generated name.

View file

@ -44,6 +44,12 @@ func Test(t *testing.T) {
{Name: "accept"},
},
},
{
Action: parser.ActionAllow,
And: []parser.Criterion{
{Name: "accept"},
},
},
},
})
assert.NoError(t, err)
@ -65,10 +71,13 @@ accept_2 {
1 == 1
}
and_0 {
accept_0
accept_1
accept_2
and_0 = v1 {
v1 := accept_0
v1
v2 := accept_1
v2
v3 := accept_2
v3
}
accept_3 {
@ -83,16 +92,19 @@ accept_5 {
1 == 1
}
or_0 {
accept_3
or_0 = v1 {
v1 := accept_3
v1
}
else {
accept_4
else = v2 {
v2 := accept_4
v2
}
else {
accept_5
else = v3 {
v3 := accept_5
v3
}
accept_6 {
@ -107,10 +119,13 @@ accept_8 {
1 == 1
}
not_0 {
not accept_6
not accept_7
not accept_8
not_0 = v1 {
v1 := accept_6
not v1
v2 := accept_7
not v2
v3 := accept_8
not v3
}
accept_9 {
@ -125,23 +140,53 @@ accept_11 {
1 == 1
}
nor_0 {
not accept_9
nor_0 = v1 {
v1 := accept_9
not v1
}
else {
not accept_10
else = v2 {
v2 := accept_10
not v2
}
else {
not accept_11
else = v3 {
v3 := accept_11
not v3
}
allow {
and_0
or_0
not_0
nor_0
accept_12 {
1 == 1
}
and_1 = v1 {
v1 := accept_12
v1
}
allow = v1 {
v1 := and_0
v1
}
else = v2 {
v2 := or_0
v2
}
else = v3 {
v3 := not_0
v3
}
else = v4 {
v4 := nor_0
v4
}
else = v5 {
v5 := and_1
v5
}
`, string(format.MustAst(mod)))
}