forked from TrueCloudLab/policy-engine
185 lines
4.8 KiB
Go
185 lines
4.8 KiB
Go
|
package policyengine
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"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 (c *Chain) Bytes() []byte {
|
||
|
data, err := json.Marshal(c)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return data
|
||
|
}
|
||
|
|
||
|
func (c *Chain) DecodeBytes(b []byte) error {
|
||
|
return json.Unmarshal(b, 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(req Request) bool {
|
||
|
var val string
|
||
|
switch c.Object {
|
||
|
case ObjectResource:
|
||
|
val = req.Resource().Property(c.Key)
|
||
|
case ObjectRequest:
|
||
|
val = req.Property(c.Key)
|
||
|
default:
|
||
|
panic(fmt.Sprintf("unknown condition type: %d", c.Object))
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|