diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go index 95893d3c61..9e821f68ce 100644 --- a/api/v1alpha1/ext_proc_types.go +++ b/api/v1alpha1/ext_proc_types.go @@ -17,7 +17,8 @@ const ( StreamedExtProcBodyProcessingMode ExtProcBodyProcessingMode = "Streamed" // BufferedExtProcBodyProcessingMode will buffer the message body in memory and send the entire body at once. If the body exceeds the configured buffer limit, then the downstream system will receive an error. BufferedExtProcBodyProcessingMode ExtProcBodyProcessingMode = "Buffered" - // FullDuplexStreamedExtBodyProcessingMode will send the body in pieces, to be read in a stream. Full details here: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_proc/v3/processing_mode.proto.html#enum-extensions-filters-http-ext-proc-v3-processingmode-bodysendmode + // FullDuplexStreamedExtBodyProcessingMode will send the body in pieces, to be read in a stream. When enabled, trailers are also sent, and failOpen must be false. + // Full details here: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_proc/v3/processing_mode.proto.html#enum-extensions-filters-http-ext-proc-v3-processingmode-bodysendmode FullDuplexStreamedExtBodyProcessingMode ExtProcBodyProcessingMode = "FullDuplexStreamed" // BufferedPartialExtBodyHeaderProcessingMode will buffer the message body in memory and send the entire body in one chunk. If the body exceeds the configured buffer limit, then the body contents up to the buffer limit will be sent. BufferedPartialExtBodyHeaderProcessingMode ExtProcBodyProcessingMode = "BufferedPartial" @@ -67,6 +68,7 @@ type ExtProcProcessingMode struct { // +kubebuilder:validation:XValidation:message="BackendRefs must be used, backendRef is not supported.",rule="!has(self.backendRef)" // +kubebuilder:validation:XValidation:message="BackendRefs only supports Service and Backend kind.",rule="has(self.backendRefs) ? self.backendRefs.all(f, f.kind == 'Service' || f.kind == 'Backend') : true" // +kubebuilder:validation:XValidation:message="BackendRefs only supports Core and gateway.envoyproxy.io group.",rule="has(self.backendRefs) ? (self.backendRefs.all(f, f.group == \"\" || 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.request.body) && self.processingMode.request.body == 'FullDuplexStreamed') || (has(self.processingMode.response.body) && self.processingMode.response.body == 'FullDuplexStreamed')))" type ExtProc struct { BackendCluster `json:",inline"` 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 6896bb9568..3513591318 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 @@ -1057,6 +1057,12 @@ spec: group. rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group == "" || f.group == ''gateway.envoyproxy.io'')) : true' + - message: If FullDuplexStreamed body processing mode is used, FailOpen + must be false. + rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body) + && self.processingMode.request.body == ''FullDuplexStreamed'') + || (has(self.processingMode.response.body) && self.processingMode.response.body + == ''FullDuplexStreamed'')))' maxItems: 16 type: array lua: diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 9e2e94109c..e825e8284e 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1056,6 +1056,12 @@ spec: group. rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group == "" || f.group == ''gateway.envoyproxy.io'')) : true' + - message: If FullDuplexStreamed body processing mode is used, FailOpen + must be false. + rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body) + && self.processingMode.request.body == ''FullDuplexStreamed'') + || (has(self.processingMode.response.body) && self.processingMode.response.body + == ''FullDuplexStreamed'')))' maxItems: 16 type: array lua: diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index 1aca83fdbd..a1039575f4 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -99,16 +99,10 @@ func extProcConfig(extProc ir.ExtProc) *extprocv3.ExternalProcessor { Seconds: defaultExtServiceRequestTimeout, }, }, - ProcessingMode: &extprocv3.ProcessingMode{ - RequestHeaderMode: extprocv3.ProcessingMode_SKIP, - ResponseHeaderMode: extprocv3.ProcessingMode_SKIP, - RequestBodyMode: extprocv3.ProcessingMode_NONE, - ResponseBodyMode: extprocv3.ProcessingMode_NONE, - RequestTrailerMode: extprocv3.ProcessingMode_SKIP, - ResponseTrailerMode: extprocv3.ProcessingMode_SKIP, - }, } + config.ProcessingMode = buildProcessingMode(extProc) + if extProc.FailOpen != nil { config.FailureModeAllow = *extProc.FailOpen } @@ -117,22 +111,6 @@ func extProcConfig(extProc ir.ExtProc) *extprocv3.ExternalProcessor { config.MessageTimeout = durationpb.New(extProc.MessageTimeout.Duration) } - if extProc.RequestBodyProcessingMode != nil { - config.ProcessingMode.RequestBodyMode = buildExtProcBodyProcessingMode(extProc.RequestBodyProcessingMode) - } - - if extProc.RequestHeaderProcessing { - config.ProcessingMode.RequestHeaderMode = extprocv3.ProcessingMode_SEND - } - - if extProc.ResponseBodyProcessingMode != nil { - config.ProcessingMode.ResponseBodyMode = buildExtProcBodyProcessingMode(extProc.ResponseBodyProcessingMode) - } - - if extProc.ResponseHeaderProcessing { - config.ProcessingMode.ResponseHeaderMode = extprocv3.ProcessingMode_SEND - } - if extProc.RequestAttributes != nil { var attrs []string attrs = append(attrs, extProc.RequestAttributes...) @@ -232,7 +210,43 @@ func (*extProc) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HT return nil } -func buildExtProcBodyProcessingMode(mode *ir.ExtProcBodyProcessingMode) extprocv3.ProcessingMode_BodySendMode { +func buildProcessingMode(extProc ir.ExtProc) *extprocv3.ProcessingMode { + processingMode := &extprocv3.ProcessingMode{ + RequestHeaderMode: extprocv3.ProcessingMode_SKIP, + ResponseHeaderMode: extprocv3.ProcessingMode_SKIP, + RequestBodyMode: extprocv3.ProcessingMode_NONE, + ResponseBodyMode: extprocv3.ProcessingMode_NONE, + RequestTrailerMode: extprocv3.ProcessingMode_SKIP, + ResponseTrailerMode: extprocv3.ProcessingMode_SKIP, + } + + if extProc.RequestBodyProcessingMode != nil { + processingMode.RequestBodyMode = translateExtProcBodyProcessingMode(extProc.RequestBodyProcessingMode) + // + if processingMode.RequestBodyMode == extprocv3.ProcessingMode_FULL_DUPLEX_STREAMED { + processingMode.RequestTrailerMode = extprocv3.ProcessingMode_SEND + } + } + + if extProc.RequestHeaderProcessing { + processingMode.RequestHeaderMode = extprocv3.ProcessingMode_SEND + } + + if extProc.ResponseBodyProcessingMode != nil { + processingMode.ResponseBodyMode = translateExtProcBodyProcessingMode(extProc.ResponseBodyProcessingMode) + if processingMode.ResponseBodyMode == extprocv3.ProcessingMode_FULL_DUPLEX_STREAMED { + processingMode.ResponseTrailerMode = extprocv3.ProcessingMode_SEND + } + } + + if extProc.ResponseHeaderProcessing { + processingMode.ResponseHeaderMode = extprocv3.ProcessingMode_SEND + } + + return processingMode +} + +func translateExtProcBodyProcessingMode(mode *ir.ExtProcBodyProcessingMode) extprocv3.ProcessingMode_BodySendMode { lookup := map[ir.ExtProcBodyProcessingMode]extprocv3.ProcessingMode_BodySendMode{ ir.ExtProcBodyBuffered: extprocv3.ProcessingMode_BUFFERED, ir.ExtProcBodyBufferedPartial: extprocv3.ProcessingMode_BUFFERED_PARTIAL, 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 6f39befb84..bc4b11ed09 100644 --- a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml @@ -102,6 +102,28 @@ http: - protocol: GRPC weight: 1 name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0/grpc-backend/backend/0 + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-3/rule/0/backend/0 + envoyExtensions: + extProcs: + - name: envoyextensionpolicy/envoy-gateway/policy-for-route-3/extproc/0 + authority: grpc-backend-3.envoy-gateway:3000 + requestBodyProcessingMode: FullDuplexStreamed + responseBodyProcessingMode: FullDuplexStreamed + destination: + name: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3 + settings: + - protocol: GRPC + weight: 1 + name: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3/backend/0 hostname: gateway.envoyproxy.io isHTTP2: false name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml index 14ff9d61df..6e72ef5092 100755 --- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml @@ -32,6 +32,23 @@ name: httproute/default/httproute-2/rule/0 perConnectionBufferLimitBytes: 32768 type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-3/rule/0 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-3/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS - circuitBreakers: thresholds: - maxRetries: 1024 @@ -128,3 +145,27 @@ http2ProtocolOptions: initialConnectionWindowSize: 1048576 initialStreamWindowSize: 65536 +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3 + perConnectionBufferLimitBytes: 32768 + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml index 4ec680ce7f..b6f8426798 100755 --- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml @@ -22,6 +22,18 @@ loadBalancingWeight: 1 locality: region: httproute/default/httproute-2/rule/0/backend/0 +- clusterName: httproute/default/httproute-3/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-3/rule/0/backend/0 - clusterName: envoyextensionpolicy/default/policy-for-route-2/0/grpc-backend-4 endpoints: - loadBalancingWeight: 1 @@ -42,3 +54,8 @@ - loadBalancingWeight: 1 locality: region: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0/grpc-backend/backend/0 +- clusterName: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3 + endpoints: + - loadBalancingWeight: 1 + locality: + region: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3/backend/0 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 16f3139344..7b5e8bbb77 100755 --- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml @@ -103,6 +103,22 @@ - connection.requested_server_name responseAttributes: - request.path + - disabled: true + name: envoy.filters.http.ext_proc/envoyextensionpolicy/envoy-gateway/policy-for-route-3/extproc/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor + grpcService: + envoyGrpc: + authority: grpc-backend-3.envoy-gateway:3000 + clusterName: envoyextensionpolicy/envoy-gateway/policy-for-route-3/0/grpc-backend-3 + timeout: 10s + processingMode: + requestBodyMode: FULL_DUPLEX_STREAMED + requestHeaderMode: SKIP + requestTrailerMode: SEND + responseBodyMode: FULL_DUPLEX_STREAMED + responseHeaderMode: SKIP + responseTrailerMode: SEND - name: envoy.filters.http.router typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml index c17e6456d1..8cef38ab6a 100755 --- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml @@ -22,6 +22,20 @@ - match: pathSeparatedPrefix: /bar name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + route: + cluster: httproute/default/httproute-3/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.ext_proc/envoyextensionpolicy/envoy-gateway/policy-for-route-3/extproc/0: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} + - domains: + - "" + name: envoy-gateway/gateway-1/http/ + routes: + - match: + prefix: / route: cluster: httproute/default/httproute-2/rule/0 upgradeConfigs: diff --git a/release-notes/current.yaml b/release-notes/current.yaml index df7b4c7604..aa805e8c84 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -36,6 +36,9 @@ bug fixes: | Fixed issue that switch on wrong SubjectAltNameType enum value in BackendTLSPolicy. Fixed issue that BackendTLSPolicy should not reference ConfigMap or Secret across namespace. Fixed bug in certificate SANs overlap detection in listeners. + Fixed issue where EnvoyExtensionPolicy ExtProc body processing mode is set to FullDuplexStreamed, but trailers were not sent. + Fixed validation issue where EnvoyExtensionPolicy ExtProc failOpen is true, and body processing mode FullDuplexStreamed is not rejected. + # Enhancements that improve performance. performance improvements: | diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index db10b6eb94..855ddb417c 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1715,7 +1715,7 @@ _Appears in:_ | ----- | ----------- | | `Streamed` | StreamedExtProcBodyProcessingMode will stream the body to the server in pieces as they arrive at the proxy.
| | `Buffered` | BufferedExtProcBodyProcessingMode will buffer the message body in memory and send the entire body at once. If the body exceeds the configured buffer limit, then the downstream system will receive an error.
| -| `FullDuplexStreamed` | FullDuplexStreamedExtBodyProcessingMode will send the body in pieces, to be read in a stream. Full details here: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_proc/v3/processing_mode.proto.html#enum-extensions-filters-http-ext-proc-v3-processingmode-bodysendmode
| +| `FullDuplexStreamed` | FullDuplexStreamedExtBodyProcessingMode will send the body in pieces, to be read in a stream. When enabled, trailers are also sent, and failOpen must be false.
Full details here: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_proc/v3/processing_mode.proto.html#enum-extensions-filters-http-ext-proc-v3-processingmode-bodysendmode
| | `BufferedPartial` | BufferedPartialExtBodyHeaderProcessingMode will buffer the message body in memory and send the entire body in one chunk. If the body exceeds the configured buffer limit, then the body contents up to the buffer limit will be sent.
| diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go index c8f3fe776a..195d45dd05 100644 --- a/test/cel-validation/envoyextensionpolicy_test.go +++ b/test/cel-validation/envoyextensionpolicy_test.go @@ -414,6 +414,209 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) { "spec.extProc[0].processingMode.request.body: Unsupported value: \"not-a-body-mode\": supported values: \"Streamed\", \"Buffered\", \"BufferedPartial\"", }, }, + { + desc: "valid ExtProc without FullDuplexStreamed body and failOpen true", + 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("Buffered")), + }, + Response: &egv1a1.ProcessingModeOptions{ + Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("Buffered")), + }, + }, + FailOpen: ptr.To(true), + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "valid ExtProc with FullDuplexStreamed body and failOpen false", + 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")), + }, + }, + FailOpen: ptr.To(false), + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "valid ExtProc with FullDuplexStreamed body and failOpen nil", + 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")), + }, + }, + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "invalid ExtProc with FullDuplexStreamed request body and failOpen", + 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("Buffered")), + }, + }, + FailOpen: ptr.To(true), + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.extProc[0]: Invalid value: \"object\": If FullDuplexStreamed body processing mode is used, FailOpen must be false.", + }, + }, + { + desc: "invalid ExtProc with FullDuplexStreamed response body and failOpen", + 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("Buffered")), + }, + Response: &egv1a1.ProcessingModeOptions{ + Body: ptr.To(egv1a1.ExtProcBodyProcessingMode("FullDuplexStreamed")), + }, + }, + FailOpen: ptr.To(true), + }, + }, + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "eg", + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.extProc[0]: Invalid value: \"object\": If FullDuplexStreamed body processing mode is used, FailOpen must be false.", + }, + }, { desc: "Valid Lua filter (inline)", mutate: func(sp *egv1a1.EnvoyExtensionPolicy) { diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index a723ab566d..9f093bfb1b 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -22227,6 +22227,12 @@ spec: group. rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group == "" || f.group == ''gateway.envoyproxy.io'')) : true' + - message: If FullDuplexStreamed body processing mode is used, FailOpen + must be false. + rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body) + && self.processingMode.request.body == ''FullDuplexStreamed'') + || (has(self.processingMode.response.body) && self.processingMode.response.body + == ''FullDuplexStreamed'')))' 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 d4a47a4240..352dcf6ede 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -4915,6 +4915,12 @@ spec: group. rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group == "" || f.group == ''gateway.envoyproxy.io'')) : true' + - message: If FullDuplexStreamed body processing mode is used, FailOpen + must be false. + rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body) + && self.processingMode.request.body == ''FullDuplexStreamed'') + || (has(self.processingMode.response.body) && self.processingMode.response.body + == ''FullDuplexStreamed'')))' maxItems: 16 type: array lua: