Skip to content

fix(translator): reject backendRef URL rewrites that can't be applied per backend - #10471

Open
lyelgate wants to merge 2 commits into
envoyproxy:mainfrom
lyelgate:fix/backendref-urlrewrite-validation
Open

lyelgate wants to merge 2 commits into
envoyproxy:mainfrom
lyelgate:fix/backendref-urlrewrite-validation

Conversation

@lyelgate

@lyelgate lyelgate commented Nov 1, 2026

Copy link
Copy Markdown

What this PR does / why we need it:

A URL rewrite in the filters of an HTTPRoute backendRef is translated onto that backend's WeightedCluster.ClusterWeight entry (applyWeightedClusterFilters in internal/xds/translator/route.go). Envoy only supports host_rewrite_literal there, so a literal hostname is the only rewrite that can be applied per backend.

The other rewrites were accepted and then dropped. buildDestinationFilters copies URLRewrite into DestinationFilters, the route reports ResolvedRefs: True, and the xDS translator ignores everything except Host.Name. Users see a route that looks healthy but doesn't rewrite anything, which is what #7099 reports for type: Header.

Rewrite on a backendRef Before After
Gateway API URLRewrite with a literal hostname applied applied (unchanged)
Gateway API URLRewrite with a path rejected by validateBackendRefFilters unchanged
HTTPRouteFilter hostname type: Header silently ignored rejected
HTTPRouteFilter hostname type: Backend silently ignored rejected
HTTPRouteFilter hostname type: PathRegex silently ignored rejected
HTTPRouteFilter path type: ReplaceRegexMatch silently ignored rejected

validateBackendRefFilters already rejects a Gateway API path rewrite on a backendRef, but it only looks at the filter in the route spec, so it can't see what an ExtensionRef to an HTTPRouteFilter resolves to. This PR adds validateBackendRefURLRewrite, called from processDestinationFilters once extension filters are resolved, next to the existing grpcJSONTranscoder check. An unsupported rewrite surfaces the same way: ResolvedRefs: False with reason InvalidBackendFilters, and a message that names it:

Failed to process route rule 0 backendRef 0: urlRewrite hostname of type Header is not supported in the filters of a backendRef.

This follows @zirain's suggestion on the issue to report the limitation through status. Routes don't have a non-fatal Warning condition yet (#9630 is discussing one), so this reuses the reason the translator already uses for unsupported backendRef filters instead of adding a new one.

Behavior change worth a look: a backendRef that carries one of the four rejected rewrites currently receives traffic with the rewrite ignored. With this change it is treated like any other InvalidBackendFilters backendRef, so its share of traffic gets a 500. I think a visible failure is better than a configuration that reports success and does something else, and it matches how a Gateway API path rewrite on a backendRef is already handled. If you would rather keep those backends serving and only report the problem, I'm happy to switch to a non-fatal condition once #9630 settles the shape.

Out of scope: appendXForwardedHost isn't honored per backend either (ClusterWeight has no equivalent field). Rejecting it would break the supported literal-hostname case, since it defaults to true, so it is left as is.

Changes:

  • internal/gatewayapi/route.go: add validateBackendRefURLRewrite and call it from processDestinationFilters.
  • internal/gatewayapi/testdata/httproutefilter-with-urlrewrite-on-backendref.{in,out}.yaml: one HTTPRoute per rejected rewrite (Header, Backend and PathRegex hostname, ReplaceRegexMatch path), plus a literal-hostname route as a control that still resolves and keeps urlRewrite.host.name in the IR.
  • site/content/en/latest/tasks/traffic/http-urlrewrite.md: note which rewrites are supported on a backendRef.
  • release-notes/current/bug_fixes/7099-backendref-urlrewrite-validation.md.

Testing:

  • The new test case fails on main without the change and passes with it.
  • go test ./internal/... passes.
  • go test ./internal/gatewayapi ./internal/xds/translator --override-testdata=true leaves no diff in existing golden files.
  • golangci-lint (repo config, CI build tags) on ./internal/gatewayapi/..., codespell, yamllint and tools/boilerplate/verify-boilerplate.sh are clean.
  • validateBackendRefURLRewrite has 100% coverage.

Which issue(s) this PR fixes:

Fixes #7099


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: no changes under /api.
  • 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.) Locally I ran the parts this change touches (listed under Testing); the full targets will run in CI.
  • Tests added/updated: New/changed code is covered by appropriate tests. N/A if this PR does not contain code changes.
  • Docs: User-facing changes update the docs, either in this PR or a follow-up PR. N/A if this PR does not contain user-facing changes.
  • Release notes: For any non-trivial change, added a release-note fragment under release-notes/current/<section>/<pr-number>-<slug>.md (see release-notes/current/README.md for sections and naming). N/A if this PR does not contain non-trivial changes.
  • Generated files committed: N/A: no API, Helm chart or module changes.
  • Scope & compatibility: The PR is reasonably scoped (no unrelated changes) and preserves backward compatibility, or any breaking change is called out above and documented in release-notes/current/breaking_changes/.
  • Codex review: Requested a Codex review and addressed all of its comments.
  • Copilot review: Requested a Copilot review and addressed all of its comments.

