Skip to content

feat(policy): support ListenerSet as targetRef for ClientTrafficPolicy - #9026

Merged
jukie merged 25 commits into
envoyproxy:mainfrom
apkatsikas:feat/clienttrafficpolicy-listenerset-targetref
Jun 18, 2026
Merged

jukie merged 25 commits into
envoyproxy:mainfrom
apkatsikas:feat/clienttrafficpolicy-listenerset-targetref

Conversation

@apkatsikas

@apkatsikas apkatsikas commented May 18, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

feature

What this PR does / why we need it

Adds ListenerSet as a valid targetRef kind for ClientTrafficPolicy, alongside the existing Gateway.

Priority follows the existing more-specific-wins model: sectionName policies > ListenerSet-wide > Gateway-wide.

Changes:

  • CEL validation: kind == 'Gateway' → kind in ['Gateway', 'ListenerSet']
  • Translation: ListenerSet and Gateway targets share loop bodies; ListenerSet-wide is processed
    before Gateway-wide to enforce priority regardless of input order
  • claimedScopes tracks whole-scope claims (Gateway-wide, ListenerSet-wide); claimedSections tracks individual listeners claimed by sectionName or ListenerSet-wide policies, keyed by a scope-qualified key (lsPrefix + listenerName for ListenerSet listeners, bare name for Gateway listeners) so that same-named listeners across different ListenerSets — or a Gateway listener sharing a name with a ListenerSet listener — do not produce false Conflicted/Overridden status
  • Three new test fixtures cover the duplicate-name collision scenarios (wide and sectionName variants)
  • CRDs regenerated (make manifests, make helm-template.gateway-crds-helm)

Known gap / follow-up: ListenerSet is missing from the upstream sectionName field description in gwapiv1.LocalPolicyTargetReferenceWithSectionName, PR opened here. CEL rules in this PR enforce valid kinds at admission time regardless.

Local verification: KIND cluster with dev EG image, CTP with tls.minVersion: "1.3" targeting a ListenerSet HTTPS listener:

# TLS 1.3 — succeeds
curl.exe -k --tlsv1.3 https://api.localhost
# Hello from Kubernetes

# TLS 1.2 — rejected by Envoy, proving the policy is enforced
curl.exe -k --tls-max 1.2 https://api.localhost
# curl: (35) schannel: ... SEC_E_UNSUPPORTED_FUNCTION

Gateway's own HTTP listener was unaffected — only the ListenerSet's HTTPS listener had the TLS restriction applied.

Which issue(s) this PR fixes

#8092

Release Notes

Yes

Reviewing the diff

clienttrafficpolicy.go - to review only meaningful changes, append ?w=1 to the GitHub PR URL.

@apkatsikas
apkatsikas requested a review from a team as a code owner May 18, 2026 20:01
@netlify

netlify Bot commented May 18, 2026 •

Copy link
Copy Markdown

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

Name Link
🔨 Latest commit 0f45ef1
🔍 Latest deploy log https://app.netlify.com/projects/cerulean-figolla-1f9435/deploys/6a32fefd17885500087dcf7b
😎 Deploy Preview https://deploy-preview-9026--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.

@codecov

codecov Bot commented May 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.04329% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.15%. Comparing base (8ff4a2f) to head (0f45ef1).

Files with missing lines Patch % Lines
internal/gatewayapi/clienttrafficpolicy.go 88.83% 15 Missing and 7 partials ⚠️
internal/gatewayapi/helpers.go 97.05% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9026      +/-   ##
==========================================
+ Coverage   75.11%   75.15%   +0.04%     
==========================================
  Files         252      252              
  Lines       40882    41008     +126     
==========================================
+ Hits        30707    30820     +113     
- Misses       8084     8092       +8     
- Partials     2091     2096       +5     

☔ 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.

@apkatsikas

Copy link
Copy Markdown
Contributor Author

/retest

1 similar comment
@jukie

jukie commented May 19, 2026

Copy link
Copy Markdown
Contributor

/retest

@jukie
jukie self-requested a review May 21, 2026 23:56
@jukie

