Skip to content

Commit

Permalink
feat: support inverting header matches for rate limit
Browse files Browse the repository at this point in the history
Signed-off-by: Rudrakh Panigrahi <[email protected]>
  • Loading branch information
rudrakhp committed Sep 29, 2024
1 parent b91f296 commit 059134c
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 5 deletions.
7 changes: 5 additions & 2 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,9 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
fallthrough
case *header.Type == egv1a1.HeaderMatchExact && header.Value != nil:
m := &ir.StringMatch{
Name: header.Name,
Exact: header.Value,
Name: header.Name,
Exact: header.Value,
Invert: header.Invert,
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)
case *header.Type == egv1a1.HeaderMatchRegularExpression && header.Value != nil:
Expand All @@ -721,12 +722,14 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
m := &ir.StringMatch{
Name: header.Name,
SafeRegex: header.Value,
Invert: header.Invert,

Check warning on line 725 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L725

Added line #L725 was not covered by tests
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)
case *header.Type == egv1a1.HeaderMatchDistinct && header.Value == nil:
m := &ir.StringMatch{
Name: header.Name,
Distinct: true,
Invert: header.Invert,
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ backendTrafficPolicies:
value: one
- name: x-org-id
type: Distinct
- name: x-org-id
value: admin
invert: true
limit:
requests: 10
unit: Hour
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ backendTrafficPolicies:
value: one
- name: x-org-id
type: Distinct
- invert: true
name: x-org-id
value: admin
limit:
requests: 10
unit: Hour
Expand Down Expand Up @@ -306,6 +309,10 @@ xdsIR:
name: x-user-id
- distinct: true
name: x-org-id
- distinct: false
exact: admin
invert: true
name: x-org-id
limit:
requests: 10
unit: Hour
Expand Down
6 changes: 6 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var (
ErrDestEndpointUDSPortInvalid = errors.New("field Port must not be specified for Unix Domain Socket address")
ErrDestEndpointUDSHostInvalid = errors.New("field Host must not be specified for Unix Domain Socket address")
ErrStringMatchConditionInvalid = errors.New("only one of the Exact, Prefix, SafeRegex or Distinct fields must be set")
ErrStringMatchInvertDistinctInvalid = errors.New("only one of the Invert or Distinct fields can be set")
ErrStringMatchNameIsEmpty = errors.New("field Name must be specified")
ErrDirectResponseStatusInvalid = errors.New("only HTTP status codes 100 - 599 are supported for DirectResponse")
ErrRedirectUnsupportedStatus = errors.New("only HTTP status codes 301 and 302 are supported for redirect filters")
Expand Down Expand Up @@ -1443,6 +1444,8 @@ type StringMatch struct {
// Distinct match condition.
// Used to match any and all possible unique values encountered within the Name field.
Distinct bool `json:"distinct" yaml:"distinct"`
// Invert inverts the final match decision
Invert *bool `json:"invert,omitempty" yaml:"invert,omitempty"`
}

// Validate the fields within the StringMatch structure
Expand All @@ -1465,6 +1468,9 @@ func (s StringMatch) Validate() error {
if s.Name == "" {
errs = errors.Join(errs, ErrStringMatchNameIsEmpty)
}
if s.Invert != nil && *s.Invert {
errs = errors.Join(errs, ErrStringMatchInvertDistinctInvalid)
}
matchCount++
}

Expand Down
19 changes: 18 additions & 1 deletion internal/ir/xds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1182,12 +1182,20 @@ func TestValidateStringMatch(t *testing.T) {
want error
}{
{
name: "happy",
name: "happy exact",
input: StringMatch{
Exact: ptr.To("example"),
},
want: nil,
},
{
name: "happy distinct",
input: StringMatch{
Distinct: true,
Name: "example",
},
want: nil,
},
{
name: "no fields set",
input: StringMatch{},
Expand All @@ -1202,6 +1210,15 @@ func TestValidateStringMatch(t *testing.T) {
},
want: ErrStringMatchConditionInvalid,
},
{
name: "both invert and distinct fields are set",
input: StringMatch{
Distinct: true,
Name: "example",
Invert: ptr.To(true),
},
want: ErrStringMatchInvertDistinctInvalid,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions internal/ir/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion internal/xds/translator/local_ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,17 @@ func buildRouteLocalRateLimits(local *ir.LocalRateLimit) (
StringMatch: buildXdsStringMatcher(match),
},
}
expectMatch := true
if match.Invert != nil && *match.Invert {
expectMatch = false

Check warning on line 223 in internal/xds/translator/local_ratelimit.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/local_ratelimit.go#L223

Added line #L223 was not covered by tests
}
action := &routev3.RateLimit_Action{
ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
DescriptorKey: descriptorKey,
DescriptorValue: descriptorVal,
ExpectMatch: &wrapperspb.BoolValue{
Value: true,
Value: expectMatch,
},
Headers: []*routev3.HeaderMatcher{headerMatcher},
},
Expand Down
6 changes: 5 additions & 1 deletion internal/xds/translator/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,17 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
StringMatch: buildXdsStringMatcher(match),
},
}
expectMatch := true
if match.Invert != nil && *match.Invert {
expectMatch = false

Check warning on line 185 in internal/xds/translator/ratelimit.go

View check run for this annotation

Codecov / codecov/patch

internal/xds/translator/ratelimit.go#L185

Added line #L185 was not covered by tests
}
action := &routev3.RateLimit_Action{
ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
DescriptorKey: descriptorKey,
DescriptorValue: descriptorVal,
ExpectMatch: &wrapperspb.BoolValue{
Value: true,
Value: expectMatch,
},
Headers: []*routev3.HeaderMatcher{headerMatcher},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: "first-listener"
address: "0.0.0.0"
port: 10080
hostnames:
- "*"
path:
mergeSlashes: true
escapedSlashesAction: UnescapeAndRedirect
routes:
- name: "first-route"
traffic:
rateLimit:
global:
rules:
- headerMatches:
- name: "x-user-id"
exact: "one"
invert: true
limit:
requests: 5
unit: second
pathMatch:
exact: "foo/bar"
destination:
name: "first-route-dest"
settings:
- endpoints:
- host: "1.2.3.4"
port: 50000
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: first-listener
domain: first-listener
descriptors:
- key: first-route
value: first-route
rate_limit: null
descriptors:
- key: rule-0-match-0
value: rule-0-match-0
rate_limit:
requests_per_unit: 5
unit: SECOND
unlimited: false
name: ""
replaces: []
descriptors: []
shadow_mode: false
detailed_metric: false
shadow_mode: false
detailed_metric: false

0 comments on commit 059134c

Please sign in to comment.