pomerium/pkg/policy/parser/grammar.go
dependabot[bot] ec495bb682
chore(deps): bump github.com/golangci/golangci-lint from 1.48.0 to 1.50.0 (#3667)
* chore(deps): bump github.com/golangci/golangci-lint

Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.48.0 to 1.50.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.48.0...v1.50.0)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* lint

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
2022-10-19 09:36:59 -06:00

306 lines
6.9 KiB
Go

package parser
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// A Policy is a policy made up of multiple allow or deny rules.
type Policy struct {
Rules []Rule
}
// MarshalJSON marshals the policy as JSON.
func (p *Policy) MarshalJSON() ([]byte, error) {
return json.Marshal(p.ToJSON())
}
// String converts the policy to a string.
func (p *Policy) String() string {
str, _ := p.MarshalJSON()
return string(str)
}
// ToJSON converts the policy to JSON.
func (p *Policy) ToJSON() Value {
var root Array
for _, r := range p.Rules {
root = append(root, r.ToJSON())
}
return root
}
// PolicyFromValue converts a value into a Policy.
func PolicyFromValue(v Value) (*Policy, error) {
rules, err := RulesFromValue(v)
if err != nil {
return nil, fmt.Errorf("invalid rules in policy: %w", err)
}
return &Policy{
Rules: rules,
}, nil
}
// A Rule is a policy rule with a corresponding action ("allow" or "deny"),
// and conditionals to determine if the rule matches or not.
type Rule struct {
Action Action
And []Criterion
Or []Criterion
Not []Criterion
Nor []Criterion
}
// RulesFromValue converts a Value into a slice of Rules. Only Arrays or Objects
// are supported.
func RulesFromValue(v Value) ([]Rule, error) {
switch t := v.(type) {
case Array:
return RulesFromArray(t)
case Object:
return RulesFromObject(t)
default:
return nil, fmt.Errorf("unsupported type for rule: %T", v)
}
}
// RulesFromArray converts an Array into a slice of Rules. Each element of the Array is
// converted using RulesFromObject and merged together.
func RulesFromArray(a Array) ([]Rule, error) {
var rules []Rule
for _, v := range a {
switch t := v.(type) {
case Object:
inner, err := RulesFromObject(t)
if err != nil {
return nil, err
}
rules = append(rules, inner...)
default:
return nil, fmt.Errorf("unsupported type for rules array: %T", v)
}
}
return rules, nil
}
// RulesFromObject converts an Object into a slice of Rules.
//
// One form is supported:
//
// 1. An object where the keys are the actions and the values are an object with "and", "or", or "not" fields:
// `{ "allow": { "and": [ {"groups": "group1"} ] } }`
func RulesFromObject(o Object) ([]Rule, error) {
var rules []Rule
for k, v := range o {
action, err := ActionFromValue(String(k))
if err != nil {
return nil, fmt.Errorf("invalid action in rule: %w", err)
}
oo, ok := v.(Object)
if !ok {
return nil, fmt.Errorf("invalid value for action in rule, expected Object, got %T", v)
}
rule := Rule{
Action: action,
}
err = rule.fillConditionalsFromObject(oo)
if err != nil {
return nil, err
}
rules = append(rules, rule)
}
// sort by action for deterministic ordering
sort.Slice(rules, func(i, j int) bool {
return rules[i].Action < rules[j].Action
})
return rules, nil
}
// MarshalJSON marshals the rule as JSON.
func (r *Rule) MarshalJSON() ([]byte, error) {
return json.Marshal(r.ToJSON())
}
// String converts the rule to a string.
func (r *Rule) String() string {
str, _ := r.MarshalJSON()
return string(str)
}
// ToJSON converts the rule to JSON.
func (r *Rule) ToJSON() Value {
body := Object{}
for _, op := range []struct {
operator string
criteria []Criterion
}{
{"and", r.And},
{"or", r.Or},
{"not", r.Not},
{"nor", r.Nor},
} {
if len(op.criteria) == 0 {
continue
}
var criteria Array
for _, c := range op.criteria {
criteria = append(criteria, c.ToJSON())
}
body[op.operator] = criteria
}
return Object{
string(r.Action): body,
}
}
func (r *Rule) fillConditionalsFromObject(o Object) error {
conditionals := []struct {
Name string
Criteria *[]Criterion
}{
{"and", &r.And},
{"or", &r.Or},
{"not", &r.Not},
{"nor", &r.Nor},
}
for _, cond := range conditionals {
if rawCriteria, ok := o[cond.Name]; ok {
criteria, err := CriteriaFromValue(rawCriteria)
if err != nil {
return fmt.Errorf("invalid criteria in \"%s\"): %w", cond.Name, err)
}
*cond.Criteria = criteria
}
}
for k := range o {
switch k {
case "and", "or", "not", "nor", "action":
default:
return fmt.Errorf("unsupported conditional \"%s\", only and, or, not, nor and action are allowed", k)
}
}
return nil
}
// A Criterion is used by a rule to determine if the rule matches or not.
//
// Criteria RegoRulesGenerators are registered based on the specified name.
// Data is arbitrary JSON data sent to the generator.
type Criterion struct {
Name string
SubPath string
Data Value
}
// CriteriaFromValue converts a Value into Criteria. Only Arrays are supported.
func CriteriaFromValue(v Value) ([]Criterion, error) {
switch t := v.(type) {
case Array:
return CriteriaFromArray(t)
default:
return nil, fmt.Errorf("unsupported type for criteria: %T", v)
}
}
// CriteriaFromArray converts an Array into Criteria. Each element of the Array is
// converted using CriterionFromObject.
func CriteriaFromArray(a Array) ([]Criterion, error) {
var criteria []Criterion
for _, v := range a {
switch t := v.(type) {
case Object:
inner, err := CriterionFromObject(t)
if err != nil {
return nil, err
}
criteria = append(criteria, *inner)
default:
return nil, fmt.Errorf("unsupported type for criteria array: %T", v)
}
}
return criteria, nil
}
// CriterionFromObject converts an Object into a Criterion.
//
// One form is supported:
//
// 1. An object where the keys are the names with a sub path and the values are the corresponding
// data for each Criterion: `{ "groups": "group1" }`
func CriterionFromObject(o Object) (*Criterion, error) {
if len(o) != 1 {
return nil, fmt.Errorf("each criteria may only contain a single key and value")
}
for k, v := range o {
name := k
subPath := ""
if idx := strings.Index(k, "/"); idx >= 0 {
name, subPath = k[:idx], k[idx+1:]
}
return &Criterion{
Name: name,
SubPath: subPath,
Data: v,
}, nil
}
// this can't happen
panic("each criteria may only contain a single key and value")
}
// MarshalJSON marshals the criterion as JSON.
func (c *Criterion) MarshalJSON() ([]byte, error) {
return json.Marshal(c.ToJSON())
}
// String converts the criterion to a string.
func (c *Criterion) String() string {
str, _ := c.MarshalJSON()
return string(str)
}
// ToJSON converts the criterion to JSON.
func (c *Criterion) ToJSON() Value {
nm := c.Name
if c.SubPath != "" {
nm += "/" + c.SubPath
}
return Object{nm: c.Data}
}
// An Action describe what to do when a rule matches, either "allow" or "deny".
type Action string
// ActionFromValue converts a Value into an Action.
func ActionFromValue(value Value) (Action, error) {
s, ok := value.(String)
if !ok {
return "", fmt.Errorf("unsupported type for action: %T", value)
}
switch Action(s) {
case ActionAllow:
return ActionAllow, nil
case ActionDeny:
return ActionDeny, nil
}
return "", fmt.Errorf("unsupported action: %s", s)
}
// Actions
const (
ActionAllow Action = "allow"
ActionDeny Action = "deny"
)