policy-engine/chain.go
Denis Kirillov 8d291039d8 [#11] Support inverted action and resource
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-11-02 12:24:37 +03:00

229 lines
5.1 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)
}
// ChainID is the ID of rule chain.
type ChainID string
type Chain struct {
ID ChainID
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.
Actions Actions
// List of the resources the operation is applied to.
Resources Resources
// 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 Actions struct {
Inverted bool
Names []string
}
type Resources struct {
Inverted bool
Names []string
}
type Condition struct {
Op ConditionType
Object ObjectType
Key string
Value string
}
type ObjectType byte
const (
ObjectResource ObjectType = iota
ObjectRequest
ObjectActor
)
type ConditionType byte
// 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 = iota
CondStringNotEquals
CondStringEqualsIgnoreCase
CondStringNotEqualsIgnoreCase
CondStringLike
CondStringNotLike
CondStringLessThan
CondStringLessThanEquals
CondStringGreaterThan
CondStringGreaterThanEquals
// Numeric condition operators.
CondNumericEquals
CondNumericNotEquals
CondNumericLessThan
CondNumericLessThanEquals
CondNumericGreaterThan
CondNumericGreaterThanEquals
)
func (c ConditionType) String() string {
switch c {
case CondStringEquals:
return "StringEquals"
case CondStringNotEquals:
return "StringNotEquals"
case CondStringEqualsIgnoreCase:
return "StringEqualsIgnoreCase"
case CondStringNotEqualsIgnoreCase:
return "StringNotEqualsIgnoreCase"
case CondStringLike:
return "StringLike"
case CondStringNotLike:
return "StringNotLike"
case CondStringLessThan:
return "StringLessThan"
case CondStringLessThanEquals:
return "StringLessThanEquals"
case CondStringGreaterThan:
return "StringGreaterThan"
case CondStringGreaterThanEquals:
return "StringGreaterThanEquals"
case CondNumericEquals:
return "NumericEquals"
case CondNumericNotEquals:
return "NumericNotEquals"
case CondNumericLessThan:
return "NumericLessThan"
case CondNumericLessThanEquals:
return "NumericLessThanEquals"
case CondNumericGreaterThan:
return "NumericGreaterThan"
case CondNumericGreaterThanEquals:
return "NumericGreaterThanEquals"
default:
return "unknown condition type"
}
}
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: %d", 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)
case CondStringLessThan:
return val < c.Value
case CondStringLessThanEquals:
return val <= c.Value
case CondStringGreaterThan:
return val > c.Value
case CondStringGreaterThanEquals:
return val >= c.Value
}
}
func (r *Rule) Match(req Request) (status Status, matched bool) {
found := len(r.Resources.Names) == 0
for i := range r.Resources.Names {
if globMatch(req.Resource().Name(), r.Resources.Names[i]) != r.Resources.Inverted {
found = true
break
}
}
if !found {
return NoRuleFound, false
}
for i := range r.Actions.Names {
if globMatch(req.Operation(), r.Actions.Names[i]) != r.Actions.Inverted {
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
}