… per backend

A backendRef's filters are translated onto its weighted cluster entry,
where Envoy only supports a literal host rewrite. A path rewrite, or a
hostname rewrite of type Header, Backend or PathRegex set through an
HTTPRouteFilter, was accepted, reported ResolvedRefs=True, and then
silently dropped during xDS translation.

Reject these in processDestinationFilters, where extension filters are
already resolved, with the InvalidBackendFilters reason used for other
unsupported backendRef filters, so the route status says why.

Fixes envoyproxy#7099

Signed-off-by: Lakhan Yelgate <39281685+lyelgate@users.noreply.github.com>
@lyelgate
lyelgate requested a review from a team as a code owner November 1, 2026 07:52
@netlify

netlify Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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

Name Link
🔨 Latest commit 2c65d54
🔍 Latest deploy log https://app.netlify.com/projects/cerulean-figolla-1f9435/deploys/6ae6fc08ba76c3a04c573c0b
😎 Deploy Preview https://deploy-preview-10471--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 commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T08:51:53.860920Z 2c65d54 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 8c3c7af161

ℹ️ 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".

{{% alert title="Note" color="primary" %}}
A URL rewrite in the `filters` of a `backendRef` only supports rewriting the hostname to a fixed value. Path
rewrites, and hostname rewrites of type `Header`, `Backend` or `PathRegex`, are only supported in the filters of a
route rule; when used on a `backendRef`, that backendRef is rejected with an `InvalidBackendFilters` condition.

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 Document the actual condition and reason for path rewrites

For a standard Gateway API URLRewrite.path, validateBackendRefFilters returns RouteReasonUnsupportedRefValue (internal/gatewayapi/validate.go:114-120), so the status is ResolvedRefs=False with reason UnsupportedRefValue; InvalidBackendFilters is only the reason used by the new post-resolution HTTPRouteFilter check (as shown in the new golden output at lines 76-80), and in neither case is it the condition type. This blanket statement can therefore make operators look for a condition/reason that is never emitted for the standard path form; describe ResolvedRefs=False and distinguish the two reasons, or narrow the statement to extension rewrites.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, thanks. InvalidBackendFilters is the reason, not the condition, and the Gateway API path rewrite goes through the earlier validateBackendRefFilters check with UnsupportedRefValue. Updated the note and the release note to describe ResolvedRefs: False with both reasons in 2c65d54.

…ckendRef rewrites

InvalidBackendFilters is the reason, not the condition, and only applies
to rewrites set through an HTTPRouteFilter. A Gateway API URLRewrite path
rewrite on a backendRef is rejected earlier with reason UnsupportedRefValue.

Signed-off-by: Lakhan Yelgate <39281685+lyelgate@users.noreply.github.com>

@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: 2c65d54d90

ℹ️ 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".

if httpFiltersContext.GRPCJSONTranscoder != nil {
return nil, nil, errors.New("grpcJSONTranscoder is not supported in the filters of a backendRef")
}
if err := validateBackendRefURLRewrite(httpFiltersContext.URLRewrite); err != nil {

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 Validate rewrites before backend early returns

When a backendRef has weight 0, or its Service currently has no ready endpoints, processDestination returns before processDestinationFilters is called, so this new check never runs. The same Header/Backend/PathRegex/regex-path configuration then reports ResolvedRefs=True (only BackendsAvailable=False in the no-endpoint case), and once endpoints appear it unexpectedly flips to InvalidBackendFilters and begins routing that backend's share to 500 responses. Resolve and validate these extension rewrites before the weight and endpoint early exits so configuration validity does not depend on runtime backend health.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed that the check only runs once a backendRef has weight and resolvable endpoints. That ordering isn't specific to this check: every backendRef filter error, including the existing grpcJSONTranscoder check and unresolvable HTTPRouteFilter references, is only reported after the weight and endpoint early returns in processDestination. Moving filter validation ahead of those would change status for all of them, so I'd rather keep this PR scoped and handle the ordering in a follow-up if maintainers want it.

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.

HTTPRouteFilter's urlRewrite does not take effect under backendRefs

1 participant