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 }