diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index 08f5cb0c39..d1b1949b00 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -67,6 +67,7 @@ type LocalRateLimit struct { // // +optional // +kubebuilder:validation:MaxItems=16 + // +kubebuilder:validation:XValidation:rule="self.all(r, r.limit.requests > 0)", message="requests must be greater than 0 for local rate limits" // +kubebuilder:validation:XValidation:rule="self.all(r, !has(r.cost) || !has(r.cost.response))", message="response cost is not supported for Local Rate Limits" // +kubebuilder:validation:XValidation:rule="self.all(r, !has(r.limit.fromMetadata))", message="limit fromMetadata is not supported for Local Rate Limits" Rules []RateLimitRule `json:"rules"` @@ -436,7 +437,7 @@ type RateLimitValue struct { // Requests is the number of requests (or cost units, when used with // cost-based rate limiting) allowed per Unit. // - // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=0 // +kubebuilder:validation:Maximum=4294967295 // +kubebuilder:validation:Format=uint32 Requests uint32 `json:"requests"` 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 e5890ea0e9..ac1975ba2d 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 @@ -1829,7 +1829,7 @@ spec: cost-based rate limiting) allowed per Unit. format: uint32 maximum: 4294967295 - minimum: 1 + minimum: 0 type: integer unit: description: |- @@ -2240,7 +2240,7 @@ spec: cost-based rate limiting) allowed per Unit. format: uint32 maximum: 4294967295 - minimum: 1 + minimum: 0 type: integer unit: description: |- @@ -2288,6 +2288,9 @@ spec: maxItems: 16 type: array x-kubernetes-validations: + - message: requests must be greater than 0 for local rate + limits + rule: self.all(r, r.limit.requests > 0) - message: response cost is not supported for Local Rate Limits rule: self.all(r, !has(r.cost) || !has(r.cost.response)) - message: limit fromMetadata is not supported for Local Rate diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index d35e2f1cd9..1722f8f6fe 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -1828,7 +1828,7 @@ spec: cost-based rate limiting) allowed per Unit. format: uint32 maximum: 4294967295 - minimum: 1 + minimum: 0 type: integer unit: description: |- @@ -2239,7 +2239,7 @@ spec: cost-based rate limiting) allowed per Unit. format: uint32 maximum: 4294967295 - minimum: 1 + minimum: 0 type: integer unit: description: |- @@ -2287,6 +2287,9 @@ spec: maxItems: 16 type: array x-kubernetes-validations: + - message: requests must be greater than 0 for local rate + limits + rule: self.all(r, r.limit.requests > 0) - message: response cost is not supported for Local Rate Limits rule: self.all(r, !has(r.cost) || !has(r.cost.response)) - message: limit fromMetadata is not supported for Local Rate diff --git a/release-notes/current/bug_fixes/9273-ratelimit-requests-zero-block-rule.md b/release-notes/current/bug_fixes/9273-ratelimit-requests-zero-block-rule.md new file mode 100644 index 0000000000..39ef1d9cec --- /dev/null +++ b/release-notes/current/bug_fixes/9273-ratelimit-requests-zero-block-rule.md @@ -0,0 +1 @@ +Fixed BackendTrafficPolicy global rate limits rejecting `requests: 0`, allowing zero-request global rules to block matching traffic while local zero limits remain rejected. diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go index 6bd00eb1c0..b3bca2b891 100644 --- a/test/cel-validation/backendtrafficpolicy_test.go +++ b/test/cel-validation/backendtrafficpolicy_test.go @@ -1936,7 +1936,7 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { wantErrors: []string{}, }, { - desc: "rate limit requests of zero is rejected", + desc: "rate limit requests of one is accepted", mutate: func(btp *egv1a1.BackendTrafficPolicy) { btp.Spec = egv1a1.BackendTrafficPolicySpec{ PolicyTargetReferences: egv1a1.PolicyTargetReferences{ @@ -1953,7 +1953,7 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { Rules: []egv1a1.RateLimitRule{ { Limit: egv1a1.RateLimitValue{ - Requests: 0, + Requests: 1, Unit: "Minute", }, }, @@ -1962,10 +1962,36 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { }, } }, - wantErrors: []string{ - "spec.ratelimit.global.rules[0].limit.requests", - "should be greater than or equal to 1", + wantErrors: []string{}, + }, + { + desc: "global rate limit requests of zero is accepted", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("Gateway"), + Name: gwapiv1.ObjectName("eg"), + }, + }, + }, + RateLimit: &egv1a1.RateLimitSpec{ + Global: &egv1a1.GlobalRateLimit{ + Rules: []egv1a1.RateLimitRule{ + { + Limit: egv1a1.RateLimitValue{ + Requests: 0, + Unit: "Minute", + }, + }, + }, + }, + }, + } }, + wantErrors: []string{}, }, { desc: "local rate limit requests at uint32 max boundary is accepted", @@ -1996,6 +2022,35 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { }, wantErrors: []string{}, }, + { + desc: "local rate limit requests of one is accepted", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("Gateway"), + Name: gwapiv1.ObjectName("eg"), + }, + }, + }, + RateLimit: &egv1a1.RateLimitSpec{ + Local: &egv1a1.LocalRateLimit{ + Rules: []egv1a1.RateLimitRule{ + { + Limit: egv1a1.RateLimitValue{ + Requests: 1, + Unit: "Minute", + }, + }, + }, + }, + }, + } + }, + wantErrors: []string{}, + }, { desc: "local rate limit requests of zero is rejected", mutate: func(btp *egv1a1.BackendTrafficPolicy) { @@ -2024,8 +2079,7 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { } }, wantErrors: []string{ - "spec.ratelimit.local.rules[0].limit.requests", - "should be greater than or equal to 1", + "requests must be greater than 0 for local rate limits", }, }, {