mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-01 03:16:31 +02:00
* 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>
306 lines
6.9 KiB
Go
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"
|
|
)
|