pomerium/pkg/policy/generator/generator.go
2021-07-27 12:29:42 -06:00

137 lines
3.2 KiB
Go

// Package generator converts Pomerium Policy Language into Rego.
package generator
import (
"fmt"
"sort"
"github.com/open-policy-agent/opa/ast"
"github.com/pomerium/pomerium/pkg/policy/parser"
)
// A Generator generates a rego script from a policy.
type Generator struct {
ids map[string]int
criteria map[string]Criterion
}
// An Option configures the Generator.
type Option func(*Generator)
// WithCriterion adds a Criterion to the generator's known criteria.
func WithCriterion(criterionConstructor CriterionConstructor) Option {
return func(g *Generator) {
c := criterionConstructor(g)
g.criteria[c.Name()] = c
}
}
// New creates a new Generator.
func New(options ...Option) *Generator {
g := &Generator{
ids: make(map[string]int),
criteria: make(map[string]Criterion),
}
for _, o := range options {
o(g)
}
return g
}
// GetCriterion gets a Criterion for the given name.
func (g *Generator) GetCriterion(name string) (Criterion, bool) {
c, ok := g.criteria[name]
return c, ok
}
// Generate generates the rego module from a policy.
func (g *Generator) Generate(policy *parser.Policy) (*ast.Module, error) {
rules := ast.NewRuleSet()
rules.Add(ast.MustParseRule(`default allow = false`))
rules.Add(ast.MustParseRule(`default deny = false`))
for _, action := range []parser.Action{parser.ActionAllow, parser.ActionDeny} {
var terms []*ast.Term
for _, policyRule := range policy.Rules {
if policyRule.Action != action {
continue
}
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, terms)
rules.Add(rule)
}
}
mod := &ast.Module{
Package: &ast.Package{
Path: ast.Ref{
ast.StringTerm("policy.rego"),
ast.StringTerm("pomerium"),
ast.StringTerm("policy"),
},
},
Rules: rules,
}
// 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.
func (g *Generator) NewRule(name string) *ast.Rule {
id := g.ids[name]
g.ids[name]++
return &ast.Rule{
Head: &ast.Head{
Name: ast.Var(fmt.Sprintf("%s_%d", name, id)),
},
}
}