From 059cf6615e56a9560ea55bb6e23c89d1b2196621 Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Tue, 17 Mar 2026 03:50:30 +0000 Subject: [PATCH 1/9] Attempt at adding observability mode Signed-off-by: Jake Oliver --- api/v1alpha1/ext_proc_types.go | 7 + api/v1alpha1/zz_generated.deepcopy.go | 5 + ....envoyproxy.io_envoyextensionpolicies.yaml | 12 + ....envoyproxy.io_envoyextensionpolicies.yaml | 12 + .../translate/out/backend-endpoint.all.yaml | 1 + internal/gatewayapi/envoyextensionpolicy.go | 4 + .../resource/testdata/all-resources.out.yaml | 1 + .../resource/testdata/all-resources2.out.yaml | 1 + ...yextensionpolicy-observabilitymode.in.yaml | 85 ++++++ ...extensionpolicy-observabilitymode.out.yaml | 243 ++++++++++++++++++ internal/ir/xds.go | 3 + internal/ir/zz_generated.deepcopy.go | 5 + internal/xds/translator/extproc.go | 4 + .../testdata/in/xds-ir/ext-proc.yaml | 1 + .../out/xds-ir/ext-proc.listeners.yaml | 1 + .../envoyextensionpolicy_test.go | 43 ++++ 16 files changed, 428 insertions(+) create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go index 41f57e9545..483689091c 100644 --- a/api/v1alpha1/ext_proc_types.go +++ b/api/v1alpha1/ext_proc_types.go @@ -69,6 +69,7 @@ type ExtProcProcessingMode struct { // +kubebuilder:validation:XValidation:message="BackendRefs only supports Service, ServiceImport, and Backend kind.",rule="has(self.backendRefs) ? self.backendRefs.all(f, f.kind == 'Service' || f.kind == 'ServiceImport' || f.kind == 'Backend') : true" // +kubebuilder:validation:XValidation:message="BackendRefs only supports Core, multicluster.x-k8s.io, and gateway.envoyproxy.io groups.",rule="has(self.backendRefs) ? (self.backendRefs.all(f, f.group == \"\" || f.group == 'multicluster.x-k8s.io' || f.group == 'gateway.envoyproxy.io')) : true" // +kubebuilder:validation:XValidation:message="If FullDuplexStreamed body processing mode is used, FailOpen must be false.",rule="!(has(self.failOpen) && self.failOpen == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body == 'FullDuplexStreamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == 'FullDuplexStreamed')))" +// +kubebuilder:validation:XValidation:message="If observabilityMode is enabled, body processing mode must be Streamed or unset.",rule="!(has(self.observabilityMode) && self.observabilityMode == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body != 'Streamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body != 'Streamed')))" type ExtProc struct { BackendCluster `json:",inline"` @@ -97,6 +98,12 @@ type ExtProc struct { // +optional ProcessingMode *ExtProcProcessingMode `json:"processingMode,omitempty"` + // ObservabilityMode sets if envoy gateway should treat this external processor as "send and go" + // + // +optional + // +kubebuilder:default=false + ObservabilityMode *bool `json:"observabilityMode,omitempty"` + // Metadata defines options related to the sending and receiving of dynamic metadata. // These options define which metadata namespaces would be sent to the processor and which dynamic metadata // namespaces the processor would be permitted to emit metadata to. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 265b1a21a5..13bfa595ae 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3130,6 +3130,11 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { *out = new(ExtProcProcessingMode) (*in).DeepCopyInto(*out) } + if in.ObservabilityMode != nil { + in, out := &in.ObservabilityMode, &out.ObservabilityMode + *out = new(bool) + **out = **in + } if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata *out = new(ExtProcMetadata) diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index c7e8ec264e..d801501728 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1462,6 +1462,11 @@ spec: Envoy HTTP filter namespaces rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object + observabilityMode: + default: false + description: ObservabilityMode sets if envoy gateway should + treat this external processor as "send and go" + type: boolean processingMode: description: |- ProcessingMode defines how request and response body is processed @@ -1542,6 +1547,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' + - message: If observabilityMode is enabled, body processing mode + must be Streamed or unset. + rule: '!(has(self.observabilityMode) && self.observabilityMode + == true && has(self.processingMode) && ((has(self.processingMode.request) + && has(self.processingMode.request.body) && self.processingMode.request.body + != ''Streamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) + && self.processingMode.response.body != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 7813492774..770b23fa31 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1461,6 +1461,11 @@ spec: Envoy HTTP filter namespaces rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object + observabilityMode: + default: false + description: ObservabilityMode sets if envoy gateway should + treat this external processor as "send and go" + type: boolean processingMode: description: |- ProcessingMode defines how request and response body is processed @@ -1541,6 +1546,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' + - message: If observabilityMode is enabled, body processing mode + must be Streamed or unset. + rule: '!(has(self.observabilityMode) && self.observabilityMode + == true && has(self.processingMode) && ((has(self.processingMode.request) + && has(self.processingMode.request.body) && self.processingMode.request.body + != ''Streamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) + && self.processingMode.response.body != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml index a1d4620b2d..d348212008 100644 --- a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml @@ -64,6 +64,7 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false + observabilityMode: false processingMode: request: {} response: diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index aa3b12ba6d..e5d133e989 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -851,6 +851,10 @@ func (t *Translator) buildExtProc( extProcIR.MessageTimeout = ir.MetaV1DurationPtr(d) } + if extProc.ObservabilityMode != nil { + extProcIR.ObservabilityMode = extProc.ObservabilityMode + } + if extProc.FailOpen != nil { extProcIR.FailOpen = extProc.FailOpen } diff --git a/internal/gatewayapi/resource/testdata/all-resources.out.yaml b/internal/gatewayapi/resource/testdata/all-resources.out.yaml index ed845ce395..62da855449 100644 --- a/internal/gatewayapi/resource/testdata/all-resources.out.yaml +++ b/internal/gatewayapi/resource/testdata/all-resources.out.yaml @@ -114,6 +114,7 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false + observabilityMode: false processingMode: request: {} response: diff --git a/internal/gatewayapi/resource/testdata/all-resources2.out.yaml b/internal/gatewayapi/resource/testdata/all-resources2.out.yaml index ed845ce395..62da855449 100644 --- a/internal/gatewayapi/resource/testdata/all-resources2.out.yaml +++ b/internal/gatewayapi/resource/testdata/all-resources2.out.yaml @@ -114,6 +114,7 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false + observabilityMode: false processingMode: request: {} response: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml new file mode 100644 index 0000000000..176d9223b5 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml @@ -0,0 +1,85 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: grpc-backend + spec: + ports: + - port: 9000 + name: grpc + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-grpc-backend + namespace: default + labels: + kubernetes.io/service-name: grpc-backend + addressType: IPv4 + ports: + - name: grpc + protocol: TCP + port: 9000 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-http-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + extProc: + - backendRefs: + - Name: grpc-backend + Port: 9000 + observabilityMode: true + processingMode: + request: + body: Streamed + response: + body: Streamed diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml new file mode 100644 index 0000000000..90b70696b7 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml @@ -0,0 +1,243 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + name: policy-for-http-route + namespace: default + spec: + extProc: + - backendRefs: + - name: grpc-backend + port: 9000 + observabilityMode: true + processingMode: + request: + body: Streamed + response: + body: Streamed + 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: default + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + 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 +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + 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: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: default/gateway-1 + namespace: envoy-gateway-system +xdsIR: + default/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-default-gateway-1-bfd08ef4 + namespace: envoy-gateway-system + sectionName: "8080" + name: default/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-default-gateway-1-bfd08ef4 + namespace: envoy-gateway-system + sectionName: "8080" + name: default/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: default + sectionName: http + name: default/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 + envoyExtensions: + extProcs: + - authority: grpc-backend.default:9000 + destination: + metadata: + kind: EnvoyExtensionPolicy + name: policy-for-http-route + namespace: default + name: envoyextensionpolicy/default/policy-for-http-route/extproc/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 9000 + metadata: + kind: Service + name: grpc-backend + namespace: default + sectionName: "9000" + name: envoyextensionpolicy/default/policy-for-http-route/extproc/0/backend/0 + protocol: GRPC + weight: 1 + name: envoyextensionpolicy/default/policy-for-http-route/extproc/0 + observabilityMode: true + requestBodyProcessingMode: Streamed + requestHeaderProcessing: true + responseBodyProcessingMode: Streamed + responseHeaderProcessing: true + hostname: www.foo.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index d4900d2902..abf1b5a924 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -3462,6 +3462,9 @@ type ExtProc struct { // MessageTimeout is the timeout for a response to be returned from the external processor MessageTimeout *metav1.Duration `json:"messageTimeout,omitempty" yaml:"messageTimeout,omitempty"` + // ObservabilityMode sets if envoy gateway should treat this external processor as "send and go". + ObservabilityMode *bool `json:"observabilityMode,omitempty" yaml:"observabilityMode,omitempty"` + // FailOpen defines if requests or responses that cannot be processed due to connectivity to the // external processor are terminated or passed-through. FailOpen *bool `json:"failOpen,omitempty" yaml:"failOpen,omitempty"` diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 2cc4e40fc7..00c2da60c2 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1464,6 +1464,11 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { *out = new(metav1.Duration) **out = **in } + if in.ObservabilityMode != nil { + in, out := &in.ObservabilityMode, &out.ObservabilityMode + *out = new(bool) + **out = **in + } if in.FailOpen != nil { in, out := &in.FailOpen, &out.FailOpen *out = new(bool) diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index 86c8c15a08..4f5b6e684b 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -106,6 +106,10 @@ func extProcConfig(extProc *ir.ExtProc) (*extprocv3.ExternalProcessor, error) { config.ProcessingMode = buildProcessingMode(extProc) + if extProc.ObservabilityMode != nil { + config.ObservabilityMode = *extProc.ObservabilityMode + } + if extProc.FailOpen != nil { config.FailureModeAllow = *extProc.FailOpen } diff --git a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml index db49c3c160..b15cb7cc9d 100644 --- a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml @@ -84,6 +84,7 @@ http: name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-2/0/grpc-backend-3/backend/0 - name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/extproc/0 failOpen: false + observabilityMode: true messageTimeout: 15s requestAttributes: - xds.route_metadata diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml index e0741bc3d4..41d9930bf5 100644 --- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml @@ -79,6 +79,7 @@ receivingNamespaces: untyped: - envoy.filters.http.prc_ext + observabilityMode: true processingMode: requestHeaderMode: SKIP requestTrailerMode: SKIP diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index 9918a7171a..5586033564 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -1333,6 +1333,49 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { }, wantErrors: []string{}, }, + { + desc: "invalid ExtProc with FullDuplexStreamed response/request body and invalid body processing mode", + mutate: func(sp *egv1a1.EnvoyExtensionPolicy) { + sp.Spec = egv1a1.EnvoyExtensionPolicySpec{ + ExtProc: []egv1a1.ExtProc{ + { + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "grpc-proc-service", + Port: ptr.To(gwapiv1.PortNumber(80)), + }, + }, + }, + }, + ProcessingMode: &egv1a1.ExtProcProcessingMode{ + Request: &egv1a1.ProcessingModeOptions{ + Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), + }, + Response: &egv1a1.ProcessingModeOptions{ + Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), + }, + }, + ObservabilityMode: ptr.To(true), + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.extProc[0]: Invalid value:", + ": If observabilityMode is enabled, body processing mode must be Streamed or unset.", + }, + } } for _, tc := range cases { From 8889d4b42d416cdc3ac5fe9dbb15f27d8be46cad Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Mon, 23 Mar 2026 21:18:47 +0000 Subject: [PATCH 2/9] Remove false default Signed-off-by: Jake Oliver --- api/v1alpha1/ext_proc_types.go | 1 - .../generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml | 1 - .../generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml | 1 - .../cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml | 1 - internal/gatewayapi/resource/testdata/all-resources.out.yaml | 1 - internal/gatewayapi/resource/testdata/all-resources2.out.yaml | 1 - 6 files changed, 6 deletions(-) diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go index 483689091c..e047e599fc 100644 --- a/api/v1alpha1/ext_proc_types.go +++ b/api/v1alpha1/ext_proc_types.go @@ -101,7 +101,6 @@ type ExtProc struct { // ObservabilityMode sets if envoy gateway should treat this external processor as "send and go" // // +optional - // +kubebuilder:default=false ObservabilityMode *bool `json:"observabilityMode,omitempty"` // Metadata defines options related to the sending and receiving of dynamic metadata. diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index d801501728..124635a3c1 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1463,7 +1463,6 @@ spec: rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object observabilityMode: - default: false description: ObservabilityMode sets if envoy gateway should treat this external processor as "send and go" type: boolean diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 770b23fa31..cb7fe25622 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1462,7 +1462,6 @@ spec: rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object observabilityMode: - default: false description: ObservabilityMode sets if envoy gateway should treat this external processor as "send and go" type: boolean diff --git a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml index d348212008..a1d4620b2d 100644 --- a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml @@ -64,7 +64,6 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false - observabilityMode: false processingMode: request: {} response: diff --git a/internal/gatewayapi/resource/testdata/all-resources.out.yaml b/internal/gatewayapi/resource/testdata/all-resources.out.yaml index 62da855449..ed845ce395 100644 --- a/internal/gatewayapi/resource/testdata/all-resources.out.yaml +++ b/internal/gatewayapi/resource/testdata/all-resources.out.yaml @@ -114,7 +114,6 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false - observabilityMode: false processingMode: request: {} response: diff --git a/internal/gatewayapi/resource/testdata/all-resources2.out.yaml b/internal/gatewayapi/resource/testdata/all-resources2.out.yaml index 62da855449..ed845ce395 100644 --- a/internal/gatewayapi/resource/testdata/all-resources2.out.yaml +++ b/internal/gatewayapi/resource/testdata/all-resources2.out.yaml @@ -114,7 +114,6 @@ envoyExtensionPolicies: port: 9002 weight: 1 failOpen: false - observabilityMode: false processingMode: request: {} response: From 1c24edb3cb93f1d5f9d1d85ec68fd26c765b7323 Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Tue, 28 Apr 2026 13:21:19 +0100 Subject: [PATCH 3/9] change observabilityMode to shadowMode Signed-off-by: Jake Oliver --- api/v1alpha1/ext_proc_types.go | 10 ++++--- api/v1alpha1/zz_generated.deepcopy.go | 4 +-- ....envoyproxy.io_envoyextensionpolicies.yaml | 26 +++++++++++-------- ....envoyproxy.io_envoyextensionpolicies.yaml | 26 +++++++++++-------- internal/gatewayapi/envoyextensionpolicy.go | 4 +-- ...> envoyextensionpolicy-shadowmode.in.yaml} | 2 +- ... envoyextensionpolicy-shadowmode.out.yaml} | 4 +-- internal/ir/xds.go | 5 ++-- internal/ir/zz_generated.deepcopy.go | 4 +-- internal/xds/translator/extproc.go | 4 +-- .../testdata/in/xds-ir/ext-proc.yaml | 2 +- .../envoyextensionpolicy_test.go | 4 +-- 12 files changed, 54 insertions(+), 41 deletions(-) rename internal/gatewayapi/testdata/{envoyextensionpolicy-observabilitymode.in.yaml => envoyextensionpolicy-shadowmode.in.yaml} (98%) rename internal/gatewayapi/testdata/{envoyextensionpolicy-observabilitymode.out.yaml => envoyextensionpolicy-shadowmode.out.yaml} (99%) diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go index e047e599fc..cdbe66fa79 100644 --- a/api/v1alpha1/ext_proc_types.go +++ b/api/v1alpha1/ext_proc_types.go @@ -69,7 +69,7 @@ type ExtProcProcessingMode struct { // +kubebuilder:validation:XValidation:message="BackendRefs only supports Service, ServiceImport, and Backend kind.",rule="has(self.backendRefs) ? self.backendRefs.all(f, f.kind == 'Service' || f.kind == 'ServiceImport' || f.kind == 'Backend') : true" // +kubebuilder:validation:XValidation:message="BackendRefs only supports Core, multicluster.x-k8s.io, and gateway.envoyproxy.io groups.",rule="has(self.backendRefs) ? (self.backendRefs.all(f, f.group == \"\" || f.group == 'multicluster.x-k8s.io' || f.group == 'gateway.envoyproxy.io')) : true" // +kubebuilder:validation:XValidation:message="If FullDuplexStreamed body processing mode is used, FailOpen must be false.",rule="!(has(self.failOpen) && self.failOpen == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body == 'FullDuplexStreamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == 'FullDuplexStreamed')))" -// +kubebuilder:validation:XValidation:message="If observabilityMode is enabled, body processing mode must be Streamed or unset.",rule="!(has(self.observabilityMode) && self.observabilityMode == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body != 'Streamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body != 'Streamed')))" +// +kubebuilder:validation:XValidation:message="If shadowMode is enabled, body processing mode must be Streamed or unset.",rule="!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body != 'Streamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body != 'Streamed')))" type ExtProc struct { BackendCluster `json:",inline"` @@ -98,10 +98,14 @@ type ExtProc struct { // +optional ProcessingMode *ExtProcProcessingMode `json:"processingMode,omitempty"` - // ObservabilityMode sets if envoy gateway should treat this external processor as "send and go" + // ShadowMode sets if envoy gateway should treat this external processor as "send and go". + // When enabled, Envoy forwards request/response data to the external processor but does + // not wait for or apply any response from it. This maps to Envoy's `observability_mode` + // on the ext_proc filter. + // Defaults to false. // // +optional - ObservabilityMode *bool `json:"observabilityMode,omitempty"` + ShadowMode *bool `json:"shadowMode,omitempty"` // Metadata defines options related to the sending and receiving of dynamic metadata. // These options define which metadata namespaces would be sent to the processor and which dynamic metadata diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 13bfa595ae..24ff995308 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3130,8 +3130,8 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { *out = new(ExtProcProcessingMode) (*in).DeepCopyInto(*out) } - if in.ObservabilityMode != nil { - in, out := &in.ObservabilityMode, &out.ObservabilityMode + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode *out = new(bool) **out = **in } diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 124635a3c1..a1a324a021 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1462,10 +1462,6 @@ spec: Envoy HTTP filter namespaces rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object - observabilityMode: - description: ObservabilityMode sets if envoy gateway should - treat this external processor as "send and go" - type: boolean processingMode: description: |- ProcessingMode defines how request and response body is processed @@ -1525,6 +1521,14 @@ spec: type: string type: object type: object + shadowMode: + description: |- + ShadowMode sets if envoy gateway should treat this external processor as "send and go". + When enabled, Envoy forwards request/response data to the external processor but does + not wait for or apply any response from it. This maps to Envoy's `observability_mode` + on the ext_proc filter. + Defaults to false. + type: boolean type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. @@ -1546,13 +1550,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' - - message: If observabilityMode is enabled, body processing mode - must be Streamed or unset. - rule: '!(has(self.observabilityMode) && self.observabilityMode - == true && has(self.processingMode) && ((has(self.processingMode.request) - && has(self.processingMode.request.body) && self.processingMode.request.body - != ''Streamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) - && self.processingMode.response.body != ''Streamed'')))' + - message: If shadowMode is enabled, body processing mode must be + Streamed or unset. + rule: '!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) + && ((has(self.processingMode.request) && has(self.processingMode.request.body) + && self.processingMode.request.body != ''Streamed'') || (has(self.processingMode.response) + && has(self.processingMode.response.body) && self.processingMode.response.body + != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index cb7fe25622..f26e2de23c 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1461,10 +1461,6 @@ spec: Envoy HTTP filter namespaces rule: self.all(f, !f.startsWith('envoy.filters.http')) type: object - observabilityMode: - description: ObservabilityMode sets if envoy gateway should - treat this external processor as "send and go" - type: boolean processingMode: description: |- ProcessingMode defines how request and response body is processed @@ -1524,6 +1520,14 @@ spec: type: string type: object type: object + shadowMode: + description: |- + ShadowMode sets if envoy gateway should treat this external processor as "send and go". + When enabled, Envoy forwards request/response data to the external processor but does + not wait for or apply any response from it. This maps to Envoy's `observability_mode` + on the ext_proc filter. + Defaults to false. + type: boolean type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. @@ -1545,13 +1549,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' - - message: If observabilityMode is enabled, body processing mode - must be Streamed or unset. - rule: '!(has(self.observabilityMode) && self.observabilityMode - == true && has(self.processingMode) && ((has(self.processingMode.request) - && has(self.processingMode.request.body) && self.processingMode.request.body - != ''Streamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) - && self.processingMode.response.body != ''Streamed'')))' + - message: If shadowMode is enabled, body processing mode must be + Streamed or unset. + rule: '!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) + && ((has(self.processingMode.request) && has(self.processingMode.request.body) + && self.processingMode.request.body != ''Streamed'') || (has(self.processingMode.response) + && has(self.processingMode.response.body) && self.processingMode.response.body + != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index e5d133e989..fd5710cfcc 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -851,8 +851,8 @@ func (t *Translator) buildExtProc( extProcIR.MessageTimeout = ir.MetaV1DurationPtr(d) } - if extProc.ObservabilityMode != nil { - extProcIR.ObservabilityMode = extProc.ObservabilityMode + if extProc.ShadowMode != nil { + extProcIR.ShadowMode = extProc.ShadowMode } if extProc.FailOpen != nil { diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.in.yaml similarity index 98% rename from internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml rename to internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.in.yaml index 176d9223b5..c736ce6ec1 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.in.yaml @@ -77,7 +77,7 @@ envoyExtensionPolicies: - backendRefs: - Name: grpc-backend Port: 9000 - observabilityMode: true + shadowMode: true processingMode: request: body: Streamed diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml similarity index 99% rename from internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml rename to internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml index 90b70696b7..05508c9ebc 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-observabilitymode.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml @@ -9,7 +9,7 @@ envoyExtensionPolicies: - backendRefs: - name: grpc-backend port: 9000 - observabilityMode: true + shadowMode: true processingMode: request: body: Streamed @@ -220,7 +220,7 @@ xdsIR: protocol: GRPC weight: 1 name: envoyextensionpolicy/default/policy-for-http-route/extproc/0 - observabilityMode: true + shadowMode: true requestBodyProcessingMode: Streamed requestHeaderProcessing: true responseBodyProcessingMode: Streamed diff --git a/internal/ir/xds.go b/internal/ir/xds.go index abf1b5a924..a175f411e3 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -3462,8 +3462,9 @@ type ExtProc struct { // MessageTimeout is the timeout for a response to be returned from the external processor MessageTimeout *metav1.Duration `json:"messageTimeout,omitempty" yaml:"messageTimeout,omitempty"` - // ObservabilityMode sets if envoy gateway should treat this external processor as "send and go". - ObservabilityMode *bool `json:"observabilityMode,omitempty" yaml:"observabilityMode,omitempty"` + // ShadowMode sets if envoy gateway should treat this external processor as "send and go". + // Maps to Envoy's `observability_mode` on the ext_proc filter. + ShadowMode *bool `json:"shadowMode,omitempty" yaml:"shadowMode,omitempty"` // FailOpen defines if requests or responses that cannot be processed due to connectivity to the // external processor are terminated or passed-through. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 00c2da60c2..290f8e4608 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1464,8 +1464,8 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { *out = new(metav1.Duration) **out = **in } - if in.ObservabilityMode != nil { - in, out := &in.ObservabilityMode, &out.ObservabilityMode + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode *out = new(bool) **out = **in } diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index 4f5b6e684b..9221167abe 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -106,8 +106,8 @@ func extProcConfig(extProc *ir.ExtProc) (*extprocv3.ExternalProcessor, error) { config.ProcessingMode = buildProcessingMode(extProc) - if extProc.ObservabilityMode != nil { - config.ObservabilityMode = *extProc.ObservabilityMode + if extProc.ShadowMode != nil { + config.ObservabilityMode = *extProc.ShadowMode } if extProc.FailOpen != nil { diff --git a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml index b15cb7cc9d..eec766ea6b 100644 --- a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml @@ -84,7 +84,7 @@ http: name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-2/0/grpc-backend-3/backend/0 - name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/extproc/0 failOpen: false - observabilityMode: true + shadowMode: true messageTimeout: 15s requestAttributes: - xds.route_metadata diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index 5586033564..cb7e12d13e 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -1357,7 +1357,7 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), }, }, - ObservabilityMode: ptr.To(true), + ShadowMode: ptr.To(true), }, }, PolicyTargetReferences: egv1a1.PolicyTargetReferences{ @@ -1373,7 +1373,7 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { }, wantErrors: []string{ "spec.extProc[0]: Invalid value:", - ": If observabilityMode is enabled, body processing mode must be Streamed or unset.", + ": If shadowMode is enabled, body processing mode must be Streamed or unset.", }, } } From 5d96dd499e5702e8fdbeeff0079ad4b8bf910eee Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Fri, 1 May 2026 15:02:20 +0100 Subject: [PATCH 4/9] run make gen Signed-off-by: Jake Oliver --- .../envoyextensionpolicy-shadowmode.out.yaml | 4 ++-- site/content/en/latest/api/extension_types.md | 1 + test/helm/gateway-crds-helm/all.out.yaml | 15 +++++++++++++++ test/helm/gateway-crds-helm/e2e.out.yaml | 15 +++++++++++++++ .../gateway-crds-helm/envoy-gateway-crds.out.yaml | 15 +++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml index 05508c9ebc..5286446657 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-shadowmode.out.yaml @@ -9,12 +9,12 @@ envoyExtensionPolicies: - backendRefs: - name: grpc-backend port: 9000 - shadowMode: true processingMode: request: body: Streamed response: body: Streamed + shadowMode: true targetRef: group: gateway.networking.k8s.io kind: HTTPRoute @@ -220,11 +220,11 @@ xdsIR: protocol: GRPC weight: 1 name: envoyextensionpolicy/default/policy-for-http-route/extproc/0 - shadowMode: true requestBodyProcessingMode: Streamed requestHeaderProcessing: true responseBodyProcessingMode: Streamed responseHeaderProcessing: true + shadowMode: true hostname: www.foo.com isHTTP2: false metadata: diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 7161a720f9..69025abcb0 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2259,6 +2259,7 @@ _Appears in:_ | `messageTimeout` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.5/spec/#duration)_ | false | | MessageTimeout is the timeout for a response to be returned from the external processor
Default: 200ms | | `failOpen` | _boolean_ | false | false | FailOpen is a switch used to control the behavior when failing to call the external processor.
If FailOpen is set to true, the system bypasses the ExtProc extension and
allows the traffic to pass through. If it is set to false or
not set (defaulting to false), the system blocks the traffic and returns
an HTTP 5xx error.
If set to true, the ExtProc extension will also be bypassed if the configuration is invalid. | | `processingMode` | _[ExtProcProcessingMode](#extprocprocessingmode)_ | false | | ProcessingMode defines how request and response body is processed
Default: header and body are not sent to the external processor | +| `shadowMode` | _boolean_ | false | | ShadowMode sets if envoy gateway should treat this external processor as "send and go".
When enabled, Envoy forwards request/response data to the external processor but does
not wait for or apply any response from it. This maps to Envoy's `observability_mode`
on the ext_proc filter.
Defaults to false. | | `metadata` | _[ExtProcMetadata](#extprocmetadata)_ | false | | Refer to Kubernetes API documentation for fields of `metadata`. | diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 1c5f0bf69b..cf56beaf7d 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -29557,6 +29557,14 @@ spec: type: string type: object type: object + shadowMode: + description: |- + ShadowMode sets if envoy gateway should treat this external processor as "send and go". + When enabled, Envoy forwards request/response data to the external processor but does + not wait for or apply any response from it. This maps to Envoy's `observability_mode` + on the ext_proc filter. + Defaults to false. + type: boolean type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. @@ -29578,6 +29586,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' + - message: If shadowMode is enabled, body processing mode must be + Streamed or unset. + rule: '!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) + && ((has(self.processingMode.request) && has(self.processingMode.request.body) + && self.processingMode.request.body != ''Streamed'') || (has(self.processingMode.response) + && has(self.processingMode.response.body) && self.processingMode.response.body + != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 5360fb0d79..603e07ec2b 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -7530,6 +7530,14 @@ spec: type: string type: object type: object + shadowMode: + description: |- + ShadowMode sets if envoy gateway should treat this external processor as "send and go". + When enabled, Envoy forwards request/response data to the external processor but does + not wait for or apply any response from it. This maps to Envoy's `observability_mode` + on the ext_proc filter. + Defaults to false. + type: boolean type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. @@ -7551,6 +7559,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' + - message: If shadowMode is enabled, body processing mode must be + Streamed or unset. + rule: '!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) + && ((has(self.processingMode.request) && has(self.processingMode.request.body) + && self.processingMode.request.body != ''Streamed'') || (has(self.processingMode.response) + && has(self.processingMode.response.body) && self.processingMode.response.body + != ''Streamed'')))' maxItems: 16 type: array lua: diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index e6ddaa2a5d..04182d493c 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -7530,6 +7530,14 @@ spec: type: string type: object type: object + shadowMode: + description: |- + ShadowMode sets if envoy gateway should treat this external processor as "send and go". + When enabled, Envoy forwards request/response data to the external processor but does + not wait for or apply any response from it. This maps to Envoy's `observability_mode` + on the ext_proc filter. + Defaults to false. + type: boolean type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. @@ -7551,6 +7559,13 @@ spec: && self.processingMode.request.body == ''FullDuplexStreamed'') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == ''FullDuplexStreamed'')))' + - message: If shadowMode is enabled, body processing mode must be + Streamed or unset. + rule: '!(has(self.shadowMode) && self.shadowMode == true && has(self.processingMode) + && ((has(self.processingMode.request) && has(self.processingMode.request.body) + && self.processingMode.request.body != ''Streamed'') || (has(self.processingMode.response) + && has(self.processingMode.response.body) && self.processingMode.response.body + != ''Streamed'')))' maxItems: 16 type: array lua: From d7c20e47de46a367a3c13d498f493cd6c90c4d5b Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Mon, 4 May 2026 04:32:04 +0100 Subject: [PATCH 5/9] Fix lint error Signed-off-by: Jake Oliver --- internal/gatewayapi/envoyextensionpolicy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index ac2766d3c9..e4a1bf9e04 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -842,7 +842,7 @@ func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resource hasFailClose := false for idx, ep := range policy.Spec.ExtProc { name := irConfigNameForExtProc(policy, idx) - extProcIR, err := t.buildExtProc(name, policy, ep, idx, resources, gtwCtx) + extProcIR, err := t.buildExtProc(name, policy, &ep, idx, resources, gtwCtx) if err != nil { errs = errors.Join(errs, err) if ep.FailOpen == nil || !*ep.FailOpen { @@ -863,7 +863,7 @@ func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resource func (t *Translator) buildExtProc( name string, policy *egv1a1.EnvoyExtensionPolicy, - extProc egv1a1.ExtProc, + extProc *egv1a1.ExtProc, extProcIdx int, resources *resource.Resources, gtwCtx *GatewayContext, From da59f921053694397b45b81c30670768faf1b421 Mon Sep 17 00:00:00 2001 From: Jake Oliver Date: Mon, 4 May 2026 04:32:13 +0100 Subject: [PATCH 6/9] Fix test error Signed-off-by: Jake Oliver --- test/cel-validation/envoyextensionpolicy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index fc72fe406f..92663e192d 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -1374,7 +1374,7 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { "spec.extProc[0]: Invalid value:", ": If shadowMode is enabled, body processing mode must be Streamed or unset.", }, - } + }, } for _, tc := range cases { From 8420df0a9c7cd7db4ce5942180e9986921410d49 Mon Sep 17 00:00:00 2001 From: Lee Boynton Date: Thu, 9 Jul 2026 16:52:16 +0100 Subject: [PATCH 7/9] add missing import Signed-off-by: Lee Boynton --- test/cel-validation/envoyextensionpolicy_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index 92663e192d..471d94d7f9 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -15,6 +15,7 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" From 40029f703fd986e0d41a324736cad8d2d9fec953 Mon Sep 17 00:00:00 2001 From: Lee Boynton Date: Thu, 9 Jul 2026 17:53:53 +0100 Subject: [PATCH 8/9] test: use new() builtin instead of ptr.To in envoyextensionpolicy CEL test Signed-off-by: Lee Boynton --- test/cel-validation/envoyextensionpolicy_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index 471d94d7f9..d838c0c2d2 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -15,7 +15,6 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -1344,20 +1343,20 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { { BackendObjectReference: gwapiv1.BackendObjectReference{ Name: "grpc-proc-service", - Port: ptr.To(gwapiv1.PortNumber(80)), + Port: new(gwapiv1.PortNumber(80)), }, }, }, }, ProcessingMode: &egv1a1.ExtProcProcessingMode{ Request: &egv1a1.ProcessingModeOptions{ - Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), + Body: new(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), }, Response: &egv1a1.ProcessingModeOptions{ - Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), + Body: new(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), }, }, - ShadowMode: ptr.To(true), + ShadowMode: new(true), }, }, PolicyTargetReferences: egv1a1.PolicyTargetReferences{ From ddaf0d7c0ae219c960707946c088a4d8b6a88b18 Mon Sep 17 00:00:00 2001 From: Lee Boynton Date: Thu, 9 Jul 2026 17:59:42 +0100 Subject: [PATCH 9/9] regen Signed-off-by: Lee Boynton --- test/helm/gateway-crds-helm/all.out.yaml | 3 --- test/helm/gateway-crds-helm/e2e.out.yaml | 3 --- test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml | 3 --- 3 files changed, 9 deletions(-) diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index d5702ad9f7..1b920530a9 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -32122,7 +32122,6 @@ spec: type: string type: object type: object -<<<<<<< HEAD shadowMode: description: |- ShadowMode sets if envoy gateway should treat this external processor as "send and go". @@ -32131,7 +32130,6 @@ spec: on the ext_proc filter. Defaults to false. type: boolean -======= statusOnError: description: |- Sets the HTTP status that is returned to the client when the external processor returns an error @@ -32177,7 +32175,6 @@ spec: - 511 format: int32 type: integer ->>>>>>> 69e072f40907f1533241861bb1d881b08ea0662b type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index ca1f422bf3..11e2617fef 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -8060,7 +8060,6 @@ spec: type: string type: object type: object -<<<<<<< HEAD shadowMode: description: |- ShadowMode sets if envoy gateway should treat this external processor as "send and go". @@ -8069,7 +8068,6 @@ spec: on the ext_proc filter. Defaults to false. type: boolean -======= statusOnError: description: |- Sets the HTTP status that is returned to the client when the external processor returns an error @@ -8115,7 +8113,6 @@ spec: - 511 format: int32 type: integer ->>>>>>> 69e072f40907f1533241861bb1d881b08ea0662b type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported. diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index a967dff25c..bfc7752c72 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -8060,7 +8060,6 @@ spec: type: string type: object type: object -<<<<<<< HEAD shadowMode: description: |- ShadowMode sets if envoy gateway should treat this external processor as "send and go". @@ -8069,7 +8068,6 @@ spec: on the ext_proc filter. Defaults to false. type: boolean -======= statusOnError: description: |- Sets the HTTP status that is returned to the client when the external processor returns an error @@ -8115,7 +8113,6 @@ spec: - 511 format: int32 type: integer ->>>>>>> 69e072f40907f1533241861bb1d881b08ea0662b type: object x-kubernetes-validations: - message: BackendRefs must be used, backendRef is not supported.