Skip to content

fix(ratelimit): reject rate limit rules with more than one sourceCIDR selector - #10046

Open
pujitha24 wants to merge 3 commits into
envoyproxy:mainfrom
pujitha24:auto/issue-10025
Open

pujitha24 wants to merge 3 commits into
envoyproxy:mainfrom
pujitha24:auto/issue-10025

Conversation

@pujitha24

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

When a global or local rate limit rule has more than one clientSelectors entry and more
than one of those entries carries a sourceCIDR, only the last sourceCIDR takes effect.
buildRateLimitRule (internal/gatewayapi/backendtrafficpolicy.go) loops over
rule.ClientSelectors and, for headers/methods, appends matches from every selector into a
slice so they combine (AND semantics, per the CRD doc: "All individual select conditions
must hold True"). But ir.RateLimitRule.CIDRMatch is a single *CIDRMatch pointer, so each
sourceCIDR selector overwrites the previous one instead of combining with it. The earlier
selector is dropped with no error, and the policy still reports Accepted: True. This is
especially dangerous when sourceCIDR + invert: true is used as an IP-exemption
mechanism: adding a second exempted CIDR silently un-exempts the first one.

Properly supporting multiple ANDed sourceCIDR selectors would require reworking
ir.RateLimitRule.CIDRMatch into a slice, regenerating deepcopy code, and reworking the
nested Envoy rate-limit-descriptor chains built in internal/xds/translator/ratelimit.go
and local_ratelimit.go (which key descriptors with fixed constant strings that would need
per-index uniqueness). That's a larger, higher-risk change than fits a surgical PR, so this
PR does not implement it.

Instead, this PR closes the silent-failure/false-Accepted: True gap: buildRateLimitRule
now detects a second sourceCIDR selector within the same rule and returns a translation
error, the same way this function already errors on other invalid configs (e.g. an invalid
regex or a header missing its value). The policy is then reported as not Accepted with a
clear reason instead of silently misapplying (or un-applying) an IP match. A single
sourceCIDR selector per rule continues to work exactly as before.

Note this does not make multiple exempted/matched CIDRs combine as the CRD description
suggests — that still isn't supported. It only prevents the dangerous silent-drop behavior
by surfacing a clear error at translation time.

Both buildGlobalRateLimit and buildLocalRateLimit call through this same function, so
the validation applies consistently to both Global and Local rate limiting.

Which issue(s) this PR fixes:

Fixes #

Report: #10025

Validation:

  • go build ./internal/gatewayapi/... passes.
  • go test ./internal/gatewayapi/... passes (all subpackages).
  • Added TestBuildRateLimitRuleSourceCIDR in
    internal/gatewayapi/backendtrafficpolicy_test.go with two cases: a single sourceCIDR
    selector (still succeeds), and two sourceCIDR selectors across two clientSelectors
    entries with Distinct+invert (now returns an error containing "only one sourceCIDR
    selector is supported per rule"). Verified this second case returns err == nil (i.e.
    silently succeeds, reproducing the reported bug) against the pre-fix code, and correctly
    errors after the fix — a failing-then-passing targeted test proving the defect and the fix.
  • gofmt -l reports no issues on the changed files.
  • golangci-lint run ./internal/gatewayapi/... produces the same pre-existing
    SA1019/ST1005/errcheck findings with and without this diff — no new lint findings.
  • Checked existing golden testdata under internal/gatewayapi/testdata/ that use
    sourceCIDR: each occurrence is either the only sourceCIDR in its rule, or belongs to a
    separate rules: entry, so none hit the new multi-selector error path — consistent with
    the full package test suite passing unchanged.
  • gh run list --repo envoyproxy/gateway --branch main --event push --limit 10: the latest
    push to main shows Build and Test green; OSV-Scanner is currently failing on main
    across multiple unrelated recent commits (dependency vulnerability scan noise), unrelated
    to this change.

PR Checklist

  • Authorship & ownership: Coding agents / AI assistants are welcome, but I have reviewed every change, understand how and why it works, can explain and maintain it, and take full responsibility for this PR. I have not submitted generated output I do not understand.
  • DCO: All commits are signed off (git commit -s). See DCO: Sign your work.
  • API agreed first: N/A: this PR does not contain API changes.
  • Required checks pass: make generate gen-check, make lint, and the unit-test/coverage build pass. (Flaky e2e failures are not considered breakages, but gen-check, lint, and coverage MUST pass.)
  • Tests added/updated: New/changed code is covered by appropriate tests.
  • Docs: N/A: this changes internal translation error handling, not user-facing docs; the sourceCIDR field's documented single-value semantics are unchanged.
  • Release notes: Added release-notes/current/bug_fixes/10025-ratelimit-multiple-sourcecidr.md.
  • Generated files committed: N/A: no API/helm chart/module changes.
  • Scope & compatibility: The PR is reasonably scoped (no unrelated changes) and preserves backward compatibility for single-sourceCIDR rules; a rule using more than one sourceCIDR selector, which never worked correctly, now fails to translate with a clear error instead of silently misbehaving. This is a narrowing of previously-silent-broken behavior, not a breaking change to any working configuration.
  • Codex review: Requested a Codex review and addressed all of its comments.
  • Copilot review: Requested a Copilot review and addressed all of its comments.

Note: CI on this repo may show OSV-Scanner red — that check is currently failing on main
independent of this change (see Validation above).


AI assistance: this change was drafted with Claude Code.

Fixes #10025

@pujitha24
pujitha24 requested a review from a team as a code owner September 16, 2026 23:20
@netlify

netlify Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for cerulean-figolla-1f9435 ready!

Name Link
🔨 Latest commit 2ef993d
🔍 Latest deploy log https://app.netlify.com/projects/cerulean-figolla-1f9435/deploys/6abbc275e41d9b0007e74b5b
😎 Deploy Preview https://deploy-preview-10046--cerulean-figolla-1f9435.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dbbf1f38d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1016 to +1020
got, err := buildRateLimitRule(&tc.rule)
if tc.expectError {
require.Error(t, err)
require.Contains(t, err.Error(), tc.errorMsg)
require.Nil(t, got)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exercise the rejection through the policy translator

This test invokes buildRateLimitRule directly, so it does not verify the user-visible part of the fix: a real BackendTrafficPolicy with duplicate sourceCIDR selectors must report Accepted: False with the translation error rather than retain the previous successful status. Add an internal/gatewayapi/testdata input/output fixture for this configuration so the translator and status propagation are covered; otherwise this test would still pass if a caller later swallowed the error.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.42%. Comparing base (f1f4888) to head (2ef993d).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #10046   +/-   ##
=======================================
  Coverage   81.41%   81.42%           
=======================================
  Files         266      266           
  Lines       41300    41304    +4     
=======================================
+ Hits        33626    33633    +7     
+ Misses       7673     7670    -3     
  Partials        1        1           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

pujitha24 added a commit to pujitha24/gateway that referenced this pull request Sep 18, 2026
…R rejection

Covers the full BackendTrafficPolicy translation path (not just
buildRateLimitRule directly) so the Accepted:False status is verified
end-to-end, per Codex review feedback on envoyproxy#10046.

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
@pujitha24

Copy link
Copy Markdown
Contributor Author

The unit test only checked buildRateLimitRule in isolation. Added a translator-level fixture (backendtrafficpolicy-with-local-ratelimit-invalid-multiple-sourcecidr.{in,out}.yaml) with a real BackendTrafficPolicy carrying two sourceCIDR selectors, and confirmed the golden output shows Accepted: False with the translation error, not the previous silent Accepted: True.

@zirain

zirain commented Sep 28, 2026

Copy link
Copy Markdown
Member

can we also reject this with CEL?

pujitha24 added a commit to pujitha24/gateway that referenced this pull request Sep 28, 2026
…R rejection

Covers the full BackendTrafficPolicy translation path (not just
buildRateLimitRule directly) so the Accepted:False status is verified
end-to-end, per Codex review feedback on envoyproxy#10046.

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
pujitha24 and others added 3 commits September 29, 2026 06:51
… selector

Motivation:
A global or local rate limit rule with more than one clientSelectors entry,
where more than one of those entries carries a sourceCIDR, silently applies
only the last sourceCIDR: buildRateLimitRule assigns the parsed CIDR to
ir.RateLimitRule.CIDRMatch, a single pointer field, so a second sourceCIDR
selector in the same rule overwrites the first instead of being ANDed with
it. The earlier selector is dropped with no error, and the policy still
reports Accepted: True. This is especially dangerous when sourceCIDR plus
invert: true is used as an IP-exemption mechanism, since adding a second
exempted CIDR silently un-exempts the first one.

Approach:
Properly combining multiple sourceCIDR selectors would require reworking
ir.RateLimitRule.CIDRMatch into a slice, regenerating deepcopy code, and
reworking the nested Envoy rate-limit-descriptor chains in
internal/xds/translator/ratelimit.go and local_ratelimit.go, which key
descriptors with fixed constant strings that would need per-index
uniqueness. That is a larger, higher-risk change than fits a surgical fix,
so this change does not implement it. Instead, buildRateLimitRule now
detects a second sourceCIDR selector within the same rule and returns a
translation error, following the same convention this function already
uses for other invalid configs. The policy is then reported as not
Accepted with a clear reason instead of silently misapplying an IP match.
A single sourceCIDR selector per rule continues to work exactly as before.
This does not make multiple CIDRs combine as the CRD description suggests;
it only prevents the silent-drop failure mode.

Validation:
- go build ./internal/gatewayapi/... passes.
- go test ./internal/gatewayapi/... passes (all subpackages).
- Added TestBuildRateLimitRuleSourceCIDR with a single-selector case (still
  succeeds) and a two-selector case using Distinct+invert matching the
  report's repro (now errors with "only one sourceCIDR selector is
  supported per rule"). Confirmed the two-selector case returns err == nil
  against the pre-fix code, reproducing the reported bug, and errors after
  the fix.
- gofmt -l reports no issues on the changed files.
- golangci-lint run ./internal/gatewayapi/... produces the same
  pre-existing findings with and without this change; no new findings.
- Existing golden testdata under internal/gatewayapi/testdata/ using
  sourceCIDR each have only one sourceCIDR per rule, so none hit the new
  error path, consistent with the full package test suite passing
  unchanged.
- gh run list shows Build and Test green on the latest push to main;
  OSV-Scanner is currently failing on main across unrelated recent
  commits (dependency scan noise), unrelated to this change.

Report: envoyproxy#10025
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Assisted-by: claude-sonnet-5 (via Claude Code)
…R rejection

Covers the full BackendTrafficPolicy translation path (not just
buildRateLimitRule directly) so the Accepted:False status is verified
end-to-end, per Codex review feedback on envoyproxy#10046.

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Add a CEL rule on RateLimitRule.clientSelectors so a rule with more than
one sourceCIDR selector is rejected at admission, in addition to the
existing translator check. Regenerate CRDs and helm goldens and add CEL
validation test cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rate limit: Only the last sourceCIDR in clientSelectors takes effect

2 participants