Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/v1alpha1/ratelimit_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type LocalRateLimit struct {
// +optional
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.cost) || !has(foo.cost.response))", message="response cost is not supported for Local Rate Limits"
// +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.shadowMode))", message="shadow mode is not supported for Local Rate Limits"
Rules []RateLimitRule `json:"rules"`
}

Expand Down Expand Up @@ -110,6 +111,15 @@ type RateLimitRule struct {
//
// +optional
Shared *bool `json:"shared,omitempty"`
// ShadowMode indicates whether this rate-limit rule runs in shadow mode.
// When enabled, all rate-limiting operations are performed (cache lookups,
// counter updates, telemetry generation), but the outcome is never enforced.
// The request always succeeds, even if the configured limit is exceeded.
//
// Only supported for Global Rate Limits.
//
// +optional
ShadowMode *bool `json:"shadowMode,omitempty"`
}

type RateLimitCost struct {
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,15 @@ spec:
- requests
- unit
type: object
shadowMode:
description: |-
ShadowMode indicates whether this rate-limit rule runs in shadow mode.
When enabled, all rate-limiting operations are performed (cache lookups,
counter updates, telemetry generation), but the outcome is never enforced.
The request always succeeds, even if the configured limit is exceeded.

Only supported for Global Rate Limits.
type: boolean
shared:
description: |-
Shared determines whether this rate limit rule applies across all the policy targets.
Expand Down Expand Up @@ -1547,6 +1556,15 @@ spec:
- requests
- unit
type: object
shadowMode:
description: |-
ShadowMode indicates whether this rate-limit rule runs in shadow mode.
When enabled, all rate-limiting operations are performed (cache lookups,
counter updates, telemetry generation), but the outcome is never enforced.
The request always succeeds, even if the configured limit is exceeded.

Only supported for Global Rate Limits.
type: boolean
shared:
description: |-
Shared determines whether this rate limit rule applies across all the policy targets.
Expand All @@ -1561,6 +1579,8 @@ spec:
x-kubernetes-validations:
- message: response cost is not supported for Local Rate Limits
rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response))
- message: shadow mode is not supported for Local Rate Limits
rule: self.all(foo, !has(foo.shadowMode))
type: object
type:
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,15 @@ spec:
- requests
- unit
type: object
shadowMode:
description: |-
ShadowMode indicates whether this rate-limit rule runs in shadow mode.
When enabled, all rate-limiting operations are performed (cache lookups,
counter updates, telemetry generation), but the outcome is never enforced.
The request always succeeds, even if the configured limit is exceeded.

Only supported for Global Rate Limits.
type: boolean
shared:
description: |-
Shared determines whether this rate limit rule applies across all the policy targets.
Expand Down Expand Up @@ -1546,6 +1555,15 @@ spec:
- requests
- unit
type: object
shadowMode:
description: |-
ShadowMode indicates whether this rate-limit rule runs in shadow mode.
When enabled, all rate-limiting operations are performed (cache lookups,
counter updates, telemetry generation), but the outcome is never enforced.
The request always succeeds, even if the configured limit is exceeded.

Only supported for Global Rate Limits.
type: boolean
shared:
description: |-
Shared determines whether this rate limit rule applies across all the policy targets.
Expand All @@ -1560,6 +1578,8 @@ spec:
x-kubernetes-validations:
- message: response cost is not supported for Local Rate Limits
rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response))
- message: shadow mode is not supported for Local Rate Limits
rule: self.all(foo, !has(foo.shadowMode))
type: object
type:
description: |-
Expand Down
1 change: 1 addition & 0 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,7 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
HeaderMatches: make([]*ir.StringMatch, 0),
MethodMatches: make([]*ir.StringMatch, 0),
Shared: rule.Shared,
ShadowMode: rule.ShadowMode,
}

for _, match := range rule.ClientSelectors {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,4 @@ backendTrafficPolicies:
limit:
requests: 30
unit: Hour
shadowMode: true
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ backendTrafficPolicies:
limit:
requests: 30
unit: Hour
shadowMode: true
type: Global
targetRef:
group: gateway.networking.k8s.io
Expand Down Expand Up @@ -702,6 +703,7 @@ xdsIR:
distinct: false
exact: /test
name: ""
shadowMode: true
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
5 changes: 5 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -2299,6 +2299,11 @@ type RateLimitRule struct {
//
// +optional
Shared *bool `json:"shared,omitempty" yaml:"shared,omitempty"`
// ShadowMode determines whether this rate limit rule enables shadow mode.
// When enabled, rate limiting functions execute as normal (cache lookup, statistics),
// but the result is always success regardless of whether the limit was exceeded.
// +optional
ShadowMode *bool `json:"shadowMode,omitempty" yaml:"shadowMode,omitempty"`
// Name is a unique identifier for this rule, set as <policy-ns>/<policy-name>/rule/<rule-index>.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
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.

11 changes: 11 additions & 0 deletions internal/xds/translator/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,11 @@ func isRuleShared(rule *ir.RateLimitRule) bool {
return rule != nil && rule.Shared != nil && *rule.Shared
}

// Helper function to check if a specific rule is in shadow mode
func isRuleShadowMode(rule *ir.RateLimitRule) bool {
return rule != nil && rule.ShadowMode != nil && *rule.ShadowMode
}

// Helper function to map a global rule index to a domain-specific rule index
// This ensures that both shared and non-shared rules have indices starting from 0 in their own domains.
func getDomainRuleIndex(rules []*ir.RateLimitRule, globalRuleIdx int, ruleIsShared bool) int {
Expand Down Expand Up @@ -718,6 +723,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
// 1) Header Matches
for mIdx, match := range rule.HeaderMatches {
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
// Distinct vs HeaderValueMatch
if match.Distinct {
// RequestHeader case
Expand All @@ -742,6 +748,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
// 2) Method Match
if len(rule.MethodMatches) > 0 {
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
pbDesc.Key = getRouteRuleMethodDescriptor(domainRuleIdx)
pbDesc.Value = getRouteRuleMethodDescriptor(domainRuleIdx)

Expand All @@ -761,6 +768,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
// 3) Path Match
if rule.PathMatch != nil {
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
pbDesc.Key = getRouteRulePathDescriptor(domainRuleIdx)
pbDesc.Value = getRouteRulePathDescriptor(domainRuleIdx)

Expand Down Expand Up @@ -802,6 +810,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
if rule.CIDRMatch != nil {
// MaskedRemoteAddress case
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
pbDesc.Key = "masked_remote_address"
pbDesc.Value = rule.CIDRMatch.CIDR

Expand All @@ -816,6 +825,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi

if rule.CIDRMatch.Distinct {
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
pbDesc.Key = "remote_address"
cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
cur = pbDesc
Expand All @@ -826,6 +836,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
// 3) No Match (apply to all traffic)
if !rule.IsMatchSet() {
pbDesc := new(rlsconfv3.RateLimitDescriptor)
pbDesc.ShadowMode = isRuleShadowMode(rule)
pbDesc.Key = getRouteRuleDescriptor(domainRuleIdx, -1)
pbDesc.Value = getRouteRuleDescriptor(domainRuleIdx, -1)
head = pbDesc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
http:
- name: "first-listener"
address: "0.0.0.0"
port: 10080
hostnames:
- "*"
path:
mergeSlashes: true
escapedSlashesAction: UnescapeAndRedirect
routes:
- name: "first-route"
traffic:
rateLimit:
global:
rules:
- name: "test-namespace/test-policy-1/rule/0"
headerMatches:
- name: "x-user-id"
exact: "one"
- name: "x-user-id"
exact: "two"
- name: "x-org-id"
exact: "three"
cidrMatch:
cidr: 0.0.0.0/0
ip: 0.0.0.0
maskLen: 0
isIPv6: false
distinct: false
limit:
requests: 5
unit: second
shadowMode: true
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,47 @@
costSpecified: false
rateLimits:
- actions:
- ActionSpecifier:
GenericKey:
descriptor_key: first-route
descriptor_value: first-route
- ActionSpecifier:
HeaderValueMatch:
descriptor_key: rule-0-match-0
descriptor_value: rule-0-match-0
expect_match:
value: true
headers:
- HeaderMatchSpecifier:
StringMatch:
MatchPattern:
Exact: one
name: x-user-id
- ActionSpecifier:
HeaderValueMatch:
descriptor_key: rule-0-match-1
descriptor_value: rule-0-match-1
expect_match:
value: true
headers:
- HeaderMatchSpecifier:
StringMatch:
MatchPattern:
Exact: two
name: x-user-id
- ActionSpecifier:
HeaderValueMatch:
descriptor_key: rule-0-match-2
descriptor_value: rule-0-match-2
expect_match:
value: true
headers:
- HeaderMatchSpecifier:
StringMatch:
MatchPattern:
Exact: three
name: x-org-id
- ActionSpecifier:
MaskedRemoteAddress:
v4_prefix_mask_len: {}
routeName: first-route
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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: null
descriptors:
- key: rule-0-match-1
value: rule-0-match-1
rate_limit: null
descriptors:
- key: rule-0-match-2
value: rule-0-match-2
rate_limit: null
descriptors:
- key: masked_remote_address
value: 0.0.0.0/0
rate_limit:
requests_per_unit: 5
unit: SECOND
unlimited: false
name: ""
replaces: []
descriptors: []
shadow_mode: true
detailed_metric: false
shadow_mode: true
detailed_metric: false
shadow_mode: true
detailed_metric: false
shadow_mode: true
detailed_metric: false
shadow_mode: false
detailed_metric: false
1 change: 1 addition & 0 deletions release-notes/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ security updates: |
new features: |
Added support for weight in BackendRef API to enable traffic splitting for non-x-route resources.
Added support for removing headers based on matching criteria (Exact, Prefix, Suffix, RegularExpression) in ClientTrafficPolicy EarlyRequestHeaders and LateResponseHeaders.
Added support for Global rate limit shadow mode.

bug fixes: |
Fixed configured OIDC authorization endpoint being overridden by discovered endpoints from issuer's well-known URL.
Expand Down
1 change: 1 addition & 0 deletions site/content/en/latest/api/extension_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4336,6 +4336,7 @@ _Appears in:_
| `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | Limit holds the rate limit values.<br />This limit is applied for traffic flows when the selectors<br />compute to True, causing the request to be counted towards the limit.<br />The limit is enforced and the request is ratelimited, i.e. a response with<br />429 HTTP status code is sent back to the client when<br />the selected requests have reached the limit. |
| `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.<br />This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on<br />the request path and do not reduce the rate limit counters on the response path. |
| `shared` | _boolean_ | false | | Shared determines whether this rate limit rule applies across all the policy targets.<br />If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes).<br />Default: false. |
| `shadowMode` | _boolean_ | false | | ShadowMode indicates whether this rate-limit rule runs in shadow mode.<br />When enabled, all rate-limiting operations are performed (cache lookups,<br />counter updates, telemetry generation), but the outcome is never enforced.<br />The request always succeeds, even if the configured limit is exceeded.<br />Only supported for Global Rate Limits. |


#### RateLimitSelectCondition
Expand Down
Loading