diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index f7c2fc8ca0e..4baefa9ba02 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -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"` } @@ -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 { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7aa2585053a..b10cf57d04c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6127,6 +6127,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(bool) **out = **in } + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 8ad379ea307..48730d750ee 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -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. @@ -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. @@ -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: |- diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 5944352f6f8..c43c372cb9f 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -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. @@ -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. @@ -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: |- diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index aaa3c8eeb96..f5ca291730d 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -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 { diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml index ca70dbd1179..5e6a542b23d 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml @@ -177,3 +177,4 @@ backendTrafficPolicies: limit: requests: 30 unit: Hour + shadowMode: true diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml index f6da13e9a7e..7d574f4bfa0 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml @@ -66,6 +66,7 @@ backendTrafficPolicies: limit: requests: 30 unit: Hour + shadowMode: true type: Global targetRef: group: gateway.networking.k8s.io @@ -702,6 +703,7 @@ xdsIR: distinct: false exact: /test name: "" + shadowMode: true readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 103762be583..7404a49fa07 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -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 //rule/. Name string `json:"name,omitempty" yaml:"name,omitempty"` } diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index d8b53748865..d0c0495d409 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -3155,6 +3155,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(bool) **out = **in } + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 907e67dc8c1..8993003ae8a 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -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 { @@ -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 @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml b/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml new file mode 100644 index 00000000000..50d9020f4ed --- /dev/null +++ b/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml @@ -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 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml new file mode 100644 index 00000000000..1b3b97eee1d --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml @@ -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 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml new file mode 100644 index 00000000000..53987c2f525 --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml @@ -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 diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 4f05f226238..9af9fe11e24 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -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. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index d2008d8035c..93295f0d109 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -4336,6 +4336,7 @@ _Appears in:_ | `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | Limit holds the rate limit values.
This limit is applied for traffic flows when the selectors
compute to True, causing the request to be counted towards the limit.
The limit is enforced and the request is ratelimited, i.e. a response with
429 HTTP status code is sent back to the client when
the selected requests have reached the limit. | | `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.
This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on
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.
If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes).
Default: false. | +| `shadowMode` | _boolean_ | false | | 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. | #### RateLimitSelectCondition diff --git a/test/e2e/testdata/ratelimit-global-shadow-mode.yaml b/test/e2e/testdata/ratelimit-global-shadow-mode.yaml new file mode 100644 index 00000000000..d012beb0b41 --- /dev/null +++ b/test/e2e/testdata/ratelimit-global-shadow-mode.yaml @@ -0,0 +1,38 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: shadow-mode-ratelimit + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: shadow-mode-ratelimit + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - methods: + - value: "GET" + limit: + requests: 3 + unit: Hour + shadowMode: true +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: shadow-mode-ratelimit + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /shadow + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go index 12583031f34..be4ebb45855 100644 --- a/test/e2e/tests/ratelimit.go +++ b/test/e2e/tests/ratelimit.go @@ -44,6 +44,7 @@ func init() { RateLimitGlobalSharedCidrMatchTest, RateLimitGlobalSharedGatewayHeaderMatchTest, RateLimitGlobalMergeTest, + RateLimitGlobalShadowModeTest, ) } @@ -1279,6 +1280,51 @@ var RateLimitGlobalMergeTest = suite.ConformanceTest{ }, } +var RateLimitGlobalShadowModeTest = suite.ConformanceTest{ + ShortName: "RateLimitGlobalShadowModeTest", + Description: "Limit requests with shadow mode enabled, verifying that requests are not actually limited", + Manifests: []string{"testdata/ratelimit-global-shadow-mode.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + shadowRouteNN := types.NamespacedName{Name: "shadow-mode-ratelimit", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, shadowRouteNN) + + t.Run("matched with shadow mode can't got limited", func(t *testing.T) { + ratelimitHeader := make(map[string]string) + + expectOkResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/shadow", + Method: "GET", + }, + Response: http.Response{ + StatusCode: 200, + Headers: ratelimitHeader, + }, + Namespace: ns, + } + expectOkResp.Response.Headers["X-Ratelimit-Limit"] = "3, 3;w=3600" + expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + + // In shadow mode, requests are never blocked even when exceeding the rate limit. + // The rate limit is set to 3 requests per window, but we send 5 total requests. + // Shadow mode ensures all requests return 200 (never 429) + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // fire the next 2 requests (total: 3 requests, reaching the limit) + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected response for the first three requests: %v", err) + } + + // send 2 more requests that exceed the limit, but still expect 200 (not 429) due to shadow mode + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected response for requests exceeding the limit: %v", err) + } + }) + }, +} + // GotExactExpectedResponse consumes a request value; keeping value semantics avoids threading pointers through many tests. // //nolint:gocritic diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index caa05613ee2..aaa4be510bb 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -22532,6 +22532,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. @@ -22844,6 +22853,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. @@ -22858,6 +22876,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: |- diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 8126bcfcb45..11d2c37180d 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1712,6 +1712,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. @@ -2024,6 +2033,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. @@ -2038,6 +2056,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: |-