jukie commented May 21, 2026

Copy link
Copy Markdown
Contributor

@codex review

@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: 1d48a9bfb7

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@apkatsikas
apkatsikas requested a review from jukie May 22, 2026 15:09
@apkatsikas

Copy link
Copy Markdown
Contributor Author

@jukie looks like the GHA for the OSV scanner is erroring on Go version

2026-05-22T15:28:58.9244733Z Failed to run code analysis (govulncheck) on '/github/workspace/examples/envoy-ext-auth/go.mod' because govulncheck: loading packages: err: exit status 1: stderr: go: go.mod requires go >= 1.26.3 (running go 1.26.2; GOTOOLCHAIN=local)

https://productionresultssa11.blob.core.windows.net/actions-results/f3abf5f9-fcf8-4c57-8235-ddd3092140cf/workflow-job-run-71a261c9-0586-5b73-b5b7-bef9d19e6dca/logs/job/job-logs.txt?rsct=text%2Fplain&se=2026-05-22T15%3A39%3A59Z&sig=%2BYM6108mvV9s0rK303laQASXeGgaXS1Qzu%2BHSi9JxD8%3D&ske=2026-05-22T18%3A56%3A30Z&skoid=ca7593d4-ee42-46cd-af88-8b886a2f84eb&sks=b&skt=2026-05-22T14%3A56%3A30Z&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skv=2025-11-05&sp=r&spr=https&sr=b&st=2026-05-22T15%3A29%3A54Z&sv=2025-11-05

@apkatsikas

Copy link
Copy Markdown
Contributor Author

/retest

@jukie

jukie commented May 27, 2026

Copy link
Copy Markdown
Contributor

Can you also read through GEP-1713 and make sure conflicts/override behavior is being handled correctly? For example we may need some additional handling in listener.go or validate.go to filter out l.isFromListenerSet().

An example that may be useful to walk through is two CTPs targeting the same resulting listerner(s). CTP A targeting a ListernerSet, and CTP B targeting a Gateway. For each scenario also consider sectionName handling.

@apkatsikas

apkatsikas commented May 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Can you also read through GEP-1713 and make sure conflicts/override behavior is being handled correctly? For example we may need some additional handling in listener.go or validate.go to filter out l.isFromListenerSet().

An example that may be useful to walk through is two CTPs targeting the same resulting listerner(s). CTP A targeting a ListernerSet, and CTP B targeting a Gateway. For each scenario also consider sectionName handling.

I reviewed the conflict/override behavior for CTPs targeting ListenerSets vs Gateways. The existing tests already cover:

  • clienttrafficpolicy-listenerset-wide-beats-gateway-wide and clienttrafficpolicy-listenerset-wide-beats-gateway-wide-gw-listed-first — LS-wide CTP beats gateway-wide regardless of input order, gateway-wide gets Overridden for all LS listeners
  • clienttrafficpolicy-gateway-listenerset-same-listener-name-sectionname — LS sectionName CTP and gateway sectionName CTP with the same listener name do not conflict with each other

Added two missing sectionName variants in commit 5f69223:

  • clienttrafficpolicy-listenerset-sectionname-beats-gateway-wide — LS sectionName CTP claims one listener, gateway-wide gets Overridden for only that listener but still applies to the remaining gateway and LS listeners (verified via xdsIR keepalive config per listener)
  • clienttrafficpolicy-listenerset-wide-no-conflict-with-gateway-sectionname — LS-wide CTP and gateway sectionName CTP target non-overlapping listeners, both accepted with neither marked Overridden

Is there a specific scenario you had in mind that isn't covered by the above?

@apkatsikas

Copy link
Copy Markdown
Contributor Author

/retest

@apkatsikas
apkatsikas requested a review from jukie May 27, 2026 21:42
@apkatsikas

Copy link
Copy Markdown
Contributor Author

/retest

@jukie

jukie commented May 28, 2026

Copy link
Copy Markdown
Contributor

Is there a specific scenario you had in mind that isn't covered by the above?

Thanks for confirming the logic, what you have above looks right to me.

I'll give this another deep look but overall looks good once the last review comments are addressed.

@jukie
jukie requested a review from a team May 28, 2026 00:59
@arkodg
arkodg requested a review from zhaohuabing May 28, 2026 14:58
@arkodg arkodg added this to the v1.9.0-rc.1 Release milestone May 28, 2026
@arkodg
arkodg requested a review from zirain May 28, 2026 14:59
@apkatsikas
apkatsikas force-pushed the feat/clienttrafficpolicy-listenerset-targetref branch from 53c7d4d to de1f690 Compare May 29, 2026 01:24
@apkatsikas

Copy link
Copy Markdown
Contributor Author

/retest

@apkatsikas
apkatsikas requested a review from jukie June 6, 2026 12:30
@apkatsikas
apkatsikas force-pushed the feat/clienttrafficpolicy-listenerset-targetref branch from de1f690 to eab7bb2 Compare June 6, 2026 12:37
@zhaohuabing

Copy link
Copy Markdown
Member

@codex review

@zhaohuabing
zhaohuabing requested a review from Copilot June 14, 2026 03:14

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

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +399 to +403
if policyTargetsLS {
if claimedSections[gatewayKey] == nil {
claimedSections[gatewayKey] = sets.New[string]()
}
claimedSections[gatewayKey].Insert(fmt.Sprintf("%s%s", lsPrefix(ls), string(l.Name)))

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 Reserve ListenerSet sections before port validation

When a ListenerSet-wide policy fails validatePortOverlapForClientTrafficPolicy for a listener (for example, a non-TLS ListenerSet listener sharing a port with a Gateway listener) and a Gateway-wide ClientTrafficPolicy also targets the parent, this insertion never runs because it is inside the success branch. The later Gateway-wide pass then sees the ListenerSet listener as unclaimed and translates the broader policy onto it instead of reporting it as overridden, breaking the intended ListenerSet-wide > Gateway-wide precedence. Claim the ListenerSet listener before port validation, as sectionName policies already do.

Useful? React with 👍 / 👎.

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.

The more specific policy should only override when it is accepted/effective; if it fails validation, allowing the Gateway-wide policy to apply as fallback seems reasonable.

Copilot AI 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.

Pull request overview

Copilot reviewed 40 out of 42 changed files in this pull request and generated no new comments.

zhaohuabing

This comment was marked as resolved.

zhaohuabing
zhaohuabing previously approved these changes Jun 17, 2026

@zhaohuabing zhaohuabing 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 thanks!

@apkatsikas

apkatsikas commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor Author

This PR adds 30 new golden YAML files for the Gateway API tests. Are all of them necessary?

I believe they are all useful. The majority of them were added 1 or 2 at a time as changes were requested and PR feedback came in. Each time the code was changed to address a bug or invariant that was not being enforced, I added a test to ensure the fix worked as intended.

I think the new practice of adding the comment at the top of the test file to describe the purpose of the test, in the future, can help us decide at the end of a review if some tests can be removed at the end.

Comment on lines +37 to +38
- lastTransitionTime: null
message: There are existing ClientTrafficPolicies that are overriding these

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.

nits:
Would the following message provide a better user experience?

message: There are existing ClientTrafficPolicies that are overriding these sections [envoy-gateway/ext-listeners/ext-http-1 envoy-gateway/ext-listeners/ext-http-2]

kkk777-7
kkk777-7 previously approved these changes Jun 17, 2026
@kkk777-7

Copy link
Copy Markdown
Member

LGTM, thanks!

I left one nits comment(not-blocking). we could do follow-up PR.
It would be great to update docs in follow-up PR.
https://gateway.envoyproxy.io/docs/concepts/gateway_api_extensions/client-traffic-policy/#clienttrafficpolicy-in-envoy-gateway
e.g. Policies attached to a Gateway affect ListenerSets.

@jukie
jukie dismissed stale reviews from kkk777-7 and zhaohuabing via 0f45ef1 June 17, 2026 20:09
Comment on lines +233 to +245
for _, passKind := range []gwapiv1.Kind{resource.KindListenerSet, resource.KindGateway} {
for i, currPolicy := range clientTrafficPolicies {
policyName := utils.NamespacedName(currPolicy)
targetRefs := resolvePolicyTargetsForGatewayAndListenerSet(
currPolicy.Spec.PolicyTargetReferences,
gateways,
resources.ListenerSets,
resources.ReferenceGrants,
egv1a1.GroupName,
egv1a1.KindClientTrafficPolicy,
currPolicy.Namespace,
t.GetNamespace,
)

@jukie jukie Jun 17, 2026 •

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.

Non-blocking: one last perf observation is that we we're processing every CTP twice, once for ListenerSets and a second for Gateways. I think we can split the clientTrafficPolicies loop and resolve policy targets before the passKind loop.

Something like this:

policyTargets := make([][]policyTargetReferenceWithSectionName, len(clientTrafficPolicies))
for i, p := range clientTrafficPolicies {
    policyTargets[i] = resolvePolicyTargetsForGatewayAndListenerSet(
        p.Spec.PolicyTargetReferences, gateways, resources.ListenerSets,
        resources.ReferenceGrants, egv1a1.GroupName, egv1a1.KindClientTrafficPolicy,
        p.Namespace, t.GetNamespace,
    )
}

for _, passKind := range []gwapiv1.Kind{resource.KindListenerSet, resource.KindGateway} {
    for i := range clientTrafficPolicies {
        for _, currTarget := range policyTargets[i] {
            ...
        }
    }
}

@jukie
jukie requested review from kkk777-7 and zhaohuabing June 17, 2026 20:24
@kkk777-7

Copy link
Copy Markdown
Member

/retest

@jukie
jukie merged commit 02cc5bc into envoyproxy:main Jun 18, 2026
85 of 91 checks passed
guydc pushed a commit to guydc/gateway that referenced this pull request Jun 30, 2026
envoyproxy#9026)

* feat(policy): support ListenerSet as targetRef for ClientTrafficPolicy
Allow ClientTrafficPolicy to target a ListenerSet in addition to a
Gateway, enabling per-listener-group traffic policy without applying
settings cluster-wide. Supports both whole-ListenerSet and
sectionName-scoped targeting with correct conflict detection.

Signed-off-by: apkatsikas <[email protected]>

* refactor(policy): merge sectionName CTP processing passes into one loop

Signed-off-by: apkatsikas <[email protected]>

* refactor(policy): collapse no-sectionName CTP processing passes into one loop

Signed-off-by: apkatsikas <[email protected]>

* chore: regenerate helm CRD snapshots for ListenerSet targetRef CEL rules

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): enforce mutual exclusion between ListenerSet-wide and Gateway-wide CTPs

  When a ListenerSet-wide CTP applies to a listener, record the individual
  listener name in policyMap so a later Gateway-wide CTP correctly skips it.
  When a Gateway-wide CTP runs first (AllSections="/"), skip LS listeners in
  the ListenerSet-wide pass. Covers both interaction orderings with new golden
  file tests.

Signed-off-by: apkatsikas <[email protected]>

* refactor(policy): unify targetRef resolution into a single function
  Replace the separate resolveClientTrafficPolicyTargetRefForListenerSet
  with a ctpResolvedTarget struct and a single resolveClientTrafficPolicyTargetRef
  that dispatches on targetRef.Kind internally, reducing the three-return signature
  to the conventional (result, error) pair.

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): enforce ListenerSet-wide > Gateway-wide priority regardless of input order

  Process ListenerSet-wide CTPs before Gateway-wide in the no-sectionName pass
  by iterating twice with a processLS filter, matching the more-specific-wins
  principle already established by sectionName policies. Adds golden file tests
  covering both input orderings.

Signed-off-by: apkatsikas <[email protected]>

* chore: add release notes for ListenerSet targetRef support in CTP

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): remove internal sentinel from CTP Overridden status message
  When a ListenerSet-wide CTP was overridden by a Gateway-wide CTP, the
  Overridden message incorrectly included the internal listenerset/...
  scope sentinel alongside real listener names, e.g.:

    sections [ext-http-1 ext-http-2 listenerset/envoy-gateway/ext-listeners]

  Fix by splitting policyMap into two maps:
  - policyMap: whole-scope sentinels only (Conflicted detection)
  - claimedListeners: listener names only (skip logic + Overridden messages)

  The message now correctly shows only real listener names.

Signed-off-by: apkatsikas <[email protected]>

* refactor(policy): rename listenersDontBelongToLS to positive listenersBelongToLS

Signed-off-by: apkatsikas <[email protected]>

* refactor(policy): hoist passKind check before handledPolicies registration

  Update fixture to reflect new output ordering: LS-wide policies are now
  appended to res in pass 1, GW-wide in pass 2. Conditions and XdsIR unchanged.

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): scope claimedSections keys to prevent false conflicts across ListenerSets

  Listeners with the same name in different ListenerSets (or a Gateway listener
  sharing a name with a ListenerSet listener) would incorrectly produce spurious
  Conflicted/Overridden status and skip translation. Scope claim keys by LS
  identity to isolate claims per target. Add test fixtures covering wide and
  sectionName variants of the collision scenario.

Signed-off-by: apkatsikas <[email protected]>

* chore: fix copyloopvar lint error in claimedSections loop

Signed-off-by: apkatsikas <[email protected]>

* refactor: precompute listenerSet map to avoid O(n) scan per CTP

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): validate port overlap per-target scope for LS-wide CTPs
  Introduces ctpAttachScope enum (scopeSpecificSection, scopeEntireGateway,
  scopeEntireListenerSet) to replace the attachedToGateway bool in
  validatePortOverlapForClientTrafficPolicy. LS-wide CTPs now correctly
  allow same-port HTTP listeners within the same ListenerSet, matching
  the existing gateway-wide behavior.

Signed-off-by: apkatsikas <[email protected]>

* test: add CTP LS sectionName conflict/override coverage

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): use ListenerSet as ancestorRef when CTP targets a ListenerSet
  Per GEP-713, the ancestorRef should be the ancestor where status meaningfully differs. When a CTP directly targets a
  ListenerSet, that is the ListenerSet itself, not the parent Gateway. Adds a mixed-type test confirming two distinct
  ancestor entries when a single CTP targets both a Gateway and a ListenerSet.

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): support ListenerSet targetSelectors in ClientTrafficPolicy

  Add resolvePolicyTargetsForGatewayAndListenerSet helper that runs selector
  resolution against both Gateways and ListenerSets, fixing the gap where
  targetSelectors with kind: ListenerSet silently matched nothing. Add two
  test fixtures covering the LS-only and mixed Gateway+ListenerSet selector cases.

Signed-off-by: apkatsikas <[email protected]>

* test(cel): update CTP CEL validation tests for ListenerSet kind

  Fix stale error message strings that still referenced Gateway-only,
  and add a positive test case for kind: ListenerSet.

Signed-off-by: apkatsikas <[email protected]>

* fix: resolve appendAssign lint error in resolvePolicyTargetsForGatewayAndListenerSet

Signed-off-by: apkatsikas <[email protected]>

* fix(policy): add trailing slash to ListenerSet prefix to prevent false prefix matches in port overlap validation

  A ListenerSet named ext would falsely match listeners from ext-b in
  strings.Index comparisons. Adding a trailing slash to the extracted prefix
  ensures exact boundary matching. Adds a test fixture covering this scenario.

Signed-off-by: apkatsikas <[email protected]>

* test(e2e): add e2e test for ClientTrafficPolicy targeting ListenerSet

Signed-off-by: apkatsikas <[email protected]>

* fix(e2e): wait for route parents before dialing in ListenerSetClientTrafficPolicy test

Signed-off-by: apkatsikas <[email protected]>

* fix(e2e): poll until listener accepts TLS before version checks in ListenerSetClientTrafficPolicy test

Signed-off-by: apkatsikas <[email protected]>

---------

Signed-off-by: apkatsikas <[email protected]>
Signed-off-by: Isaac Wilson <[email protected]>
Co-authored-by: Isaac Wilson <[email protected]>
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.

7 participants