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 19, 2024
1 parent 9cf7828 commit bb18b2e
Show file tree
Hide file tree
Showing 5 changed files with 39 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
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 @@ -1392,6 +1393,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" yaml:"invert"`
}

// Validate the fields within the StringMatch structure
Expand All @@ -1414,6 +1417,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 @@ -1178,12 +1178,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 @@ -1198,6 +1206,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
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

0 comments on commit bb18b2e

Please sign in to comment.