Skip to content

xds: lowercase RDS route header matcher names before matching - #9353

Merged
easwars merged 2 commits into
grpc:masterfrom
winklemad:xds-rds-lowercase-header-matcher-name
Sep 1, 2026
Merged

easwars merged 2 commits into
grpc:masterfrom
winklemad:xds-rds-lowercase-header-matcher-name

Conversation

@winklemad

Copy link
Copy Markdown
Contributor

Fixes #9352

xDS RDS route header matchers store the header name verbatim from the config in routesProtoToSlice:

header.Name = h.GetName()

The matcher engine looks that name up against metadata.MD, whose keys are always lowercase, so a matcher configured with a mixed-case name such as X-Role matches no header and the route never fires.

This is the last consumer of that matcher engine that does not normalize the name. The RBAC HTTP filter started lowercasing it at parse time in #9332 (normalizeHeaderMatcher), and authz/rbac_translator.go does the same on the non-xDS path. Envoy stores HeaderMatcher.name in a Http::LowerCaseString, so this is valid config that works on other proxies.

The fix lowercases the name where the route header matcher is built, matching the sibling paths. Added a table test covering a mixed-case name against lowercased metadata, and asserting the parsed matcher stores the lowercased name; it fails on master and passes with the change.

RELEASE NOTES:

  • xds: fix RDS route header matchers with non-lowercase names never matching

The metadata that the header matchers run against always has lowercase
keys, so a route header matcher configured with a name such as X-Role
matched no header and the route never fired. Lowercase the name when the
route configuration is parsed, which is what Envoy does (it holds the
name in a LowerCaseString), what the RBAC HTTP filter does since grpc#9332,
and what authz/rbac_translator.go does on the non-xDS path.
@codecov

codecov Bot commented Aug 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.59%. Comparing base (4793ad0) to head (3b7b598).
⚠️ Report is 9 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9353      +/-   ##
==========================================
+ Coverage   87.42%   87.59%   +0.16%     
==========================================
  Files         424      429       +5     
  Lines       30283    30622     +339     
==========================================
+ Hits        26476    26824     +348     
+ Misses       3806     3798       -8     
+ Partials        1        0       -1     
Files with missing lines Coverage Δ
...nternal/xds/xdsclient/xdsresource/unmarshal_rds.go 93.27% <100.00%> (ø)

... and 44 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eshitachandwani eshitachandwani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM modulo minor nits. Adding @easwars as a second reviewer

// The metadata the matchers run against always has lowercase keys,
// so a name that contains an uppercase character matches no header
// and the route holding it never fires. Lowercase the name, as
// Envoy and the RBAC filter do.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: we can remove the last sentence.

// header matcher name is not lowercase still matches an RPC carrying that
// header. Metadata keys are always lowercase, so the name has to be
// normalized when the route configuration is parsed.
func (s) TestRoutesProtoToSliceHeaderMatcherNameCase(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I think this test is redundant and we can remove this since we have already added a test case in the test above

Trim the trailing sentence of the header-name normalization comment, and remove
the standalone TestRoutesProtoToSliceHeaderMatcherNameCase test (the non-lowercase
header-name case is already covered by a table entry in TestRoutesProtoToSlice),
dropping the now-unused metadata import.
@winklemad

Copy link
Copy Markdown
Contributor Author

Thanks @eshitachandwani — addressed both nits: trimmed the last sentence of the comment, and removed the standalone TestRoutesProtoToSliceHeaderMatcherNameCase since the non-lowercase case is already covered by the "header specifier with a name that is not lowercase" entry in TestRoutesProtoToSlice (and dropped the now-unused metadata import). go vet / gofmt clean and the package tests pass.

@easwars

easwars commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the RDS unmarshaling logic to convert header matcher names to lowercase, ensuring they match correctly against metadata keys which are always lowercase. A corresponding unit test has been added to verify this behavior. There are no review comments, so I have no feedback to provide.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

xds: RDS route header matchers with non-lowercase names never match

3 participants