diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index d71d49f32ca9..8a40bfe7a3f7 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -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: @@ -721,12 +722,14 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) { m := &ir.StringMatch{ Name: header.Name, SafeRegex: header.Value, + Invert: header.Invert, } 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: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index a40cd8547665..6f6c36100843 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -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") @@ -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 @@ -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++ } diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 882aa090e552..d0c6fee600b3 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -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{}, @@ -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) { diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go index 1503758dfb42..ba330e22034c 100644 --- a/internal/xds/translator/local_ratelimit.go +++ b/internal/xds/translator/local_ratelimit.go @@ -218,13 +218,17 @@ func buildRouteLocalRateLimits(local *ir.LocalRateLimit) ( StringMatch: buildXdsStringMatcher(match), }, } + expectMatch := true + if match.Invert != nil && *match.Invert { + expectMatch = false + } 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}, }, diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 8e3e661f9d7e..660bc2a7dec0 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -180,13 +180,17 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [ StringMatch: buildXdsStringMatcher(match), }, } + expectMatch := true + if match.Invert != nil && *match.Invert { + expectMatch = false + } 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}, },