policy-engine/chain.go

194 lines
5 KiB
Go
Raw Permalink Normal View History

package policyengine
import (
"bytes"
"encoding/gob"
"fmt"
"strings"
)
// Engine ...
type Engine interface {
// IsAllowed returns status for the operation after all checks.
// The second return value signifies whether a matching rule was found.
IsAllowed(name Name, namespace string, r Request) (Status, bool)
}
type Chain struct {
Rules []Rule
}
func init() {
// FIXME #1 (@fyrchik): Introduce more optimal serialization format.
gob.Register(Chain{})
}
func (c *Chain) Bytes() []byte {
b := bytes.NewBuffer(nil)
e := gob.NewEncoder(b)
if err := e.Encode(c); err != nil {
panic(err)
}
return b.Bytes()
}
func (c *Chain) DecodeBytes(b []byte) error {
r := bytes.NewReader(b)
d := gob.NewDecoder(r)
return d.Decode(c)
}
type Rule struct {
Status Status
// Actions the operation is applied to.
Action []string
// List of the resources the operation is applied to.
Resource []string
// True iff individual conditions must be combined with the logical OR.
// By default AND is used, so _each_ condition must pass.
Any bool
Condition []Condition
}
type Condition struct {
Op ConditionType
Object ObjectType
Key string
Value string
}
type ObjectType byte
const (
ObjectResource ObjectType = iota
ObjectRequest
ObjectActor
)
// TODO @fyrchik: replace string with int-like type.
type ConditionType string
// TODO @fyrchik: reduce the number of conditions.
// Everything from here should be expressable, but we do not need them all.
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
const (
// String condition operators.
CondStringEquals ConditionType = "StringEquals"
CondStringNotEquals ConditionType = "StringNotEquals"
CondStringEqualsIgnoreCase ConditionType = "StringEqualsIgnoreCase"
CondStringNotEqualsIgnoreCase ConditionType = "StringNotEqualsIgnoreCase"
CondStringLike ConditionType = "StringLike"
CondStringNotLike ConditionType = "StringNotLike"
// Numeric condition operators.
CondNumericEquals ConditionType = "NumericEquals"
CondNumericNotEquals ConditionType = "NumericNotEquals"
CondNumericLessThan ConditionType = "NumericLessThan"
CondNumericLessThanEquals ConditionType = "NumericLessThanEquals"
CondNumericGreaterThan ConditionType = "NumericGreaterThan"
CondNumericGreaterThanEquals ConditionType = "NumericGreaterThanEquals"
// Date condition operators.
CondDateEquals ConditionType = "DateEquals"
CondDateNotEquals ConditionType = "DateNotEquals"
CondDateLessThan ConditionType = "DateLessThan"
CondDateLessThanEquals ConditionType = "DateLessThanEquals"
CondDateGreaterThan ConditionType = "DateGreaterThan"
CondDateGreaterThanEquals ConditionType = "DateGreaterThanEquals"
// Bolean condition operators.
CondBool ConditionType = "Bool"
// IP address condition operators.
CondIPAddress ConditionType = "IpAddress"
CondNotIPAddress ConditionType = "NotIpAddress"
// ARN condition operators.
CondArnEquals ConditionType = "ArnEquals"
CondArnLike ConditionType = "ArnLike"
CondArnNotEquals ConditionType = "ArnNotEquals"
CondArnNotLike ConditionType = "ArnNotLike"
)
func (c *Condition) Match(obj Request) bool {
var val string
switch c.Object {
case ObjectResource:
val = obj.Resource().Property(c.Key)
case ObjectRequest:
val = obj.Property(c.Key)
default:
return false
}
switch c.Op {
default:
panic(fmt.Sprintf("unimplemented: %s", c.Op))
case CondStringEquals:
return val == c.Value
case CondStringNotEquals:
return val != c.Value
case CondStringEqualsIgnoreCase:
return strings.EqualFold(val, c.Value)
case CondStringNotEqualsIgnoreCase:
return !strings.EqualFold(val, c.Value)
case CondStringLike:
return globMatch(val, c.Value)
case CondStringNotLike:
return !globMatch(val, c.Value)
}
}
func (r *Rule) Match(req Request) (status Status, matched bool) {
found := len(r.Resource) == 0
for i := range r.Resource {
if globMatch(req.Resource().Name(), r.Resource[i]) {
found = true
break
}
}
if !found {
return NoRuleFound, false
}
for i := range r.Action {
if globMatch(req.Operation(), r.Action[i]) {
return r.matchCondition(req)
}
}
return NoRuleFound, false
}
func (r *Rule) matchCondition(obj Request) (status Status, matched bool) {
if r.Any {
return r.matchAny(obj)
}
return r.matchAll(obj)
}
func (r *Rule) matchAny(obj Request) (status Status, matched bool) {
for i := range r.Condition {
if r.Condition[i].Match(obj) {
return r.Status, true
}
}
return NoRuleFound, false
}
func (r *Rule) matchAll(obj Request) (status Status, matched bool) {
for i := range r.Condition {
if !r.Condition[i].Match(obj) {
return NoRuleFound, false
}
}
return r.Status, true
}
func (c *Chain) Match(req Request) (status Status, matched bool) {
for i := range c.Rules {
status, matched := c.Rules[i].Match(req)
if matched {
return status, true
}
}
return NoRuleFound, false
}