diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index 5e94940..dfeca6b 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -111,6 +111,20 @@ const ( CondIPAddress CondNotIPAddress + + CondStringEqualsIfExists + CondStringEqualsIgnoreCaseIfExists + CondStringLikeIfExists + CondStringLessThanIfExists + CondStringLessThanEqualsIfExists + CondStringGreaterThanIfExists + CondStringGreaterThanEqualsIfExists + + CondNumericEqualsIfExists + CondNumericLessThanIfExists + CondNumericLessThanEqualsIfExists + CondNumericGreaterThanIfExists + CondNumericGreaterThanEqualsIfExists ) var condToStr = []struct { @@ -127,12 +141,24 @@ var condToStr = []struct { {CondStringLessThanEquals, "StringLessThanEquals"}, {CondStringGreaterThan, "StringGreaterThan"}, {CondStringGreaterThanEquals, "StringGreaterThanEquals"}, + {CondStringEqualsIfExists, "StringEqualsIfExists"}, + {CondStringEqualsIgnoreCaseIfExists, "StringEqualsIgnoreCaseIfExists"}, + {CondStringLikeIfExists, "StringLikeIfExists"}, + {CondStringLessThanIfExists, "StringLessThanIfExists"}, + {CondStringLessThanEqualsIfExists, "StringLessThanEqualsIfExists"}, + {CondStringGreaterThanIfExists, "StringGreaterThanIfExists"}, + {CondStringGreaterThanEqualsIfExists, "StringGreaterThanEqualsIfExists"}, {CondNumericEquals, "NumericEquals"}, {CondNumericNotEquals, "NumericNotEquals"}, {CondNumericLessThan, "NumericLessThan"}, {CondNumericLessThanEquals, "NumericLessThanEquals"}, {CondNumericGreaterThan, "NumericGreaterThan"}, {CondNumericGreaterThanEquals, "NumericGreaterThanEquals"}, + {CondNumericEqualsIfExists, "NumericEqualsIfExists"}, + {CondNumericLessThanIfExists, "NumericLessThanIfExists"}, + {CondNumericLessThanEqualsIfExists, "NumericLessThanEqualsIfExists"}, + {CondNumericGreaterThanIfExists, "NumericGreaterThanIfExists"}, + {CondNumericGreaterThanEqualsIfExists, "NumericGreaterThanEqualsIfExists"}, {CondSliceContains, "SliceContains"}, {CondIPAddress, "IPAddress"}, {CondNotIPAddress, "NotIPAddress"}, @@ -157,11 +183,12 @@ func FormCondSliceContainsValue(values []string) string { func (c *Condition) Match(req resource.Request) bool { var val string + var exists bool switch c.Kind { case KindResource: - val = req.Resource().Property(c.Key) + val, exists = req.Resource().Property(c.Key) case KindRequest: - val = req.Property(c.Key) + val, exists = req.Property(c.Key) default: panic(fmt.Sprintf("unknown condition type: %d", c.Kind)) } @@ -170,30 +197,47 @@ func (c *Condition) Match(req resource.Request) bool { default: panic(fmt.Sprintf("unimplemented: %d", c.Op)) case CondStringEquals: - return val == c.Value + return exists && val == c.Value + case CondStringEqualsIfExists: + return !exists || val == c.Value case CondStringNotEquals: - return val != c.Value + return exists && val != c.Value case CondStringEqualsIgnoreCase: - return strings.EqualFold(val, c.Value) + return exists && strings.EqualFold(val, c.Value) + case CondStringEqualsIgnoreCaseIfExists: + return !exists || strings.EqualFold(val, c.Value) case CondStringNotEqualsIgnoreCase: - return !strings.EqualFold(val, c.Value) + return exists && !strings.EqualFold(val, c.Value) case CondStringLike: - return util.GlobMatch(val, c.Value) + return exists && util.GlobMatch(val, c.Value) + case CondStringLikeIfExists: + return !exists || util.GlobMatch(val, c.Value) case CondStringNotLike: - return !util.GlobMatch(val, c.Value) + return exists && !util.GlobMatch(val, c.Value) case CondStringLessThan: - return val < c.Value + return exists && val < c.Value + case CondStringLessThanIfExists: + return !exists || val < c.Value case CondStringLessThanEquals: - return val <= c.Value + return exists && val <= c.Value + case CondStringLessThanEqualsIfExists: + return !exists || val <= c.Value case CondStringGreaterThan: - return val > c.Value + return exists && val > c.Value + case CondStringGreaterThanIfExists: + return !exists || val > c.Value case CondStringGreaterThanEquals: - return val >= c.Value + return exists && val >= c.Value + case CondStringGreaterThanEqualsIfExists: + return !exists || val >= c.Value case CondSliceContains: return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value) case CondNumericEquals, CondNumericNotEquals, CondNumericLessThan, CondNumericLessThanEquals, CondNumericGreaterThan, CondNumericGreaterThanEquals: - return c.matchNumeric(val) + return exists && c.matchNumeric(val) + case CondNumericEqualsIfExists, CondNumericLessThanIfExists, CondNumericLessThanEqualsIfExists, CondNumericGreaterThanIfExists, + CondNumericGreaterThanEqualsIfExists: + return !exists || c.matchNumeric(val) case CondIPAddress, CondNotIPAddress: return c.matchIP(val) } @@ -213,17 +257,17 @@ func (c *Condition) matchNumeric(val string) bool { switch c.Op { default: panic(fmt.Sprintf("unimplemented: %d", c.Op)) - case CondNumericEquals: + case CondNumericEquals, CondNumericEqualsIfExists: return valDecimal.Equal(condVal) case CondNumericNotEquals: return !valDecimal.Equal(condVal) - case CondNumericLessThan: + case CondNumericLessThan, CondNumericLessThanIfExists: return valDecimal.LessThan(condVal) - case CondNumericLessThanEquals: + case CondNumericLessThanEquals, CondNumericLessThanEqualsIfExists: return valDecimal.LessThan(condVal) || valDecimal.Equal(condVal) - case CondNumericGreaterThan: + case CondNumericGreaterThan, CondNumericGreaterThanIfExists: return valDecimal.GreaterThan(condVal) - case CondNumericGreaterThanEquals: + case CondNumericGreaterThanEquals, CondNumericGreaterThanEqualsIfExists: return valDecimal.GreaterThan(condVal) || valDecimal.Equal(condVal) } } diff --git a/pkg/chain/chain_test.go b/pkg/chain/chain_test.go index a5154eb..7c5542a 100644 --- a/pkg/chain/chain_test.go +++ b/pkg/chain/chain_test.go @@ -398,6 +398,25 @@ func testNumericConditionsMatch(t *testing.T) { value: "50", status: Allow, }, + { + name: "value if exists from interval", + conditions: []Condition{ + { + Op: CondNumericLessThanIfExists, + Kind: KindRequest, + Key: propKey, + Value: "100", + }, + { + Op: CondNumericGreaterThanIfExists, + Kind: KindRequest, + Key: propKey, + Value: "80", + }, + }, + value: "90", + status: Allow, + }, } { t.Run(tc.name, func(t *testing.T) { resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) @@ -411,6 +430,16 @@ func testNumericConditionsMatch(t *testing.T) { }}} st, _ := ch.Match(request) require.Equal(t, tc.status.String(), st.String()) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found := ch.Match(emptyPropsRequest) + if strings.HasSuffix(tc.conditions[0].Op.String(), "IfExists") { + require.True(t, found) + require.Equal(t, tc.status.String(), st.String()) + } else { + require.False(t, found) + require.Equal(t, st, NoRuleFound) + } }) } } @@ -448,6 +477,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + _, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringEqualsIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringEqualsIfExists, + Kind: KindRequest, + Key: propKey, + Value: val, + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val, + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: "distort_tag_value" + val, + }) + + st, found = ch.Match(request) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringNotEquals.String(), func(t *testing.T) { @@ -479,6 +549,11 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + _, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) }) t.Run(CondStringEqualsIgnoreCase.String(), func(t *testing.T) { @@ -510,6 +585,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + _, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringEqualsIgnoreCaseIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringEqualsIgnoreCaseIfExists, + Kind: KindRequest, + Key: propKey, + Value: val, + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: strings.ToUpper(val), + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: strings.ToUpper("distort_tag_value" + val), + }) + + st, found = ch.Match(request) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringNotEqualsIgnoreCase.String(), func(t *testing.T) { @@ -541,6 +657,11 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) }) t.Run(CondStringLike.String(), func(t *testing.T) { @@ -572,6 +693,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringLikeIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringLikeIfExists, + Kind: KindRequest, + Key: propKey, + Value: val + "*", + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "suffix", + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: string([]byte(val)[:len(val)-1]), //cut last letter + }) + + st, found = ch.Match(request) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringNotLike.String(), func(t *testing.T) { @@ -603,6 +765,11 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) }) t.Run(CondStringLessThan.String(), func(t *testing.T) { @@ -634,6 +801,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringLessThanIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringLessThanIfExists, + Kind: KindRequest, + Key: propKey, + Value: val + "b", + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "a", + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "c", + }) + + st, found = ch.Match(request) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringLessThanEquals.String(), func(t *testing.T) { @@ -665,6 +873,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.True(t, found) require.Equal(t, Allow, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringLessThanEqualsIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringLessThanEqualsIfExists, + Kind: KindRequest, + Key: propKey, + Value: val + "b", + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "a", + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "b", + }) + + st, found = ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringGreaterThan.String(), func(t *testing.T) { @@ -696,6 +945,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.False(t, found) require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringGreaterThanIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringGreaterThanIfExists, + Kind: KindRequest, + Key: propKey, + Value: val + "b", + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "c", + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "b", + }) + + st, found = ch.Match(request) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) t.Run(CondStringGreaterThanEquals.String(), func(t *testing.T) { @@ -727,6 +1017,47 @@ func testStringConiditionsMatch(t *testing.T) { st, found = ch.Match(request) require.True(t, found) require.Equal(t, Allow, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.False(t, found) + require.Equal(t, NoRuleFound, st) + }) + + t.Run(CondStringGreaterThanEqualsIfExists.String(), func(t *testing.T) { + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondStringGreaterThanEqualsIfExists, + Kind: KindRequest, + Key: propKey, + Value: val + "b", + }}, + }}} + + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "c", + }) + + st, found := ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + request = testutil.NewRequest(native.MethodPutObject, resource, map[string]string{ + propKey: val + "b", + }) + + st, found = ch.Match(request) + require.True(t, found) + require.Equal(t, Allow, st) + + emptyPropsRequest := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{}) + st, found = ch.Match(emptyPropsRequest) + require.True(t, found) + require.Equal(t, Allow, st) }) } diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 7180f20..e258eb7 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -7,13 +7,17 @@ type Request interface { // Name is the operation name, such as Object.Put. Must not include wildcards. Operation() string // Property returns request properties, such as IP address of the origin. - Property(string) string + // The second return boolean value determines if the specified value exists within the properties. + Property(string) (string, bool) // Resource returns resource the operation is applied to. Resource() Resource } // Resource represents the resource operation is applied to. type Resource interface { + // Name is the resource name. Name() string - Property(string) string + // Property returns resource properties, such as object type etc. + // The second return boolean value determines if the specified value exists within the properties. + Property(string) (string, bool) } diff --git a/pkg/resource/testutil/resource.go b/pkg/resource/testutil/resource.go index 02e6394..5b0ce85 100644 --- a/pkg/resource/testutil/resource.go +++ b/pkg/resource/testutil/resource.go @@ -13,8 +13,9 @@ func (r *Resource) Name() string { return r.name } -func (r *Resource) Property(name string) string { - return r.properties[name] +func (r *Resource) Property(name string) (val string, exists bool) { + val, exists = r.properties[name] + return } func NewResource(name string, properties map[string]string) *Resource { @@ -40,8 +41,9 @@ func (r *Request) Resource() resourcepkg.Resource { return r.resource } -func (r *Request) Property(name string) string { - return r.properties[name] +func (r *Request) Property(name string) (val string, exists bool) { + val, exists = r.properties[name] + return } func NewRequest(op string, r *Resource, properties map[string]string) *Request {