diff --git a/api/v1alpha1/envoyextensionypolicy_types.go b/api/v1alpha1/envoyextensionypolicy_types.go index f4bb6aa9d561..6cee71c8d2d9 100644 --- a/api/v1alpha1/envoyextensionypolicy_types.go +++ b/api/v1alpha1/envoyextensionypolicy_types.go @@ -46,12 +46,12 @@ type EnvoyExtensionPolicySpec struct { // TargetRef TargetRef gwapiv1a2.PolicyTargetReferenceWithSectionName `json:"targetRef"` - // WASM is a list of Wasm extensions to be loaded by the Gateway. + // Wasm is a list of Wasm extensions to be loaded by the Gateway. // Order matters, as the extensions will be loaded in the order they are // defined in this list. // // +optional - WASM []Wasm `json:"wasm,omitempty"` + Wasm []Wasm `json:"wasm,omitempty"` // ExtProc is an ordered list of external processing filters // that should added to the envoy filter chain diff --git a/api/v1alpha1/wasm_types.go b/api/v1alpha1/wasm_types.go index f918f92c9e07..dc20a6de67a3 100644 --- a/api/v1alpha1/wasm_types.go +++ b/api/v1alpha1/wasm_types.go @@ -78,8 +78,9 @@ type WasmCodeSource struct { Image *ImageWasmCodeSource `json:"image,omitempty"` // SHA256 checksum that will be used to verify the wasm code. - // +optional - // SHA256 *string `json:"sha256,omitempty"` + // + // kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` + SHA256 string `json:"sha256"` } // WasmCodeSourceType specifies the types of sources for the wasm code. @@ -92,6 +93,9 @@ const ( // ImageWasmCodeSourceType allows the user to specify the wasm code in an OCI image. ImageWasmCodeSourceType WasmCodeSourceType = "Image" + + // ConfigMapCodeSourceType allows the user to specify the wasm code in a ConfigMap. + ConfigMapWasmCodeSourceType WasmCodeSourceType = "ConfigMap" ) // HTTPWasmCodeSource defines the HTTP URL containing the wasm code. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e82cda7787fc..d9c95d0b0fee 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -766,8 +766,8 @@ func (in *EnvoyExtensionPolicyList) DeepCopyObject() runtime.Object { func (in *EnvoyExtensionPolicySpec) DeepCopyInto(out *EnvoyExtensionPolicySpec) { *out = *in in.TargetRef.DeepCopyInto(&out.TargetRef) - if in.WASM != nil { - in, out := &in.WASM, &out.WASM + if in.Wasm != nil { + in, out := &in.Wasm, &out.Wasm *out = make([]Wasm, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index ca843d4d7a9c..5c02f6e7de31 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -6,6 +6,7 @@ package gatewayapi import ( + "errors" "fmt" "sort" "strings" @@ -293,6 +294,19 @@ func resolveEEPolicyRouteTargetRef(policy *egv1a1.EnvoyExtensionPolicy, routes m func (t *Translator) translateEnvoyExtensionPolicyForRoute(policy *egv1a1.EnvoyExtensionPolicy, route RouteContext, xdsIR XdsIRMap, resources *Resources) error { + var ( + extProcs []ir.ExtProc + wasms []ir.Wasm + err, errs error + ) + + if extProcs, err = t.buildExtProcs(policy, resources); err != nil { + errs = errors.Join(errs, err) + } + if wasms, err = t.buildWasms(policy); err != nil { + errs = errors.Join(errs, err) + } + // Apply IR to all relevant routes prefix := irRoutePrefix(route) for _, ir := range xdsIR { @@ -300,17 +314,14 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(policy *egv1a1.EnvoyE for _, r := range http.Routes { // Apply if there is a match if strings.HasPrefix(r.Name, prefix) { - if extProcs, err := t.buildExtProcs(policy, resources); err == nil { - r.ExtProcs = extProcs - } else { - return err - } + r.ExtProcs = extProcs + r.Wasms = wasms } } } } - return nil + return errs } func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resources *Resources) ([]ir.ExtProc, error) { @@ -320,21 +331,24 @@ func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resource return nil, nil } - if len(policy.Spec.ExtProc) > 0 { - for idx, ep := range policy.Spec.ExtProc { - name := irConfigNameForEEP(policy, idx) - extProcIR, err := t.buildExtProc(name, utils.NamespacedName(policy), ep, idx, resources) - if err != nil { - return nil, err - } - extProcIRList = append(extProcIRList, *extProcIR) + for idx, ep := range policy.Spec.ExtProc { + name := irConfigNameForEEP(policy, idx) + extProcIR, err := t.buildExtProc(name, utils.NamespacedName(policy), ep, idx, resources) + if err != nil { + return nil, err } + extProcIRList = append(extProcIRList, *extProcIR) } return extProcIRList, nil } func (t *Translator) translateEnvoyExtensionPolicyForGateway(policy *egv1a1.EnvoyExtensionPolicy, gateway *GatewayContext, xdsIR XdsIRMap, resources *Resources) error { + var ( + extProcs []ir.ExtProc + wasms []ir.Wasm + err, errs error + ) irKey := t.getIRKey(gateway.Gateway) // Should exist since we've validated this @@ -345,9 +359,11 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(policy *egv1a1.Envo string(policy.Spec.TargetRef.Name), ) - extProcs, err := t.buildExtProcs(policy, resources) - if err != nil { - return err + if extProcs, err = t.buildExtProcs(policy, resources); err != nil { + errs = errors.Join(errs, err) + } + if wasms, err = t.buildWasms(policy); err != nil { + errs = errors.Join(errs, err) } for _, http := range ir.HTTP { @@ -360,13 +376,21 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(policy *egv1a1.Envo // targeting a lesser specific scope(Gateway). for _, r := range http.Routes { // if already set - there's a route level policy, so skip + if r.ExtProcs != nil || + r.Wasms != nil { + continue + } + if r.ExtProcs == nil { r.ExtProcs = extProcs } + if r.Wasms == nil { + r.Wasms = wasms + } } } - return nil + return errs } func (t *Translator) buildExtProc( @@ -428,3 +452,57 @@ func irConfigNameForEEP(policy *egv1a1.EnvoyExtensionPolicy, idx int) string { utils.NamespacedName(policy).String(), idx) } + +func (t *Translator) buildWasms(policy *egv1a1.EnvoyExtensionPolicy) ([]ir.Wasm, error) { + var wasmIRList []ir.Wasm + + if policy == nil { + return nil, nil + } + + for idx, wasm := range policy.Spec.Wasm { + name := irConfigNameForEEP(policy, idx) + wasmIR, err := t.buildWasm(name, wasm) + if err != nil { + return nil, err + } + wasmIRList = append(wasmIRList, *wasmIR) + } + return wasmIRList, nil +} + +func (t *Translator) buildWasm(name string, wasm egv1a1.Wasm) (*ir.Wasm, error) { + var ( + failOpen = false + httpWasmCode *ir.HTTPWasmCode + ) + + if wasm.FailOpen != nil { + failOpen = *wasm.FailOpen + } + + switch wasm.Code.Type { + case egv1a1.HTTPWasmCodeSourceType: + httpWasmCode = &ir.HTTPWasmCode{ + URL: wasm.Code.HTTP.URL, + SHA256: wasm.Code.SHA256, + } + case egv1a1.ConfigMapWasmCodeSourceType: + return nil, fmt.Errorf("ConfigMap Wasm code source is not supported yet") + case egv1a1.ImageWasmCodeSourceType: + return nil, fmt.Errorf("OCI image Wasm code source is not supported yet") + default: + // should never happen because of kubebuilder validation, just a sanity check + return nil, fmt.Errorf("unsupported Wasm code source type %q", wasm.Code.Type) + } + + wasmIR := &ir.Wasm{ + Name: name, + WasmName: wasm.Name, + Config: wasm.Config, + FailOpen: failOpen, + HTTPWasmCode: httpWasmCode, + } + + return wasmIR, nil +} diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.in.yaml new file mode 100644 index 000000000000..2de252e44f7d --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.in.yaml @@ -0,0 +1,112 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 +envoyextensionpolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway # This policy should attach httproute-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + wasm: + - name: wasm-filter-1 + code: + type: HTTP + http: + url: https://www.example.com/wasm-filter-1.wasm + sha256: 746df05c8f3a0b07a46c0967cfbc5cbe5b9d48d0f79b6177eeedf8be6c8b34b5 + config: + parameter1: + key1: value1 + key2: value2 + parameter2: value3 + - name: wasm-filter-2 + code: + type: HTTP + http: + url: https://www.example.com/wasm-filter-2.wasm + sha256: a1efca12ea51069abb123bf9c77889fcc2a31cc5483fc14d115e44fdf07c7980 + config: + parameter1: value1 + parameter2: value2 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-http-route # This policy should attach httproute-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + wasm: + - name: wasm-filter-3 + code: + type: HTTP + http: + url: https://www.test.com/wasm-filter-3.wasm + sha256: a1f0b78b8c1320690327800e3a5de10e7dbba7b6c752e702193a395a52c727b6 + config: + parameter1: + key1: value1 + parameter2: + key2: + key3: value3 + failOpen: true diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml new file mode 100755 index 000000000000..064659f15447 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml @@ -0,0 +1,317 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route + namespace: default + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + wasm: + - code: + http: + url: https://www.test.com/wasm-filter-3.wasm + sha256: a1f0b78b8c1320690327800e3a5de10e7dbba7b6c752e702193a395a52c727b6 + type: HTTP + config: + parameter1: + key1: value1 + parameter2: + key2: + key3: value3 + failOpen: true + name: wasm-filter-3 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + wasm: + - code: + http: + url: https://www.example.com/wasm-filter-1.wasm + sha256: 746df05c8f3a0b07a46c0967cfbc5cbe5b9d48d0f79b6177eeedf8be6c8b34b5 + type: HTTP + config: + parameter1: + key1: value1 + key2: value2 + parameter2: value3 + name: wasm-filter-1 + - code: + http: + url: https://www.example.com/wasm-filter-2.wasm + sha256: a1efca12ea51069abb123bf9c77889fcc2a31cc5483fc14d115e44fdf07c7980 + type: HTTP + config: + parameter1: value1 + parameter2: value2 + name: wasm-filter-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being overridden by other envoyExtensionPolicies + for these routes: [default/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + 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: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + 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: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + 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 + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + wasm: + - config: + parameter1: + key1: value1 + parameter2: + key2: + key3: value3 + failOpen: true + httpWasmCode: + sha256: a1f0b78b8c1320690327800e3a5de10e7dbba7b6c752e702193a395a52c727b6 + url: https://www.test.com/wasm-filter-3.wasm + name: envoyextensionpolicy/default/policy-for-http-route/0 + wasmName: wasm-filter-3 + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar + wasm: + - config: + parameter1: + key1: value1 + key2: value2 + parameter2: value3 + failOpen: false + httpWasmCode: + sha256: 746df05c8f3a0b07a46c0967cfbc5cbe5b9d48d0f79b6177eeedf8be6c8b34b5 + url: https://www.example.com/wasm-filter-1.wasm + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/0 + wasmName: wasm-filter-1 + - config: + parameter1: value1 + parameter2: value2 + failOpen: false + httpWasmCode: + sha256: a1efca12ea51069abb123bf9c77889fcc2a31cc5483fc14d115e44fdf07c7980 + url: https://www.example.com/wasm-filter-2.wasm + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/1 + wasmName: wasm-filter-2 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index abe5d59b6b99..174097f2219b 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -495,6 +495,8 @@ type HTTPRoute struct { Retry *Retry `json:"retry,omitempty" yaml:"retry,omitempty"` // External Processing extensions ExtProcs []ExtProc `json:"extProc,omitempty" yaml:"extProc,omitempty"` + // Wasm extensions + Wasms []Wasm `json:"wasm,omitempty" yaml:"wasm,omitempty"` } // UnstructuredRef holds unstructured data for an arbitrary k8s resource introduced by an extension @@ -1854,3 +1856,36 @@ type ExtProc struct { // Authority is the hostname:port of the HTTP External Processing service. Authority string `json:"authority"` } + +// Wasm holds the information associated with the Wasm extensions. +// +k8s:deepcopy-gen=true +type Wasm struct { + // Name is a unique name for an Wasm configuration. + // The xds translator only generates one ExtProc filter for each unique name. + Name string `json:"name"` + + // WasmName is used to identify the Wasm extension if multiple extensions are + // handled by the same vm_id and root_id. + // It's also used for logging/debugging. + WasmName string `json:"wasmName"` + + // Config is the configuration for the Wasm extension. + // This configuration will be passed as a JSON string to the Wasm extension. + Config *apiextensionsv1.JSON `json:"config"` + + // FailOpen is a switch used to control the behavior when a fatal error occurs + // during the initialization or the execution of the Wasm extension. + FailOpen bool `json:"failOpen"` + + // HTTPWasmCode is the HTTP Wasm code source. + HTTPWasmCode *HTTPWasmCode `json:"httpWasmCode,omitempty"` +} + +// HTTPWasmCode holds the information associated with the HTTP Wasm code source. +type HTTPWasmCode struct { + // URL is the URL of the Wasm code. + URL string `json:"url"` + + // SHA256 checksum that will be used to verify the wasm code. + SHA256 string `json:"sha256"` +} diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 34d00d3eb486..4d14337c7603 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1049,6 +1049,13 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Wasms != nil { + in, out := &in.Wasms, &out.Wasms + *out = make([]Wasm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. @@ -2368,6 +2375,31 @@ func (in *UnstructuredRef) DeepCopy() *UnstructuredRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Wasm) DeepCopyInto(out *Wasm) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } + if in.HTTPWasmCode != nil { + in, out := &in.HTTPWasmCode, &out.HTTPWasmCode + *out = new(HTTPWasmCode) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wasm. +func (in *Wasm) DeepCopy() *Wasm { + if in == nil { + return nil + } + out := new(Wasm) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Xds) DeepCopyInto(out *Xds) { *out = *in diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index 19dd84807535..ac03a49d08e1 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -32,9 +32,9 @@ type extProc struct { var _ httpFilter = &extProc{} -// patchHCM builds and appends the ext_authz Filters to the HTTP Connection Manager +// patchHCM builds and appends the ext_proc Filters to the HTTP Connection Manager // if applicable, and it does not already exist. -// Note: this method creates an ext_authz filter for each route that contains an ExtAuthz config. +// Note: this method creates an ext_proc filter for each route that contains an ExtAuthz config. // The filter is disabled by default. It is enabled on the route level. func (*extProc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { var errs error @@ -70,7 +70,7 @@ func (*extProc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPLi return errs } -// buildHCMExtProcFilter returns an ext_authp HTTP filter from the provided IR HTTPRoute. +// buildHCMExtProcFilter returns an ext_proc HTTP filter from the provided IR HTTPRoute. func buildHCMExtProcFilter(extProc ir.ExtProc) (*hcmv3.HttpFilter, error) { extAuthProto := extProcConfig(extProc) if err := extAuthProto.ValidateAll(); err != nil { @@ -128,7 +128,7 @@ func routeContainsExtProc(irRoute *ir.HTTPRoute) bool { return len(irRoute.ExtProcs) > 0 } -// patchResources patches the cluster resources for the external auth services. +// patchResources patches the cluster resources for the external services. func (*extProc) patchResources(tCtx *types.ResourceVersionTable, routes []*ir.HTTPRoute) error { if tCtx == nil || tCtx.XdsResources == nil { diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go index 72e8029f1c4c..14900315e117 100644 --- a/internal/xds/translator/jwt.go +++ b/internal/xds/translator/jwt.go @@ -255,47 +255,16 @@ func (*jwt) patchResources(tCtx *types.ResourceVersionTable, routes []*ir.HTTPRo return errors.New("xds resource table is nil") } - var errs error + var err, errs error for _, route := range routes { if !routeContainsJWTAuthn(route) { continue } for i := range route.JWT.Providers { - var ( - jwks *urlCluster - ds *ir.DestinationSetting - tSocket *corev3.TransportSocket - err error - ) - provider := route.JWT.Providers[i] - jwks, err = url2Cluster(provider.RemoteJWKS.URI) - if err != nil { - errs = errors.Join(errs, err) - continue - } - - ds = &ir.DestinationSetting{ - Weight: ptr.To[uint32](1), - Endpoints: []*ir.DestinationEndpoint{ir.NewDestEndpoint(jwks.hostname, jwks.port)}, - } - - clusterArgs := &xdsClusterArgs{ - name: jwks.name, - settings: []*ir.DestinationSetting{ds}, - endpointType: jwks.endpointType, - } - if jwks.tls { - tSocket, err = buildXdsUpstreamTLSSocket(jwks.hostname) - if err != nil { - errs = errors.Join(errs, err) - continue - } - clusterArgs.tSocket = tSocket - } - if err = addXdsCluster(tCtx, clusterArgs); err != nil && !errors.Is(err, ErrXdsClusterExists) { + if err = addClusterFromURL(provider.RemoteJWKS.URI, tCtx); err != nil { errs = errors.Join(errs, err) } } diff --git a/internal/xds/translator/testdata/in/xds-ir/wasm.yaml b/internal/xds/translator/testdata/in/xds-ir/wasm.yaml new file mode 100644 index 000000000000..9e1ede8768bf --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/wasm.yaml @@ -0,0 +1,83 @@ +http: +- address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + wasm: + - config: + parameter1: + key1: value1 + parameter2: + key2: + key3: value3 + failOpen: true + httpWasmCode: + sha256: a1f0b78b8c1320690327800e3a5de10e7dbba7b6c752e702193a395a52c727b6 + url: https://www.test.com/wasm-filter-3.wasm + name: envoyextensionpolicy/default/policy-for-http-route/0 + wasmName: wasm-filter-3 + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar + wasm: + - config: + parameter1: + key1: value1 + key2: value2 + parameter2: value3 + failOpen: false + httpWasmCode: + sha256: 746df05c8f3a0b07a46c0967cfbc5cbe5b9d48d0f79b6177eeedf8be6c8b34b5 + url: https://www.example.com/wasm-filter-1.wasm + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/0 + wasmName: wasm-filter-1 + - config: + parameter1: value1 + parameter2: value2 + failOpen: false + httpWasmCode: + sha256: a1efca12ea51069abb123bf9c77889fcc2a31cc5483fc14d115e44fdf07c7980 + url: https://www.example.com/wasm-filter-2.wasm + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/1 + wasmName: wasm-filter-2 diff --git a/internal/xds/translator/testdata/out/xds-ir/wasm.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/wasm.clusters.yaml new file mode 100755 index 000000000000..a70d771a1db9 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/wasm.clusters.yaml @@ -0,0 +1,106 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-1/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-1/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-2/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-2/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + dnsRefreshRate: 30s + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: www_test_com_443 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: www.test.com + portValue: 443 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: www_test_com_443/backend/0 + name: www_test_com_443 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + validationContext: + trustedCa: + filename: /etc/ssl/certs/ca-certificates.crt + sni: www.test.com + type: STRICT_DNS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + dnsRefreshRate: 30s + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: www_example_com_443 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: www.example.com + portValue: 443 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: www_example_com_443/backend/0 + name: www_example_com_443 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + validationContext: + trustedCa: + filename: /etc/ssl/certs/ca-certificates.crt + sni: www.example.com + type: STRICT_DNS diff --git a/internal/xds/translator/testdata/out/xds-ir/wasm.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/wasm.endpoints.yaml new file mode 100755 index 000000000000..05442a9a15b5 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/wasm.endpoints.yaml @@ -0,0 +1,24 @@ +- clusterName: httproute/default/httproute-1/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-1/rule/0/backend/0 +- clusterName: httproute/default/httproute-2/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-2/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/wasm.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/wasm.listeners.yaml new file mode 100755 index 000000000000..a21fa99c9607 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/wasm.listeners.yaml @@ -0,0 +1,92 @@ +- 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: + - disabled: true + name: envoy.filters.http.wasm/envoyextensionpolicy/default/policy-for-http-route/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + configuration: + '@type': type.googleapis.com/google.protobuf.StringValue + value: '{"parameter1":{"key1":"value1"},"parameter2":{"key2":{"key3":"value3"}}}' + failOpen: true + name: wasm-filter-3 + vmConfig: + code: + remote: + httpUri: + cluster: www_test_com_443 + timeout: 10s + uri: https://www.test.com/wasm-filter-3.wasm + sha256: a1f0b78b8c1320690327800e3a5de10e7dbba7b6c752e702193a395a52c727b6 + runtime: envoy.wasm.runtime.v8 + vmId: envoyextensionpolicy/default/policy-for-http-route/0 + - disabled: true + name: envoy.filters.http.wasm/envoyextensionpolicy/envoy-gateway/policy-for-gateway/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + configuration: + '@type': type.googleapis.com/google.protobuf.StringValue + value: '{"parameter1":{"key1":"value1","key2":"value2"},"parameter2":"value3"}' + name: wasm-filter-1 + vmConfig: + code: + remote: + httpUri: + cluster: www_example_com_443 + timeout: 10s + uri: https://www.example.com/wasm-filter-1.wasm + sha256: 746df05c8f3a0b07a46c0967cfbc5cbe5b9d48d0f79b6177eeedf8be6c8b34b5 + runtime: envoy.wasm.runtime.v8 + vmId: envoyextensionpolicy/envoy-gateway/policy-for-gateway/0 + - disabled: true + name: envoy.filters.http.wasm/envoyextensionpolicy/envoy-gateway/policy-for-gateway/1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + configuration: + '@type': type.googleapis.com/google.protobuf.StringValue + value: '{"parameter1":"value1","parameter2":"value2"}' + name: wasm-filter-2 + vmConfig: + code: + remote: + httpUri: + cluster: www_example_com_443 + timeout: 10s + uri: https://www.example.com/wasm-filter-2.wasm + sha256: a1efca12ea51069abb123bf9c77889fcc2a31cc5483fc14d115e44fdf07c7980 + runtime: envoy.wasm.runtime.v8 + vmId: envoyextensionpolicy/envoy-gateway/policy-for-gateway/1 + - 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/http + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + drainType: MODIFY_ONLY + name: envoy-gateway/gateway-1/http + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/wasm.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/wasm.routes.yaml new file mode 100755 index 000000000000..8fb6f03a0f0f --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/wasm.routes.yaml @@ -0,0 +1,32 @@ +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-1/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.wasm/envoyextensionpolicy/default/policy-for-http-route/0: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} + - match: + pathSeparatedPrefix: /bar + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-2/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.wasm/envoyextensionpolicy/envoy-gateway/policy-for-gateway/0: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} + envoy.filters.http.wasm/envoyextensionpolicy/envoy-gateway/policy-for-gateway/1: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 367e77412770..82c6da51d3e6 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -310,6 +310,9 @@ func TestTranslateXds(t *testing.T) { { name: "ext-proc", }, + { + name: "wasm", + }, } for _, tc := range testCases { diff --git a/internal/xds/translator/utils.go b/internal/xds/translator/utils.go index e3002d9a8a3f..f4d24c9965db 100644 --- a/internal/xds/translator/utils.go +++ b/internal/xds/translator/utils.go @@ -13,6 +13,8 @@ import ( "strconv" "strings" + "k8s.io/utils/ptr" + "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/xds/types" @@ -161,3 +163,39 @@ func createExtServiceXDSCluster(rd *ir.RouteDestination, tCtx *types.ResourceVer } return nil } + +// addClusterFromURL adds a cluster to the resource version table from the provided URL. +func addClusterFromURL(url string, tCtx *types.ResourceVersionTable) error { + var ( + uc *urlCluster + ds *ir.DestinationSetting + tSocket *corev3.TransportSocket + err error + ) + + if uc, err = url2Cluster(url); err != nil { + return err + } + + ds = &ir.DestinationSetting{ + Weight: ptr.To[uint32](1), + Endpoints: []*ir.DestinationEndpoint{ir.NewDestEndpoint(uc.hostname, uc.port)}, + } + + clusterArgs := &xdsClusterArgs{ + name: uc.name, + settings: []*ir.DestinationSetting{ds}, + endpointType: uc.endpointType, + } + if uc.tls { + if tSocket, err = buildXdsUpstreamTLSSocket(uc.hostname); err != nil { + return err + } + clusterArgs.tSocket = tSocket + } + + if err = addXdsCluster(tCtx, clusterArgs); err != nil && !errors.Is(err, ErrXdsClusterExists) { + return err + } + return nil +} diff --git a/internal/xds/translator/wasm.go b/internal/xds/translator/wasm.go new file mode 100644 index 000000000000..02ab0cfc5709 --- /dev/null +++ b/internal/xds/translator/wasm.go @@ -0,0 +1,200 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "errors" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + wasmfilterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3" + hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + wasmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/wasm/v3" + "github.com/golang/protobuf/ptypes/duration" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/xds/types" +) + +const ( + wasmFilter = "envoy.filters.http.wasm" + vmRuntimeV8 = "envoy.wasm.runtime.v8" +) + +func init() { + registerHTTPFilter(&wasm{}) +} + +type wasm struct { +} + +var _ httpFilter = &wasm{} + +// patchHCM builds and appends the wasm Filters to the HTTP Connection Manager +// if applicable, and it does not already exist. +// Note: this method creates a wasm filter for each route that contains an wasm config. +// The filter is disabled by default. It is enabled on the route level. +func (*wasm) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { + var errs error + + if mgr == nil { + return errors.New("hcm is nil") + } + if irListener == nil { + return errors.New("ir listener is nil") + } + + for _, route := range irListener.Routes { + if !routeContainsWasm(route) { + continue + } + for _, ep := range route.Wasms { + if hcmContainsFilter(mgr, wasmFilterName(ep)) { + continue + } + filter, err := buildHCMWasmFilter(ep) + if err != nil { + errs = errors.Join(errs, err) + continue + } + mgr.HttpFilters = append(mgr.HttpFilters, filter) + } + } + + return errs +} + +// buildHCMWasmFilter returns a wasm HTTP filter from the provided IR HTTPRoute. +func buildHCMWasmFilter(wasm ir.Wasm) (*hcmv3.HttpFilter, error) { + var ( + wasmProto *wasmfilterv3.Wasm + wasmAny *anypb.Any + err error + ) + + if wasmProto, err = wasmConfig(wasm); err != nil { + return nil, err + } + if err = wasmProto.ValidateAll(); err != nil { + return nil, err + } + if wasmAny, err = anypb.New(wasmProto); err != nil { + return nil, err + } + + // All wasm filters for all Routes are aggregated on HCM and disabled by default + // Per-route config is used to enable the relevant filters on appropriate routes + return &hcmv3.HttpFilter{ + Name: wasmFilterName(wasm), + Disabled: true, + ConfigType: &hcmv3.HttpFilter_TypedConfig{ + TypedConfig: wasmAny, + }, + }, nil +} + +func wasmFilterName(wasm ir.Wasm) string { + return perRouteFilterName(wasmFilter, wasm.Name) +} + +func wasmConfig(wasm ir.Wasm) (*wasmfilterv3.Wasm, error) { + var ( + uc *urlCluster + configAny *anypb.Any + err error + ) + + if uc, err = url2Cluster(wasm.HTTPWasmCode.URL); err != nil { + return nil, err + } + configAny, err = anypb.New(wrapperspb.String(string(wasm.Config.Raw))) + if err != nil { + return nil, err + } + + return &wasmfilterv3.Wasm{ + Config: &wasmv3.PluginConfig{ + Name: wasm.WasmName, + Vm: &wasmv3.PluginConfig_VmConfig{ + VmConfig: &wasmv3.VmConfig{ + VmId: wasm.Name, // Do not share VMs across different filters + Runtime: vmRuntimeV8, + Code: &corev3.AsyncDataSource{ + Specifier: &corev3.AsyncDataSource_Remote{ + Remote: &corev3.RemoteDataSource{ + HttpUri: &corev3.HttpUri{ + Uri: wasm.HTTPWasmCode.URL, + HttpUpstreamType: &corev3.HttpUri_Cluster{ + Cluster: uc.name, + }, + Timeout: &duration.Duration{ + Seconds: defaultExtServiceRequestTimeout, + }, + }, + Sha256: wasm.HTTPWasmCode.SHA256, + }, + }, + }, + }, + }, + Configuration: configAny, + FailOpen: wasm.FailOpen, + }, + }, nil +} + +// routeContainsWasm returns true if Wasms exists for the provided route. +func routeContainsWasm(irRoute *ir.HTTPRoute) bool { + if irRoute == nil { + return false + } + + return len(irRoute.Wasms) > 0 +} + +// patchResources patches the cluster resources for the http wasm code source. +func (*wasm) patchResources(tCtx *types.ResourceVersionTable, + routes []*ir.HTTPRoute) error { + if tCtx == nil || tCtx.XdsResources == nil { + return errors.New("xds resource table is nil") + } + + var err, errs error + for _, route := range routes { + if !routeContainsWasm(route) { + continue + } + + for _, w := range route.Wasms { + if err = addClusterFromURL(w.HTTPWasmCode.URL, tCtx); err != nil { + errs = errors.Join(errs, err) + } + } + } + + return errs +} + +// patchRoute patches the provided route with the wasm config if applicable. +// Note: this method enables the corresponding wasm filter for the provided route. +func (*wasm) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { + if route == nil { + return errors.New("xds route is nil") + } + if irRoute == nil { + return errors.New("ir route is nil") + } + + for _, ep := range irRoute.Wasms { + filterName := wasmFilterName(ep) + if err := enableFilterOnRoute(route, filterName); err != nil { + return err + } + } + return nil +} diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index c3316f670265..da137caf03f3 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -542,7 +542,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `targetRef` | _[PolicyTargetReferenceWithSectionName](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.PolicyTargetReferenceWithSectionName)_ | true | TargetRef is the name of the Gateway resource this policy
is being attached to.
This Policy and the TargetRef MUST be in the same namespace
for this Policy to have effect and be applied to the Gateway.
TargetRef | -| `wasm` | _[Wasm](#wasm) array_ | false | WASM is a list of Wasm extensions to be loaded by the Gateway.
Order matters, as the extensions will be loaded in the order they are
defined in this list. | +| `wasm` | _[Wasm](#wasm) array_ | false | Wasm is a list of Wasm extensions to be loaded by the Gateway.
Order matters, as the extensions will be loaded in the order they are
defined in this list. | | `extProc` | _[ExtProc](#extproc) array_ | true | ExtProc is an ordered list of external processing filters
that should added to the envoy filter chain | @@ -2796,6 +2796,7 @@ _Appears in:_ | `type` | _[WasmCodeSourceType](#wasmcodesourcetype)_ | true | Type is the type of the source of the wasm code.
Valid WasmCodeSourceType values are "HTTP" or "Image". | | `http` | _[HTTPWasmCodeSource](#httpwasmcodesource)_ | false | HTTP is the HTTP URL containing the wasm code.

Note that the HTTP server must be accessible from the Envoy proxy. | | `image` | _[ImageWasmCodeSource](#imagewasmcodesource)_ | false | Image is the OCI image containing the wasm code.

Note that the image must be accessible from the Envoy Gateway. | +| `sha256` | _string_ | true | SHA256 checksum that will be used to verify the wasm code.

kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` | #### WasmCodeSourceType