diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index b2e7e29149f..fc917d06a98 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -201,5 +201,25 @@ func (t *Translator) translateExtServerPolicyForGateway( }) found = true } + for _, currListener := range gwIR.TCP { + listenerName := currListener.Name[strings.LastIndex(currListener.Name, "/")+1:] + if target.SectionName != nil && string(*target.SectionName) != listenerName { + continue + } + currListener.ExtensionRefs = append(currListener.ExtensionRefs, &ir.UnstructuredRef{ + Object: policy, + }) + found = true + } + for _, currListener := range gwIR.UDP { + listenerName := currListener.Name[strings.LastIndex(currListener.Name, "/")+1:] + if target.SectionName != nil && string(*target.SectionName) != listenerName { + continue + } + currListener.ExtensionRefs = append(currListener.ExtensionRefs, &ir.UnstructuredRef{ + Object: policy, + }) + found = true + } return found } diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index 7e9dc649002..47c1df0a7b1 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -101,10 +101,12 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap switch listener.Protocol { case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType: irListener := &ir.HTTPListener{ - Name: irListenerName(listener), - Address: "0.0.0.0", - Port: uint32(containerPort), - TLS: irTLSConfigs(listener.tlsSecrets...), + CoreListenerDetails: ir.CoreListenerDetails{ + Name: irListenerName(listener), + Address: "0.0.0.0", + Port: uint32(containerPort), + }, + TLS: irTLSConfigs(listener.tlsSecrets...), Path: ir.PathSettings{ MergeSlashes: true, EscapedSlashesAction: ir.UnescapeAndRedirect, @@ -121,9 +123,11 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap xdsIR[irKey].HTTP = append(xdsIR[irKey].HTTP, irListener) case gwapiv1.TCPProtocolType, gwapiv1.TLSProtocolType: irListener := &ir.TCPListener{ - Name: irListenerName(listener), - Address: "0.0.0.0", - Port: uint32(containerPort), + CoreListenerDetails: ir.CoreListenerDetails{ + Name: irListenerName(listener), + Address: "0.0.0.0", + Port: uint32(containerPort), + }, // Gateway is processed firstly, then ClientTrafficPolicy, then xRoute. // TLS field should be added to TCPListener as ClientTrafficPolicy will affect @@ -134,9 +138,11 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap xdsIR[irKey].TCP = append(xdsIR[irKey].TCP, irListener) case gwapiv1.UDPProtocolType: irListener := &ir.UDPListener{ - Name: irListenerName(listener), - Address: "0.0.0.0", - Port: uint32(containerPort), + CoreListenerDetails: ir.CoreListenerDetails{ + Name: irListenerName(listener), + Address: "0.0.0.0", + Port: uint32(containerPort), + }, } xdsIR[irKey].UDP = append(xdsIR[irKey].UDP, irListener) } diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.in.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.in.yaml new file mode 100644 index 00000000000..4e345374561 --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.in.yaml @@ -0,0 +1,45 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 81 + allowedRoutes: + namespaces: + from: All +extensionServerPolicies: + - apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + targetRef: + kind: Gateway + group: gateway.networking.k8s.io + name: gateway-1 + data: "attached to all listeners" + - apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + targetRef: + kind: Gateway + group: gateway.networking.k8s.io + name: gateway-1 + sectionName: tcp1 + data: "attached only to listener on port 80" diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.out.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.out.yaml new file mode 100644 index 00000000000..0095b815fd7 --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-tcp-listener.out.yaml @@ -0,0 +1,236 @@ +extensionServerPolicies: +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + data: attached only to listener on port 80 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: tcp1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: tcp1 + 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: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: tcp1 + port: 80 + protocol: TCP + - allowedRoutes: + namespaces: + from: All + name: tcp2 + port: 81 + protocol: TCP + status: + listeners: + - attachedRoutes: 0 + 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: tcp1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + - attachedRoutes: 0 + 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: tcp2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/tcp1 + ports: + - containerPort: 10080 + name: tcp-80 + protocol: TCP + servicePort: 80 + - address: null + name: envoy-gateway/gateway-1/tcp2 + ports: + - containerPort: 10081 + name: tcp-81 + protocol: TCP + servicePort: 81 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + tcp: + - address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + data: attached only to listener on port 80 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: tcp1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: tcp1 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/tcp1 + port: 10080 + - address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/tcp2 + port: 10081 diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.in.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.in.yaml new file mode 100644 index 00000000000..4fe98c919e9 --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.in.yaml @@ -0,0 +1,45 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: udp1 + protocol: UDP + port: 162 + allowedRoutes: + namespaces: + from: All + - name: udp2 + protocol: UDP + port: 163 + allowedRoutes: + namespaces: + from: All +extensionServerPolicies: + - apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + targetRef: + kind: Gateway + group: gateway.networking.k8s.io + name: gateway-1 + data: "attached to all listeners" + - apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + targetRef: + kind: Gateway + group: gateway.networking.k8s.io + name: gateway-1 + sectionName: udp1 + data: "attached only to listener on port 162" diff --git a/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.out.yaml b/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.out.yaml new file mode 100644 index 00000000000..1ff835552d9 --- /dev/null +++ b/internal/gatewayapi/testdata/extensions/extensionpolicy-udp-listener.out.yaml @@ -0,0 +1,236 @@ +extensionServerPolicies: +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + data: attached only to listener on port 162 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: udp1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: udp1 + 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: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: udp1 + port: 162 + protocol: UDP + - allowedRoutes: + namespaces: + from: All + name: udp2 + port: 163 + protocol: UDP + status: + listeners: + - attachedRoutes: 0 + 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: udp1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute + - attachedRoutes: 0 + 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: udp2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: UDPRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/udp1 + ports: + - containerPort: 10162 + name: udp-162 + protocol: UDP + servicePort: 162 + - address: null + name: envoy-gateway/gateway-1/udp2 + ports: + - containerPort: 10163 + name: udp-163 + protocol: UDP + servicePort: 163 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + udp: + - address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test2 + namespace: envoy-gateway + spec: + data: attached only to listener on port 162 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: udp1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: udp1 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/udp1 + port: 10162 + - address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/udp2 + port: 10163 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 2c58ab98783..b6669fd16fc 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -192,15 +192,45 @@ func (x Xds) Printable() *Xds { return out } -// HTTPListener holds the listener configuration. +type Listener interface { + GetName() string + GetAddress() string + GetPort() uint32 + GetExtensionRefs() []*UnstructuredRef +} + // +k8s:deepcopy-gen=true -type HTTPListener struct { +type CoreListenerDetails struct { // Name of the HttpListener Name string `json:"name" yaml:"name"` // Address that the listener should listen on. Address string `json:"address" yaml:"address"` // Port on which the service can be expected to be accessed by clients. Port uint32 `json:"port" yaml:"port"` + // ExtensionRefs holds unstructured resources that were introduced by an extension policy + ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"` +} + +func (l CoreListenerDetails) GetName() string { + return l.Name +} + +func (l CoreListenerDetails) GetAddress() string { + return l.Address +} + +func (l CoreListenerDetails) GetPort() uint32 { + return l.Port +} + +func (l CoreListenerDetails) GetExtensionRefs() []*UnstructuredRef { + return l.ExtensionRefs +} + +// HTTPListener holds the listener configuration. +// +k8s:deepcopy-gen=true +type HTTPListener struct { + CoreListenerDetails `json:",inline" yaml:",inline"` // Hostnames (Host/Authority header value) with which the service can be expected to be accessed by clients. // This field is required. Wildcard hosts are supported in the suffix or prefix form. // Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-virtualhost @@ -236,8 +266,6 @@ type HTTPListener struct { Timeout *ClientTimeout `json:"timeout,omitempty" yaml:"clientTimeout,omitempty"` // Connection settings Connection *Connection `json:"connection,omitempty" yaml:"connection,omitempty"` - // ExtensionRefs holds unstructured resources that were introduced by an extension policy - ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"` } // Validate the fields within the HTTPListener structure @@ -1307,12 +1335,7 @@ func (s StringMatch) Validate() error { // TCPListener holds the TCP listener configuration. // +k8s:deepcopy-gen=true type TCPListener struct { - // Name of the TCPListener - Name string `json:"name" yaml:"name"` - // Address that the listener should listen on. - Address string `json:"address" yaml:"address"` - // Port on which the service can be expected to be accessed by clients. - Port uint32 `json:"port" yaml:"port"` + CoreListenerDetails `json:",inline" yaml:",inline"` // TLS holds information for configuring TLS on a listener. TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty"` // TCPKeepalive configuration for the listener @@ -1442,12 +1465,7 @@ func (t TLSInspectorConfig) Validate() error { // UDPListener holds the UDP listener configuration. // +k8s:deepcopy-gen=true type UDPListener struct { - // Name of the UDPListener - Name string `json:"name" yaml:"name"` - // Address that the listener should listen on. - Address string `json:"address" yaml:"address"` - // Port on which the service can be expected to be accessed by clients. - Port uint32 `json:"port" yaml:"port"` + CoreListenerDetails `json:",inline" yaml:",inline"` // Route associated with UDP traffic to the listener. Route *UDPRoute `json:"route,omitempty" yaml:"route,omitempty"` } diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index c5f9f1e34a6..aa16a614a8c 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -22,16 +22,20 @@ import ( var ( // HTTPListener happyHTTPListener = HTTPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&happyHTTPRoute}, } happyHTTPSListener = HTTPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, TLS: &TLSConfig{ Certificates: []TLSCertificate{{ @@ -43,9 +47,11 @@ var ( Routes: []*HTTPRoute{&happyHTTPRoute}, } redactedHappyHTTPSListener = HTTPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, TLS: &TLSConfig{ Certificates: []TLSCertificate{{ @@ -57,63 +63,81 @@ var ( Routes: []*HTTPRoute{&happyHTTPRoute}, } invalidAddrHTTPListener = HTTPListener{ - Name: "invalid-addr", - Address: "1.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-addr", + Address: "1.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&happyHTTPRoute}, } invalidBackendHTTPListener = HTTPListener{ - Name: "invalid-backend-match", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-backend-match", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&invalidBackendHTTPRoute}, } weightedInvalidBackendsHTTPListener = HTTPListener{ - Name: "weighted-invalid-backends-match", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Name: "weighted-invalid-backends-match", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&weightedInvalidBackendsHTTPRoute}, } // TCPListener happyTCPListenerTLSPassthrough = TCPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, - Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, } happyTCPListenerTLSTerminate = TCPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, - Routes: []*TCPRoute{&happyTCPRouteTLSTermination}, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&happyTCPRouteTLSTermination}, } emptySNITCPListenerTLSPassthrough = TCPListener{ - Name: "empty-sni", - Address: "0.0.0.0", - Port: 80, - Routes: []*TCPRoute{&emptySNITCPRoute}, + CoreListenerDetails: CoreListenerDetails{ + Name: "empty-sni", + Address: "0.0.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&emptySNITCPRoute}, } invalidNameTCPListenerTLSPassthrough = TCPListener{ - Address: "0.0.0.0", - Port: 80, - Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, + CoreListenerDetails: CoreListenerDetails{ + Address: "0.0.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, } invalidAddrTCPListenerTLSPassthrough = TCPListener{ - Name: "invalid-addr", - Address: "1.0.0", - Port: 80, - Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-addr", + Address: "1.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&happyTCPRouteTLSPassthrough}, } invalidSNITCPListenerTLSPassthrough = TCPListener{ - Address: "0.0.0.0", - Port: 80, - Routes: []*TCPRoute{&invalidSNITCPRoute}, + CoreListenerDetails: CoreListenerDetails{ + Address: "0.0.0.0", + Port: 80, + }, + Routes: []*TCPRoute{&invalidSNITCPRoute}, } // TCPRoute @@ -144,27 +168,35 @@ var ( // UDPListener happyUDPListener = UDPListener{ - Name: "happy", - Address: "0.0.0.0", - Port: 80, - Route: &happyUDPRoute, + CoreListenerDetails: CoreListenerDetails{ + Name: "happy", + Address: "0.0.0.0", + Port: 80, + }, + Route: &happyUDPRoute, } invalidNameUDPListener = UDPListener{ - Address: "0.0.0.0", - Port: 80, - Route: &happyUDPRoute, + CoreListenerDetails: CoreListenerDetails{ + Address: "0.0.0.0", + Port: 80, + }, + Route: &happyUDPRoute, } invalidAddrUDPListener = UDPListener{ - Name: "invalid-addr", - Address: "1.0.0", - Port: 80, - Route: &happyUDPRoute, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-addr", + Address: "1.0.0", + Port: 80, + }, + Route: &happyUDPRoute, } invalidPortUDPListenerT = UDPListener{ - Name: "invalid-port", - Address: "0.0.0.0", - Port: 0, - Route: &happyUDPRoute, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-port", + Address: "0.0.0.0", + Port: 0, + }, + Route: &happyUDPRoute, } // UDPRoute @@ -577,8 +609,10 @@ func TestValidateHTTPListener(t *testing.T) { { name: "invalid name", input: HTTPListener{ - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: CoreListenerDetails{ + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*HTTPRoute{&happyHTTPRoute}, }, @@ -592,9 +626,11 @@ func TestValidateHTTPListener(t *testing.T) { { name: "invalid port and hostnames", input: HTTPListener{ - Name: "invalid-port-and-hostnames", - Address: "1.0.0", - Routes: []*HTTPRoute{&happyHTTPRoute}, + CoreListenerDetails: CoreListenerDetails{ + Name: "invalid-port-and-hostnames", + Address: "1.0.0", + }, + Routes: []*HTTPRoute{&happyHTTPRoute}, }, want: []error{ErrListenerPortInvalid, ErrHTTPListenerHostnamesEmpty}, }, @@ -724,14 +760,14 @@ func TestEqualXds(t *testing.T) { desc: "out of order tcp listeners are equal", a: &Xds{ TCP: []*TCPListener{ - {Name: "listener-1"}, - {Name: "listener-2"}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-2"}}, }, }, b: &Xds{ TCP: []*TCPListener{ - {Name: "listener-2"}, - {Name: "listener-1"}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-2"}}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}}, }, }, equal: true, @@ -741,7 +777,7 @@ func TestEqualXds(t *testing.T) { a: &Xds{ HTTP: []*HTTPListener{ { - Name: "listener-1", + CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}, Routes: []*HTTPRoute{ {Name: "route-1"}, {Name: "route-2"}, @@ -752,7 +788,7 @@ func TestEqualXds(t *testing.T) { b: &Xds{ HTTP: []*HTTPListener{ { - Name: "listener-1", + CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}, Routes: []*HTTPRoute{ {Name: "route-2"}, {Name: "route-1"}, @@ -766,14 +802,14 @@ func TestEqualXds(t *testing.T) { desc: "out of order udp listeners are equal", a: &Xds{ UDP: []*UDPListener{ - {Name: "listener-1"}, - {Name: "listener-2"}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-2"}}, }, }, b: &Xds{ UDP: []*UDPListener{ - {Name: "listener-2"}, - {Name: "listener-1"}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-2"}}, + {CoreListenerDetails: CoreListenerDetails{Name: "listener-1"}}, }, }, equal: true, diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index c760c66055a..20f8f78fab1 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -447,6 +447,32 @@ func (in *ConsistentHash) DeepCopy() *ConsistentHash { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CoreListenerDetails) DeepCopyInto(out *CoreListenerDetails) { + *out = *in + if in.ExtensionRefs != nil { + in, out := &in.ExtensionRefs, &out.ExtensionRefs + *out = make([]*UnstructuredRef, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(UnstructuredRef) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoreListenerDetails. +func (in *CoreListenerDetails) DeepCopy() *CoreListenerDetails { + if in == nil { + return nil + } + out := new(CoreListenerDetails) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestinationEndpoint) DeepCopyInto(out *DestinationEndpoint) { *out = *in @@ -957,6 +983,7 @@ func (in *HTTPHealthChecker) DeepCopy() *HTTPHealthChecker { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPListener) DeepCopyInto(out *HTTPListener) { *out = *in + in.CoreListenerDetails.DeepCopyInto(&out.CoreListenerDetails) if in.Hostnames != nil { in, out := &in.Hostnames, &out.Hostnames *out = make([]string, len(*in)) @@ -1019,17 +1046,6 @@ func (in *HTTPListener) DeepCopyInto(out *HTTPListener) { *out = new(Connection) (*in).DeepCopyInto(*out) } - if in.ExtensionRefs != nil { - in, out := &in.ExtensionRefs, &out.ExtensionRefs - *out = make([]*UnstructuredRef, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(UnstructuredRef) - (*in).DeepCopyInto(*out) - } - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPListener. @@ -2240,6 +2256,7 @@ func (in *TCPKeepalive) DeepCopy() *TCPKeepalive { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPListener) DeepCopyInto(out *TCPListener) { *out = *in + in.CoreListenerDetails.DeepCopyInto(&out.CoreListenerDetails) if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSConfig) @@ -2674,6 +2691,7 @@ func (in *TrafficFeatures) DeepCopy() *TrafficFeatures { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UDPListener) DeepCopyInto(out *UDPListener) { *out = *in + in.CoreListenerDetails.DeepCopyInto(&out.CoreListenerDetails) if in.Route != nil { in, out := &in.Route, &out.Route *out = new(UDPRoute) diff --git a/internal/message/watchutil_test.go b/internal/message/watchutil_test.go index 8e76a2554be..2c08821b211 100644 --- a/internal/message/watchutil_test.go +++ b/internal/message/watchutil_test.go @@ -74,14 +74,14 @@ func TestXdsIRUpdates(t *testing.T) { xx: []*ir.Xds{ { HTTP: []*ir.HTTPListener{ - {Name: "listener-1"}, - {Name: "listener-2"}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-1"}}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-2"}}, }, }, { HTTP: []*ir.HTTPListener{ - {Name: "listener-2"}, - {Name: "listener-1"}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-2"}}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-1"}}, }, }, }, @@ -92,13 +92,13 @@ func TestXdsIRUpdates(t *testing.T) { xx: []*ir.Xds{ { HTTP: []*ir.HTTPListener{ - {Name: "listener-1"}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-1"}}, }, }, { HTTP: []*ir.HTTPListener{ - {Name: "listener-1"}, - {Name: "listener-2"}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-1"}}, + {CoreListenerDetails: ir.CoreListenerDetails{Name: "listener-2"}}, }, }, }, diff --git a/internal/xds/translator/extensionserver_test.go b/internal/xds/translator/extensionserver_test.go index a25077d63a8..ef34d3f8d50 100644 --- a/internal/xds/translator/extensionserver_test.go +++ b/internal/xds/translator/extensionserver_test.go @@ -166,6 +166,34 @@ func (t *testingExtensionServer) PostHTTPListenerModify(_ context.Context, req * return &pb.PostHTTPListenerModifyResponse{ Listener: modifiedListener, }, nil + case "envoy-gateway/gateway-1/http1": + if len(req.PostListenerContext.ExtensionResources) != 1 { + return &pb.PostHTTPListenerModifyResponse{ + Listener: req.Listener, + }, fmt.Errorf("received %d extension policies when expecting 1: %s", + len(req.PostListenerContext.ExtensionResources), req.Listener.Name) + } + modifiedListener := proto.Clone(req.Listener).(*listenerV3.Listener) + modifiedListener.StatPrefix = req.Listener.Name + return &pb.PostHTTPListenerModifyResponse{ + Listener: modifiedListener, + }, nil + case "envoy-gateway/gateway-1/tcp1": + return &pb.PostHTTPListenerModifyResponse{ + Listener: req.Listener, + }, fmt.Errorf("should not be called for this listener, test 'extensionpolicy-tcp-and-http' should merge tcp and http gateways to one listener") + case "envoy-gateway/gateway-1/udp1": + if len(req.PostListenerContext.ExtensionResources) != 1 { + return &pb.PostHTTPListenerModifyResponse{ + Listener: req.Listener, + }, fmt.Errorf("received %d extension policies when expecting 1: %s", + len(req.PostListenerContext.ExtensionResources), req.Listener.Name) + } + modifiedListener := proto.Clone(req.Listener).(*listenerV3.Listener) + modifiedListener.StatPrefix = req.Listener.Name + return &pb.PostHTTPListenerModifyResponse{ + Listener: modifiedListener, + }, nil } return &pb.PostHTTPListenerModifyResponse{ Listener: req.Listener, diff --git a/internal/xds/translator/runner/runner_test.go b/internal/xds/translator/runner/runner_test.go index e6c9eebe90d..dba7ddeb622 100644 --- a/internal/xds/translator/runner/runner_test.go +++ b/internal/xds/translator/runner/runner_test.go @@ -50,9 +50,11 @@ func TestRunner(t *testing.T) { res := ir.Xds{ HTTP: []*ir.HTTPListener{ { - Name: "test", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: ir.CoreListenerDetails{ + Name: "test", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*ir.HTTPRoute{ { @@ -129,9 +131,11 @@ func TestRunner_withExtensionManager(t *testing.T) { res := ir.Xds{ HTTP: []*ir.HTTPListener{ { - Name: "test", - Address: "0.0.0.0", - Port: 80, + CoreListenerDetails: ir.CoreListenerDetails{ + Name: "test", + Address: "0.0.0.0", + Port: 80, + }, Hostnames: []string{"example.com"}, Routes: []*ir.HTTPRoute{ { diff --git a/internal/xds/translator/testdata/in/extension-xds-ir/extensionpolicy-tcp-udp-http.yaml b/internal/xds/translator/testdata/in/extension-xds-ir/extensionpolicy-tcp-udp-http.yaml new file mode 100644 index 00000000000..77ced570f46 --- /dev/null +++ b/internal/xds/translator/testdata/in/extension-xds-ir/extensionpolicy-tcp-udp-http.yaml @@ -0,0 +1,107 @@ +http: +- address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http1 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 +tcp: +- address: 0.0.0.0 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/tcp1 + port: 10080 +udp: +- address: 0.0.0.0 + route: + name: "udp-route" + destination: + name: "udp-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + extensionRefs: + - object: + apiVersion: foo.example.io/v1alpha1 + kind: Bar + metadata: + name: test1 + namespace: envoy-gateway + spec: + data: attached to all listeners + targetRef: + 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 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + name: envoy-gateway/gateway-1/udp1 + port: 10162 diff --git a/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.clusters.yaml b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.clusters.yaml new file mode 100644 index 00000000000..8012c6fa499 --- /dev/null +++ b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.clusters.yaml @@ -0,0 +1,27 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: udp-route-dest + lbPolicy: LEAST_REQUEST + name: udp-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- loadAssignment: + clusterName: mock-extension-injected-cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: exampleservice.examplenamespace.svc.cluster.local + portValue: 5000 + name: mock-extension-injected-cluster diff --git a/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.endpoints.yaml b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.endpoints.yaml new file mode 100644 index 00000000000..8869685de5e --- /dev/null +++ b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.endpoints.yaml @@ -0,0 +1,12 @@ +- clusterName: udp-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: udp-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.listeners.yaml b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.listeners.yaml new file mode 100644 index 00000000000..3d5d41d69ff --- /dev/null +++ b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.listeners.yaml @@ -0,0 +1,55 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: envoy-gateway/gateway-1/http1 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + name: envoy-gateway/gateway-1/http1 + drainType: MODIFY_ONLY + name: envoy-gateway/gateway-1/http1 + perConnectionBufferLimitBytes: 32768 + statPrefix: envoy-gateway/gateway-1/http1 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10162 + protocol: UDP + listenerFilters: + - name: envoy.filters.udp_listener.udp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + matcher: + onNoMatch: + action: + name: route + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: udp-route-dest + statPrefix: service + name: envoy-gateway/gateway-1/udp1 + statPrefix: envoy-gateway/gateway-1/udp1 diff --git a/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.routes.yaml b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.routes.yaml new file mode 100644 index 00000000000..b03ec37faa6 --- /dev/null +++ b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.routes.yaml @@ -0,0 +1,2 @@ +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http1 diff --git a/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.secrets.yaml b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.secrets.yaml new file mode 100644 index 00000000000..b04034756d9 --- /dev/null +++ b/internal/xds/translator/testdata/out/extension-xds-ir/extensionpolicy-tcp-udp-http.secrets.yaml @@ -0,0 +1,4 @@ +- genericSecret: + secret: + inlineString: super-secret-extension-secret + name: mock-extension-injected-secret diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 707e8d32995..2f6876df099 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -29,6 +29,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" extensionTypes "github.com/envoyproxy/gateway/internal/extension/types" "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/utils" "github.com/envoyproxy/gateway/internal/utils/protocov" "github.com/envoyproxy/gateway/internal/xds/types" ) @@ -69,8 +70,8 @@ type GlobalRateLimitSettings struct { } // Translate translates the XDS IR into xDS resources -func (t *Translator) Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { - if ir == nil { +func (t *Translator) Translate(xdsIR *ir.Xds) (*types.ResourceVersionTable, error) { + if xdsIR == nil { return nil, errors.New("ir is nil") } @@ -86,28 +87,33 @@ func (t *Translator) Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) // to fail the entire xDS translation to panic users, but instead, we want // to collect all errors and reflect them in the status of the CRDs. var errs error + if err := t.processHTTPListenerXdsTranslation( - tCtx, ir.HTTP, ir.AccessLog, ir.Tracing, ir.Metrics); err != nil { + tCtx, xdsIR.HTTP, xdsIR.AccessLog, xdsIR.Tracing, xdsIR.Metrics); err != nil { errs = errors.Join(errs, err) } - if err := processTCPListenerXdsTranslation(tCtx, ir.TCP, ir.AccessLog, ir.Metrics); err != nil { + if err := t.processTCPListenerXdsTranslation(tCtx, xdsIR.TCP, xdsIR.AccessLog, xdsIR.Metrics); err != nil { errs = errors.Join(errs, err) } - if err := processUDPListenerXdsTranslation(tCtx, ir.UDP, ir.AccessLog, ir.Metrics); err != nil { + if err := processUDPListenerXdsTranslation(tCtx, xdsIR.UDP, xdsIR.AccessLog, xdsIR.Metrics); err != nil { errs = errors.Join(errs, err) } - if err := processJSONPatches(tCtx, ir.EnvoyPatchPolicies); err != nil { + if err := t.notifyExtensionServerAboutListeners(tCtx, xdsIR); err != nil { errs = errors.Join(errs, err) } - if err := processClusterForAccessLog(tCtx, ir.AccessLog, ir.Metrics); err != nil { + if err := processJSONPatches(tCtx, xdsIR.EnvoyPatchPolicies); err != nil { errs = errors.Join(errs, err) } - if err := processClusterForTracing(tCtx, ir.Tracing, ir.Metrics); err != nil { + if err := processClusterForAccessLog(tCtx, xdsIR.AccessLog, xdsIR.Metrics); err != nil { + errs = errors.Join(errs, err) + } + + if err := processClusterForTracing(tCtx, xdsIR.Tracing, xdsIR.Metrics); err != nil { errs = errors.Join(errs, err) } @@ -120,6 +126,65 @@ func (t *Translator) Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) return tCtx, errs } +func findIRListenersByXDSListener(xdsIR *ir.Xds, listener *listenerv3.Listener) []ir.Listener { + ret := []ir.Listener{} + + addr := listener.Address.GetSocketAddress() + if addr == nil { + return ret + } + for _, l := range xdsIR.HTTP { + if l.GetAddress() == addr.GetAddress() && l.GetPort() == addr.GetPortValue() { + ret = append(ret, l) + } + } + for _, l := range xdsIR.TCP { + if l.GetAddress() == addr.GetAddress() && l.GetPort() == addr.GetPortValue() { + ret = append(ret, l) + } + } + for _, l := range xdsIR.UDP { + if l.GetAddress() == addr.GetAddress() && l.GetPort() == addr.GetPortValue() { + ret = append(ret, l) + } + } + return ret +} + +// notifyExtensionServerAboutListeners calls the extension server about all the translated listeners. +func (t *Translator) notifyExtensionServerAboutListeners( + tCtx *types.ResourceVersionTable, + xdsIR *ir.Xds, +) error { + // Return quickly if there is no extension manager or the Listener hook is not being used. + if t.ExtensionManager == nil { + return nil + } + if (*t.ExtensionManager).GetPostXDSHookClient(egv1a1.XDSHTTPListener) == nil { + return nil + } + + var errs error + for _, l := range tCtx.XdsResources[resourcev3.ListenerType] { + listener := l.(*listenerv3.Listener) + policies := []*ir.UnstructuredRef{} + alreadyIncludedPolicies := map[utils.NamespacedNameWithGroupKind]bool{} + for _, irListener := range findIRListenersByXDSListener(xdsIR, listener) { + for _, pol := range irListener.GetExtensionRefs() { + key := utils.GetNamespacedNameWithGroupKind(pol.Object) + if _, found := alreadyIncludedPolicies[key]; !found { + policies = append(policies, pol) + alreadyIncludedPolicies[key] = true + } + } + } + if err := processExtensionPostListenerHook(tCtx, listener, policies, t.ExtensionManager); err != nil { + errs = errors.Join(errs, err) + } + } + return errs +} + func (t *Translator) processHTTPListenerXdsTranslation( tCtx *types.ResourceVersionTable, httpListeners []*ir.HTTPListener, @@ -302,13 +367,6 @@ func (t *Translator) processHTTPListenerXdsTranslation( if err = t.createRateLimitServiceCluster(tCtx, httpListener, metrics); err != nil { errs = errors.Join(errs, err) } - - // Check if an extension want to modify the listener that was just configured/created - // If no extension exists (or it doesn't subscribe to this hook) then this is a quick no-op - // TODO zhaohuabing should we also process the quicXDSListener? - if err = processExtensionPostListenerHook(tCtx, tcpXDSListener, httpListener.ExtensionRefs, t.ExtensionManager); err != nil { - errs = errors.Join(errs, err) - } } return errs @@ -484,7 +542,12 @@ func buildHTTP3AltSvcHeader(port int) *corev3.HeaderValueOption { } } -func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListeners []*ir.TCPListener, accesslog *ir.AccessLog, metrics *ir.Metrics) error { +func (t *Translator) processTCPListenerXdsTranslation( + tCtx *types.ResourceVersionTable, + tcpListeners []*ir.TCPListener, + accesslog *ir.AccessLog, + metrics *ir.Metrics, +) error { // The XDS translation is done in a best-effort manner, so we collect all // errors and return them at the end. var errs error @@ -553,7 +616,12 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe return errs } -func processUDPListenerXdsTranslation(tCtx *types.ResourceVersionTable, udpListeners []*ir.UDPListener, accesslog *ir.AccessLog, metrics *ir.Metrics) error { +func processUDPListenerXdsTranslation( + tCtx *types.ResourceVersionTable, + udpListeners []*ir.UDPListener, + accesslog *ir.AccessLog, + metrics *ir.Metrics, +) error { // The XDS translation is done in a best-effort manner, so we collect all // errors and return them at the end. var errs error diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 751224b6e12..4e559dda22b 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -243,6 +243,11 @@ func TestTranslateXdsWithExtension(t *testing.T) { Version: "v1alpha1", Kind: "ExtensionPolicy", }, + { + Group: "foo.example.io", + Version: "v1alpha1", + Kind: "Bar", + }, }, Hooks: &egv1a1.ExtensionHooks{ XDSTranslator: &egv1a1.XDSTranslatorHooks{