Skip to content

RateLimitValue.Requests Minimum=1 prevents using limit 0 as a block rule in global rate limiting #9273

Description

@voraci0us

Description

PR #8798 added +kubebuilder:validation:Minimum=1 to RateLimitValue.Requests to fix silent uint32 truncation for large values. The Maximum=4294967295 bound from that PR is correct and should stay. However, Minimum=1 is too restrictive, it breaks a legitimate use case for global rate limiting that relies on requests: 0.

Setting requests: 0 on a global rate limit rule causes the rate limit service to return "over limit" for every matching request (0 requests per unit allowed = unconditional block). This made it possible to use the full clientSelector expressiveness (header matches, source CIDR, path, methods, invert) as targeted block rules without needing a separate mechanism. For example, blocking traffic matching a specific User-Agent or source CIDR in response to
an active vulnerability, without touching routing config:

rateLimit:
  global:
    rules:
      - clientSelectors:
          - headers:
              - name: User-Agent
                value: "bad-scanner/.*"
                type: RegularExpression
        limit:
          requests: 0
          unit: Second

After #8798, this is rejected at admission with a validation error.

Note: why requests: 0 is valid for global but not local

The two limit types have different underlying semantics:

  • Global: requests: 0 maps to requests_per_unit: 0 in the rate limit service config, which the service treats as "always over limit." This is well-defined and works correctly as a block.
  • Local: requests: 0 maps to MaxTokens: 0 in Envoy's TokenBucket proto, which has a > 0 constraint enforced by go-control-plane's proto validation (token_bucket.pb.validate.go: if m.GetMaxTokens() <= 0 { ... }). Sending this to Envoy would cause a NACK. Minimum=1 is therefore correct for local rate limits.

Proposed fix

  1. Change +kubebuilder:validation:Minimum=1+kubebuilder:validation:Minimum=0 on RateLimitValue.Requests.
  2. Add a CEL validation rule on LocalRateLimit rejecting requests: 0 with a clear message, e.g.:
    // +kubebuilder:validation:XValidation:rule="self.all(r, r.limit.requests > 0)",message="requests must be greater than 0 for local rate limits"
  3. Update the CEL test case added in fix: bound BackendTrafficPolicy rateLimit requests to uint32 max #8798 that expected requests: 0 to be rejected globally, it should now only be rejected on the local path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions