From 0ddc456c2a83b89e6af1607c2ffe87a5a3d7b69f Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Tue, 20 Oct 2026 18:36:25 +0900 Subject: [PATCH 1/5] fix(gatewayapi): keep the parentRef port in route policy ancestors Signed-off-by: kkk777-7 --- internal/gatewayapi/backendtrafficpolicy.go | 4 +- internal/gatewayapi/contexts.go | 5 +- internal/gatewayapi/envoyextensionpolicy.go | 4 +- internal/gatewayapi/helpers.go | 14 + internal/gatewayapi/securitypolicy.go | 4 +- internal/gatewayapi/status/policy.go | 8 + internal/gatewayapi/status/policy_test.go | 33 + ...-parentrefs-differing-only-by-port.in.yaml | 178 ++++ ...parentrefs-differing-only-by-port.out.yaml | 885 ++++++++++++++++++ .../10333-route-policy-ancestor-port.md | 1 + 10 files changed, 1126 insertions(+), 10 deletions(-) create mode 100644 internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.in.yaml create mode 100644 internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml create mode 100644 release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 0b8dd799fec..77a7be414d2 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -880,7 +880,7 @@ func (t *Translator) processBackendTrafficPolicyForRoute( } // Do need a section name since the policy is targeting to a route. - ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) if parentRefCtx := targetedRoute.GetRouteParentContext(p); parentRefCtx != nil { parentRefCtxs = append(parentRefCtxs, parentRefCtx) @@ -910,7 +910,7 @@ func (t *Translator) processBackendTrafficPolicyForRoute( // ListenerSet-attached Route policies report status against the // ListenerSet itself. - ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) if parentRefCtx := targetedRoute.GetRouteParentContext(p); parentRefCtx != nil { parentRefCtxs = append(parentRefCtxs, parentRefCtx) diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 10fc271b287..70defed0fa1 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -832,10 +832,7 @@ func (r *RouteParentContext) PolicyAncestorRef() gwapiv1.ParentReference { Namespace: NamespaceDerefOr(r.Namespace, r.routeNamespace()), Name: string(r.Name), } - if r.Kind != nil && *r.Kind == resource.KindListenerSet { - return getAncestorRefForListenerSetPolicy(parentNN, r.SectionName) - } - return getAncestorRefForPolicy(parentNN, r.SectionName) + return getAncestorRefForRouteParent(parentNN, *r.ParentReference) } func (r *RouteParentContext) routeNamespace() string { diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index e04f95b27d0..0b04b4821d5 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -445,7 +445,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute( } // Do need a section name since the policy is targeting to a route - ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) // Only process parentRefs that were handled by this translator @@ -476,7 +476,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute( // ListenerSet-attached Route policies report status against the // ListenerSet itself. - ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) // Only process parentRefs that were handled by this translator diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index 6655fc8b411..33d1b7b42ff 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -764,6 +764,20 @@ func getAncestorRefForListenerSetPolicy(lsNN types.NamespacedName, sectionName * } } +// getAncestorRefForRouteParent returns the Gateway or ListenerSet that a route parentRef refers to +// as an ancestor reference for a policy targeting the route. It keeps the sectionName and port of +// the parentRef, so that parentRefs to different listeners of the same parent get their own ancestors. +func getAncestorRefForRouteParent(parentNN types.NamespacedName, parentRef gwapiv1.ParentReference) gwapiv1.ParentReference { + var ref gwapiv1.ParentReference + if parentRef.Kind != nil && *parentRef.Kind == resource.KindListenerSet { + ref = getAncestorRefForListenerSetPolicy(parentNN, parentRef.SectionName) + } else { + ref = getAncestorRefForPolicy(parentNN, parentRef.SectionName) + } + ref.Port = parentRef.Port + return ref +} + type policyTargetRouteKey struct { Kind string Namespace string diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 202dfcaa4e6..2e4694f953a 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -456,7 +456,7 @@ func (t *Translator) processSecurityPolicyForRoute( } // Do need a section name since the policy is targeting to a route. - ancestorRef := getAncestorRefForPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) // Only process parentRefs that were handled by this translator @@ -488,7 +488,7 @@ func (t *Translator) processSecurityPolicyForRoute( // ListenerSet-attached Route policies report status against the // ListenerSet itself. - ancestorRef := getAncestorRefForListenerSetPolicy(parentNN, p.SectionName) + ancestorRef := getAncestorRefForRouteParent(parentNN, p) ancestorRefs = append(ancestorRefs, &ancestorRef) // Only process parentRefs that were handled by this translator diff --git a/internal/gatewayapi/status/policy.go b/internal/gatewayapi/status/policy.go index 1747ca53171..4995c52ae11 100644 --- a/internal/gatewayapi/status/policy.go +++ b/internal/gatewayapi/status/policy.go @@ -289,6 +289,14 @@ func ancestorRefsEqual(a, b *gwapiv1.ParentReference) bool { return false } + // Compare Port pointers + if (a.Port == nil) != (b.Port == nil) { + return false + } + if a.Port != nil && *a.Port != *b.Port { + return false + } + return true } diff --git a/internal/gatewayapi/status/policy_test.go b/internal/gatewayapi/status/policy_test.go index d8a5eeadc2d..1a833779e1d 100644 --- a/internal/gatewayapi/status/policy_test.go +++ b/internal/gatewayapi/status/policy_test.go @@ -134,3 +134,36 @@ func TestSetWarningForPolicyAncestorMergesWarnings(t *testing.T) { } } } + +// TestSetConditionForPolicyAncestorPort verifies that ancestors differing only by port are kept +// apart, as route parentRefs to the same Gateway differing only by port attach to different listeners. +func TestSetConditionForPolicyAncestorPort(t *testing.T) { + const controller = "example.com/controller" + policyStatus := &gwapiv1.PolicyStatus{} + + refs := []*gwapiv1.ParentReference{ + {Name: "gw"}, + {Name: "gw", Port: new(gwapiv1.PortNumber(80))}, + {Name: "gw", Port: new(gwapiv1.PortNumber(8080))}, + } + for i, ref := range refs { + SetConditionForPolicyAncestor(policyStatus, ref, controller, + gwapiv1.PolicyConditionAccepted, metav1.ConditionTrue, gwapiv1.PolicyReasonAccepted, fmt.Sprintf("msg-%d", i), 1) + } + // Setting a condition on an existing ancestor updates it instead of adding a new one. + SetConditionForPolicyAncestor(policyStatus, &gwapiv1.ParentReference{Name: "gw", Port: new(gwapiv1.PortNumber(80))}, controller, + gwapiv1.PolicyConditionAccepted, metav1.ConditionFalse, gwapiv1.PolicyReasonInvalid, "updated", 1) + + assert.Len(t, policyStatus.Ancestors, len(refs)) + for i, ref := range refs { + cond := getPolicyAncestorCondition(policyStatus, ref, controller, gwapiv1.PolicyConditionAccepted) + if !assert.NotNil(t, cond) { + continue + } + want := fmt.Sprintf("msg-%d", i) + if i == 1 { + want = "updated" + } + assert.Equal(t, want, cond.Message) + } +} diff --git a/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.in.yaml b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.in.yaml new file mode 100644 index 00000000000..c03b268c767 --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.in.yaml @@ -0,0 +1,178 @@ +securityPolicies: +# this policy status +# - Accepted: true +# - Merged: true +# - default/route-merge +# - Overridden: true +# - envoy-gateway/gateway-1/http-8080 +# - default/route +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + cors: + allowOrigins: + - "https://gateway.example.com" +# this policy status +# - Accepted: true +# - Merged: true +# - default/route-merge +# - Overridden: true +# - default/route +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: policy-for-listener + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-8080 + cors: + allowOrigins: + - "https://listener.example.com" +# this policy status +# - Accepted: true on gateway-1 port 80 and on gateway-1 port 8080 +# - Merged: true +# - envoy-gateway/policy-for-gateway on gateway-1/http (through the port 80 parentRef) +# - envoy-gateway/policy-for-listener on gateway-1/http-8080 (through the port 8080 parentRef) +# The parentRefs differ only by port, so each of them gets its own ancestor. +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-route-merge + spec: + mergeType: StrategicMerge + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: route-merge + cors: + allowOrigins: + - "https://route.example.com" +# this policy status +# - Accepted: true on gateway-1 port 80, gateway-1 port 8080, ext-listeners port 8081 and ext-listeners port 8082 +# The parentRefs differ only by port, so each of them gets its own ancestor. +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: route + cors: + allowOrigins: + - "https://route.example.com" +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: route-merge + spec: + hostnames: + - merge.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + port: 80 + - namespace: envoy-gateway + name: gateway-1 + port: 8080 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: route + spec: + hostnames: + - route.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + port: 80 + - namespace: envoy-gateway + name: gateway-1 + port: 8080 + - namespace: envoy-gateway + kind: ListenerSet + group: gateway.networking.k8s.io + name: ext-listeners + port: 8081 + - namespace: envoy-gateway + kind: ListenerSet + group: gateway.networking.k8s.io + name: ext-listeners + port: 8082 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + allowedListeners: + namespaces: + from: All + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: http-8080 + protocol: HTTP + port: 8080 + allowedRoutes: + namespaces: + from: All +listenerSets: +- apiVersion: gateway.networking.k8s.io/v1 + kind: ListenerSet + metadata: + namespace: envoy-gateway + name: ext-listeners + spec: + parentRef: + name: gateway-1 + listeners: + - name: ext-http-1 + protocol: HTTP + port: 8081 + allowedRoutes: + namespaces: + from: All + - name: ext-http-2 + protocol: HTTP + port: 8082 + allowedRoutes: + namespaces: + from: All diff --git a/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml new file mode 100644 index 00000000000..e7983e9489c --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml @@ -0,0 +1,885 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + allowedListeners: + namespaces: + from: All + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: http-8080 + port: 8080 + protocol: HTTP + status: + attachedListenerSets: 1 + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-8080 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: route-merge + namespace: default + spec: + hostnames: + - merge.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + port: 80 + - name: gateway-1 + namespace: envoy-gateway + port: 8080 + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 80 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: route + namespace: default + spec: + hostnames: + - route.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + port: 80 + - name: gateway-1 + namespace: envoy-gateway + port: 8080 + - group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8081 + - group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8082 + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 80 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 8080 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8081 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8082 +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - name: envoy-gateway/gateway-1/http-8080 + ports: + - containerPort: 8080 + name: http-8080 + protocol: HTTP + servicePort: 8080 + - name: envoy-gateway/gateway-1/envoy-gateway/ext-listeners/ext-http-1 + ports: + - containerPort: 8081 + name: http-8081 + protocol: HTTP + servicePort: 8081 + - name: envoy-gateway/gateway-1/envoy-gateway/ext-listeners/ext-http-2 + ports: + - containerPort: 8082 + name: http-8082 + protocol: HTTP + servicePort: 8082 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +listenerSets: +- apiVersion: gateway.networking.k8s.io/v1 + kind: ListenerSet + metadata: + name: ext-listeners + namespace: envoy-gateway + spec: + listeners: + - allowedRoutes: + namespaces: + from: All + name: ext-http-1 + port: 8081 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: ext-http-2 + port: 8082 + protocol: HTTP + parentRef: + name: gateway-1 + status: + conditions: + - lastTransitionTime: null + message: All listeners are accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: All listeners are programmed + reason: Programmed + status: "True" + type: Programmed + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: No conflicts detected + reason: NoConflicts + status: "False" + type: Conflicted + name: ext-http-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: No conflicts detected + reason: NoConflicts + status: "False" + type: Conflicted + name: ext-http-2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: policy-for-route-merge + namespace: default + spec: + cors: + allowOrigins: + - https://route.example.com + mergeType: StrategicMerge + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: route-merge + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-8080 + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-listener + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 80 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 8080 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: policy-for-route + namespace: default + spec: + cors: + allowOrigins: + - https://route.example.com + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: route + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 80 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 8080 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8081 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: ext-listeners + namespace: envoy-gateway + port: 8082 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: policy-for-listener + namespace: envoy-gateway + spec: + cors: + allowOrigins: + - https://listener.example.com + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-8080 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-8080 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being merged by other securityPolicies for these + routes: [default/route-merge]' + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: 'This policy is being overridden by other securityPolicies for these + routes: [default/route]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: policy-for-gateway + namespace: envoy-gateway + spec: + cors: + allowOrigins: + - https://gateway.example.com + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being merged by other securityPolicies for these + routes: [default/route-merge]' + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: 'This policy is being overridden by other securityPolicies for these + gateway listeners: [envoy-gateway/gateway-1/http-8080] and these routes: + [default/route]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: route-merge + namespace: default + name: httproute/default/route-merge/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route-merge/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: merge.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route-merge + namespace: default + name: httproute/default/route-merge/rule/0/match/0/merge_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + - destination: + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0/match/0/route_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + - address: 0.0.0.0 + externalPort: 8080 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-8080 + name: envoy-gateway/gateway-1/http-8080 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: route-merge + namespace: default + name: httproute/default/route-merge/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route-merge/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: merge.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route-merge + namespace: default + name: httproute/default/route-merge/rule/0/match/0/merge_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + - destination: + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0/match/0/route_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + - address: 0.0.0.0 + externalPort: 8081 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: ext-http-1 + name: envoy-gateway/gateway-1/envoy-gateway/ext-listeners/ext-http-1 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8081 + routes: + - destination: + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0/match/0/route_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + - address: 0.0.0.0 + externalPort: 8082 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: ext-http-2 + name: envoy-gateway/gateway-1/envoy-gateway/ext-listeners/ext-http-2 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8082 + routes: + - destination: + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: route + namespace: default + name: httproute/default/route/rule/0/match/0/route_example_com + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowOrigins: + - distinct: false + exact: https://route.example.com + name: "" + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md b/release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md new file mode 100644 index 00000000000..69dd8e6ce2b --- /dev/null +++ b/release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md @@ -0,0 +1 @@ +Fixed route policy status merging the ancestors of route parentRefs to the same Gateway or ListenerSet that differed only by port (with no sectionName). The ancestor built from a route parentRef dropped its port, and ancestor references were compared without it, so both parentRefs reported on a single ancestor. The ancestor now keeps the parentRef's port, so each parentRef gets its own ancestor, as parentRefs differing by sectionName already did. From d7e7807c064d3061a055c9fa8fffcc19d4bb9b49 Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Tue, 20 Oct 2026 18:44:18 +0900 Subject: [PATCH 2/5] renumber release note Signed-off-by: kkk777-7 --- ...olicy-ancestor-port.md => 10334-route-policy-ancestor-port.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename release-notes/current/bug_fixes/{10333-route-policy-ancestor-port.md => 10334-route-policy-ancestor-port.md} (100%) diff --git a/release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md b/release-notes/current/bug_fixes/10334-route-policy-ancestor-port.md similarity index 100% rename from release-notes/current/bug_fixes/10333-route-policy-ancestor-port.md rename to release-notes/current/bug_fixes/10334-route-policy-ancestor-port.md From e5c747d8e4c2a655f719931a0905dbb044879719 Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Tue, 20 Oct 2026 19:05:53 +0900 Subject: [PATCH 3/5] fix(gatewayapi): keep the parentRef port in ExtensionServerPolicy route ancestors Signed-off-by: kkk777-7 --- internal/gatewayapi/extensionserverpolicy.go | 13 +- ...-parentrefs-differing-only-by-port.in.yaml | 57 +++ ...parentrefs-differing-only-by-port.out.yaml | 357 ++++++++++++++++++ 3 files changed, 423 insertions(+), 4 deletions(-) create mode 100644 internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml create mode 100644 internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index 5dd15c749b6..eb7a9a73cda 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -12,6 +12,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -235,7 +236,11 @@ func (t *Translator) processExtensionServerPolicyForRoute( policyStatus := ExtServerPolicyStatusAsPolicyStatus(policy) gatewayNN := utils.NamespacedName(gtwCtx) + // Keep the port of the parentRef, so that parentRefs to the same Gateway that differ only by port get their own ancestors. + // TODO: once ExtensionServerPolicy supports ListenerSet, report a ListenerSet parentRef against the ListenerSet + // instead of its parent Gateway, and build the ancestor with getAncestorRefForRouteParent as the other route policies do. ancestorRef := getAncestorRefForPolicy(gatewayNN, p.SectionName) + ancestorRef.Port = p.Port if !supportedRouteKind { status.SetTranslationErrorForPolicyAncestor( @@ -356,10 +361,10 @@ func resolveExtServerPolicyRouteTargetRef( } func PolicyStatusToUnstructured(policyStatus gwapiv1.PolicyStatus) map[string]any { - ret := map[string]any{} - // No need to check the marshal/unmarshal error here - d, _ := json.Marshal(policyStatus) - _ = json.Unmarshal(d, &ret) + // Use the unstructured converter rather than a JSON round trip, so that numbers such as the port of an + // ancestorRef are int64 as in any unstructured object, not float64. + // No need to check the error here, the policyStatus is a typed object. + ret, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&policyStatus) return ret } diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml new file mode 100644 index 00000000000..80f37d4ea0c --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml @@ -0,0 +1,57 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: http-8080 + protocol: HTTP + port: 8080 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - foo.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + port: 80 + - namespace: envoy-gateway + name: gateway-1 + port: 8080 + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +extensionServerPolicies: +# The parentRefs differ only by port, so the policy reports an ancestor for each of them. +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-1 + namespace: default + spec: + targetRef: + kind: HTTPRoute + group: gateway.networking.k8s.io + name: httproute-1 + data: "attached to an httproute with parentRefs differing only by port" diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml new file mode 100644 index 00000000000..4a7417da6e4 --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml @@ -0,0 +1,357 @@ +extensionServerPolicies: +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-1 + namespace: default + spec: + data: attached to an httproute with parentRefs differing only by port + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 80 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 8080 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: http-8080 + port: 8080 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-8080 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - foo.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + port: 80 + - name: gateway-1 + namespace: envoy-gateway + port: 8080 + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 80 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + port: 8080 +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - name: envoy-gateway/gateway-1/http-8080 + ports: + - containerPort: 8080 + name: http-8080 + protocol: HTTP + servicePort: 8080 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: "" +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + extensionResources: + - name: foo.example.io/bar/default/policy-1 + object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-1 + namespace: default + spec: + data: attached to an httproute with parentRefs differing only by port + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 80 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + port: 8080 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + extensionServerPolicies: + - name: foo.example.io/bar/default/policy-1 + hostname: foo.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/foo_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + - address: 0.0.0.0 + externalPort: 8080 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-8080 + name: envoy-gateway/gateway-1/http-8080 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + extensionServerPolicies: + - name: foo.example.io/bar/default/policy-1 + hostname: foo.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/foo_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 From e7e86c8960b0e1980b19ebe7988f4e1579a55fbc Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Thu, 22 Oct 2026 17:52:18 +0900 Subject: [PATCH 4/5] generate testdata Signed-off-by: kkk777-7 --- ...parentrefs-differing-only-by-port.out.yaml | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml index e7983e9489c..591e9ca05f3 100644 --- a/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-route-parentrefs-differing-only-by-port.out.yaml @@ -380,34 +380,13 @@ securityPolicies: kind: Gateway name: gateway-1 namespace: envoy-gateway - sectionName: http + port: 80 conditions: - lastTransitionTime: null message: Merged with policy envoy-gateway/policy-for-gateway reason: Merged status: "True" type: Merged - controllerName: gateway.envoyproxy.io/gatewayclass-controller - - ancestorRef: - group: gateway.networking.k8s.io - kind: Gateway - name: gateway-1 - namespace: envoy-gateway - sectionName: http-8080 - conditions: - - lastTransitionTime: null - message: Merged with policy envoy-gateway/policy-for-listener - reason: Merged - status: "True" - type: Merged - controllerName: gateway.envoyproxy.io/gatewayclass-controller - - ancestorRef: - group: gateway.networking.k8s.io - kind: Gateway - name: gateway-1 - namespace: envoy-gateway - port: 80 - conditions: - lastTransitionTime: null message: Policy has been accepted. reason: Accepted @@ -421,6 +400,11 @@ securityPolicies: namespace: envoy-gateway port: 8080 conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-listener + reason: Merged + status: "True" + type: Merged - lastTransitionTime: null message: Policy has been accepted. reason: Accepted From 05c9852468548b652b622221d6bb70680dda9a8e Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Fri, 30 Oct 2026 15:40:15 +0900 Subject: [PATCH 5/5] update extension server policy Signed-off-by: kkk777-7 --- internal/gatewayapi/extensionserverpolicy.go | 7 +- ...-parentrefs-differing-only-by-port.in.yaml | 65 ++++ ...parentrefs-differing-only-by-port.out.yaml | 346 ++++++++++++++++++ 3 files changed, 412 insertions(+), 6 deletions(-) diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index 2bb77b80d04..5ca5e12503a 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -341,12 +341,7 @@ func (t *Translator) processExtensionServerPolicyForRoute( } policyStatus := ExtServerPolicyStatusAsPolicyStatus(policy) - gatewayNN := utils.NamespacedName(gtwCtx) - // Keep the port of the parentRef, so that parentRefs to the same Gateway that differ only by port get their own ancestors. - // TODO: once ExtensionServerPolicy supports ListenerSet, report a ListenerSet parentRef against the ListenerSet - // instead of its parent Gateway, and build the ancestor with getAncestorRefForRouteParent as the other route policies do. - ancestorRef := getAncestorRefForPolicy(gatewayNN, p.SectionName) - ancestorRef.Port = p.Port + ancestorRef := parentRefCtx.PolicyAncestorRef() if !supportedRouteKind { status.SetTranslationErrorForPolicyAncestor( diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml index 80f37d4ea0c..21d384de826 100644 --- a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.in.yaml @@ -6,6 +6,9 @@ gateways: name: gateway-1 spec: gatewayClassName: envoy-gateway-class + allowedListeners: + namespaces: + from: All listeners: - name: http protocol: HTTP @@ -19,6 +22,28 @@ gateways: allowedRoutes: namespaces: from: All +listenerSets: +- apiVersion: gateway.networking.k8s.io/v1 + kind: ListenerSet + metadata: + namespace: envoy-gateway + name: listenerset-1 + spec: + parentRef: + name: gateway-1 + listeners: + - name: ls-http-8081 + protocol: HTTP + port: 8081 + allowedRoutes: + namespaces: + from: All + - name: ls-http-8082 + protocol: HTTP + port: 8082 + allowedRoutes: + namespaces: + from: All httpRoutes: - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -42,6 +67,32 @@ httpRoutes: backendRefs: - name: service-1 port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - bar.example.com + parentRefs: + - namespace: envoy-gateway + name: listenerset-1 + kind: ListenerSet + group: gateway.networking.k8s.io + port: 8081 + - namespace: envoy-gateway + name: listenerset-1 + kind: ListenerSet + group: gateway.networking.k8s.io + port: 8082 + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 extensionServerPolicies: # The parentRefs differ only by port, so the policy reports an ancestor for each of them. - apiVersion: foo.example.io/v1alpha1 @@ -55,3 +106,17 @@ extensionServerPolicies: group: gateway.networking.k8s.io name: httproute-1 data: "attached to an httproute with parentRefs differing only by port" + +# The parentRefs to the ListenerSet differ only by port, so the policy reports an ancestor for each of them, +# against the ListenerSet rather than its parent Gateway. +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-2 + namespace: default + spec: + targetRef: + kind: HTTPRoute + group: gateway.networking.k8s.io + name: httproute-2 + data: "attached to an httproute with ListenerSet parentRefs differing only by port" diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml index 4a7417da6e4..8c2d3bc3976 100644 --- a/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-with-httproute-parentrefs-differing-only-by-port.out.yaml @@ -38,6 +38,45 @@ extensionServerPolicies: status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-2 + namespace: default + spec: + data: attached to an httproute with ListenerSet parentRefs differing only by port + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8081 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8082 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway @@ -45,6 +84,9 @@ gateways: name: gateway-1 namespace: envoy-gateway spec: + allowedListeners: + namespaces: + from: All gatewayClassName: envoy-gateway-class listeners: - allowedRoutes: @@ -60,6 +102,7 @@ gateways: port: 8080 protocol: HTTP status: + attachedListenerSets: 1 listeners: - attachedRoutes: 1 conditions: @@ -164,6 +207,70 @@ httpRoutes: name: gateway-1 namespace: envoy-gateway port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-2 + namespace: default + spec: + hostnames: + - bar.example.com + parentRefs: + - group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8081 + - group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8082 + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8081 + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8082 infraIR: envoy-gateway/gateway-1: proxy: @@ -180,6 +287,18 @@ infraIR: name: http-8080 protocol: HTTP servicePort: 8080 + - name: envoy-gateway/gateway-1/envoy-gateway/listenerset-1/ls-http-8081 + ports: + - containerPort: 8081 + name: http-8081 + protocol: HTTP + servicePort: 8081 + - name: envoy-gateway/gateway-1/envoy-gateway/listenerset-1/ls-http-8082 + ports: + - containerPort: 8082 + name: http-8082 + protocol: HTTP + servicePort: 8082 metadata: labels: gateway.envoyproxy.io/owning-gateway-name: gateway-1 @@ -189,6 +308,97 @@ infraIR: name: envoy-gateway-class name: envoy-gateway/gateway-1 namespace: "" +listenerSets: +- apiVersion: gateway.networking.k8s.io/v1 + kind: ListenerSet + metadata: + name: listenerset-1 + namespace: envoy-gateway + spec: + listeners: + - allowedRoutes: + namespaces: + from: All + name: ls-http-8081 + port: 8081 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: ls-http-8082 + port: 8082 + protocol: HTTP + parentRef: + name: gateway-1 + status: + conditions: + - lastTransitionTime: null + message: All listeners are accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: All listeners are programmed + reason: Programmed + status: "True" + type: Programmed + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: No conflicts detected + reason: NoConflicts + status: "False" + type: Conflicted + name: ls-http-8081 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: No conflicts detected + reason: NoConflicts + status: "False" + type: Conflicted + name: ls-http-8082 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute xdsIR: envoy-gateway/gateway-1: accessLog: @@ -236,6 +446,48 @@ xdsIR: status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller + - name: foo.example.io/bar/default/policy-2 + object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: policy-2 + namespace: default + spec: + data: attached to an httproute with ListenerSet parentRefs differing only + by port + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8081 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: ListenerSet + name: listenerset-1 + namespace: envoy-gateway + port: 8082 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller globalResources: proxyServiceCluster: metadata: @@ -350,6 +602,100 @@ xdsIR: distinct: false name: "" prefix: /foo + - address: 0.0.0.0 + externalPort: 8081 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: ls-http-8081 + name: envoy-gateway/gateway-1/envoy-gateway/listenerset-1/ls-http-8081 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8081 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + extensionServerPolicies: + - name: foo.example.io/bar/default/policy-2 + hostname: bar.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/bar_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar + - address: 0.0.0.0 + externalPort: 8082 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: ls-http-8082 + name: envoy-gateway/gateway-1/envoy-gateway/listenerset-1/ls-http-8082 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8082 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + extensionServerPolicies: + - name: foo.example.io/bar/default/policy-2 + hostname: bar.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/bar_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar readyListener: address: 0.0.0.0 ipFamily: IPv4