Envoy Gateway supports two types of Wasm extensions within the EnvoyExtensionPolicy API: HTTP Wasm Extensions and Image Wasm Extensions.
Packaging a Wasm extension as an OCI image is beneficial because it simplifies versioning and distribution for users.
Additionally, users can leverage existing image toolchain to build and manage Wasm images.
This document describes how to build OCI images which are consumable by Envoy Gateway.
Wasm Image Formats
There are two types of images that are supported by Envoy Gateway. One is in the Docker format, and another is the standard
OCI specification compliant format. Please note that both of them are supported by any OCI registries. You can choose
either format depending on your preference, and both types of images are consumable by Envoy Gateway EnvoyExtensionPolicy API.
Build Wasm Docker image
We assume that you have a valid Wasm binary named plugin.wasm. Then you can build a Wasm Docker image with the Docker CLI.
First, we prepare the following Dockerfile:
$ cat Dockerfile
FROM scratch
COPY plugin.wasm ./
Note: you must have exactly one COPY instruction in the Dockerfile in order to end up having only one layer in produced images.
Then, build your image via docker build command
$ docker build . -t my-registry/mywasm:0.1.0
Finally, push the image to your registry via docker push command
$ docker push my-registry/mywasm:0.1.0
Build Wasm OCI image
We assume that you have a valid Wasm binary named plugin.wasm, and you have buildah installed on your machine.
Then you can build a Wasm OCI image with the buildah CLI.
First, we create a working container from scratch base image with buildah from command.
$ buildah --name mywasm from scratch
mywasm
Then copy the Wasm binary into that base image by buildah copy command to create the layer.
This task explains the usage of the EnvoyPatchPolicy API.
Note: This API is meant for users extremely familiar with Envoy xDS semantics.
Also before considering this API for production use cases, please be aware that this API
is unstable and the outcome may change across versions. Use at your own risk.
Introduction
The EnvoyPatchPolicy API allows user to modify the output xDS
configuration generated by Envoy Gateway intended for EnvoyProxy,
using JSON Patch semantics.
Motivation
This API was introduced to allow advanced users to be able to leverage Envoy Proxy functionality
not exposed by Envoy Gateway APIs today.
Quickstart
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before
proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
The default installation of Envoy Gateway installs a default EnvoyGateway configuration and attaches it
using a ConfigMap. In the next step, we will update this resource to enable EnvoyPatchPolicy.
After updating the ConfigMap, you will need to wait the configuration kicks in. You can force the configuration to be reloaded by restarting the envoy-gateway deployment.
Starting from v1.5, Envoy Gateway uses version 2 of the xDS name scheme when generating xDS resources.
Because EnvoyPatchPolicy relies on specific xDS resource names, it’s important to use the correct naming format when authoring a patch policy.
Use EnvoyProxy’s Local Reply Modification feature to return a custom response back to the client when
the status code is 404
Apply the configuration
cat <<EOF | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyPatchPolicy
metadata:
name: custom-response-patch-policy
namespace: default
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
type: JSONPatch
jsonPatches:
- type: "type.googleapis.com/envoy.config.listener.v3.Listener"
# The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>
name: default/eg/http
operation:
op: add
path: "/default_filter_chain/filters/0/typed_config/local_reply_config"
value:
mappers:
- filter:
status_code_filter:
comparison:
op: EQ
value:
default_value: 404
runtime_key: key_b
status_code: 406
body:
inline_string: "could not find what you are looking for"
EOF
Save and apply the following resource to your cluster:
---apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyPatchPolicymetadata:name:custom-response-patch-policynamespace:defaultspec:targetRef:group:gateway.networking.k8s.iokind:Gatewayname:egtype:JSONPatchjsonPatches:- type:"type.googleapis.com/envoy.config.listener.v3.Listener"# The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>name:default/eg/httpoperation:op:addpath:"/default_filter_chain/filters/0/typed_config/local_reply_config"value:mappers:- filter:status_code_filter:comparison:op:EQvalue:default_value:404runtime_key:key_bstatus_code:406body:inline_string:"could not find what you are looking for"
When mergeGateways is enabled, there will be one Envoy deployment for all Gateways in the cluster.
Then the EnvoyPatchPolicy should target a specific GatewayClass.
cat <<EOF | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyPatchPolicy
metadata:
name: custom-response-patch-policy
namespace: default
spec:
targetRef:
group: gateway.networking.k8s.io
kind: GatewayClass
name: eg
type: JSONPatch
jsonPatches:
- type: "type.googleapis.com/envoy.config.listener.v3.Listener"
# The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>
name: default/eg/http
operation:
op: add
path: "/default_filter_chain/filters/0/typed_config/local_reply_config"
value:
mappers:
- filter:
status_code_filter:
comparison:
op: EQ
value:
default_value: 404
runtime_key: key_b
status_code: 406
body:
inline_string: "could not find what you are looking for"
EOF
Save and apply the following resource to your cluster:
---apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyPatchPolicymetadata:name:custom-response-patch-policynamespace:defaultspec:targetRef:group:gateway.networking.k8s.iokind:GatewayClassname:egtype:JSONPatchjsonPatches:- type:"type.googleapis.com/envoy.config.listener.v3.Listener"# The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>name:default/eg/httpoperation:op:addpath:"/default_filter_chain/filters/0/typed_config/local_reply_config"value:mappers:- filter:status_code_filter:comparison:op:EQvalue:default_value:404runtime_key:key_bstatus_code:406body:inline_string:"could not find what you are looking for"
Edit the HTTPRoute resource from the Quickstart to only match on paths with value /get
$ curl --header "Host: www.example.com" http://$GATEWAY_HOST/find
Handling connection for8888could not find what you are looking for
Customize VirtualHost by name
Use EnvoyProxy’s include_attempt_count_in_response feature to include the attempt count as header in the downstream response.
Apply the configuration
cat <<EOF | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyPatchPolicy
metadata:
name: include-attempts
namespace: default
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
type: JSONPatch
jsonPatches:
- type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
# The RouteConfiguration name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>
name: default/eg/http
operation:
op: add
# Every virtual_host that ends with 'www_example_com' (using RegEx Filter)
jsonPath: "..virtual_hosts[?match(@.name, '.*www_example_com')]"
# If the property does not exists, it can not be selected with jsonPath
# Therefore the new property must be set in path
path: "include_attempt_count_in_response"
value: true
EOF
Save and apply the following resource to your cluster:
---apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyPatchPolicymetadata:name:include-attemptsnamespace:defaultspec:targetRef:group:gateway.networking.k8s.iokind:Gatewayname:egtype:JSONPatchjsonPatches:- type:"type.googleapis.com/envoy.config.route.v3.RouteConfiguration"# The RouteConfiguration name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>name:default/eg/httpoperation:op:add# Every virtual_host that ends with 'www_example_com' (using RegEx Filter)jsonPath:"..virtual_hosts[?match(@.name, '.*www_example_com')]"# If the property does not exists, it can not be selected with jsonPath# Therefore the new property must be set in pathpath:"include_attempt_count_in_response"value:true
The Status subresource should have information about the status of the resource. Make sure
Accepted=True and Programmed=True conditions are set to ensure that the policy has been
applied to Envoy Proxy.
apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyPatchPolicymetadata:annotations:kubectl.kubernetes.io/last-applied-configuration:| {"apiVersion":"gateway.envoyproxy.io/v1alpha1","kind":"EnvoyPatchPolicy","metadata":{"annotations":{},"name":"custom-response-patch-policy","namespace":"default"},"spec":{"jsonPatches":[{"name":"default/eg/http","operation":{"op":"add","path":"/default_filter_chain/filters/0/typed_config/local_reply_config","value":{"mappers":[{"body":{"inline_string":"could not find what you are looking for"},"filter":{"status_code_filter":{"comparison":{"op":"EQ","value":{"default_value":404}}}}}]}},"type":"type.googleapis.com/envoy.config.listener.v3.Listener"}],"priority":0,"targetRef":{"group":"gateway.networking.k8s.io","kind":"Gateway","name":"eg","namespace":"default"},"type":"JSONPatch"}}creationTimestamp:"2023-07-31T21:47:53Z"generation:1name:custom-response-patch-policynamespace:defaultresourceVersion:"10265"uid:a35bda6e-a0cc-46d7-a63a-cee765174bc3spec:jsonPatches:- name:default/eg/httpoperation:op:addpath:/default_filter_chain/filters/0/typed_config/local_reply_configvalue:mappers:- body:inline_string:could not find what you are looking forfilter:status_code_filter:comparison:op:EQvalue:default_value:404type:type.googleapis.com/envoy.config.listener.v3.Listenerpriority:0targetRef:group:gateway.networking.k8s.iokind:Gatewayname:egtype:JSONPatchstatus:conditions:- lastTransitionTime:"2023-07-31T21:48:19Z"message:EnvoyPatchPolicy has been accepted.observedGeneration:1reason:Acceptedstatus:"True"type:Accepted- lastTransitionTime:"2023-07-31T21:48:19Z"message:successfully applied patches.reason:Programmedstatus:"True"type:Programmed
Offline
You can use egctl x translate to validate the translated xds output.
Caveats
This API will always be an unstable API and the same outcome cannot be guaranteed
across versions for these reasons
The Envoy Proxy API might deprecate and remove API fields
Envoy Gateway might alter the xDS translation creating a different xDS output
such as changing the name field of resources.
3 - Envoy Gateway Extension Server
This task explains how to extend Envoy Gateway using an Extension Server. Envoy Gateway
can be configured to call an external server over gRPC with the xDS configuration before
it is sent to Envoy Proxy. The external server can modify the provided configuration
programmatically using any semantics supported by the xDS API.
Using an extension server allows vendors to add xDS configuration that Envoy Gateway itself
doesn’t support with a very high level of control over the generated xDS configuration.
Note: Modifying the xDS configuration generated by Envoy Gateway may break functionality
configured by native Envoy Gateway means. Like other cases where the xDS configuration
is modified outside of Envoy Gateway’s control, this is risky and should be tested thoroughly,
especially when using the same extension server across different Envoy Gateway versions.
Introduction
One of the Envoy Gateway project goals is to “provide a common foundation for vendors to
build value-added products without having to re-engineer fundamental interactions”. The
Envoy Gateway Extension Server provides a mechanism where Envoy Gateway tracks all provider
resources and then calls a set of hooks that allow the generated xDS configuration to be
modified before it is sent to Envoy Proxy. See the design documentation for full details.
Extension Hooks Overview
Envoy Gateway provides several extension hooks that are called at different stages of the xDS translation process. These hooks allow extensions to modify various aspects of the generated xDS configuration:
Available Hooks
Hook Type
Hook Method Name
Purpose
Resources Modified
Execution Context
Route
PostRouteModifyHook
Modify individual routes
Single envoy.config.route.v3.Route
Per-route, only for routes with extension filters
Cluster
PostClusterModifyHook
Modify clusters for custom backends
Single envoy.config.cluster.v3.Cluster
Per-cluster, only for custom backend clusters
VirtualHost
PostVirtualHostModifyHook
Modify virtual hosts and add custom routes
Single envoy.config.route.v3.VirtualHost
Per-virtual-host
HTTPListener
PostHTTPListenerModifyHook
Modify HTTP listeners
Single envoy.config.listener.v3.Listener
Per-listener
Translation
PostTranslateModifyHook
Global modification of all xDS resources
All clusters, secrets, listeners, and routes
Once per translation cycle
Hook Execution Order
The hooks are executed in the following order during xDS translation:
Route Processing Phase
Route hook (PostRouteModifyHook): Called for each route that has extension filters attached
Cluster hook (PostClusterModifyHook): Called for each cluster generated from custom backend references
Virtual Host Processing Phase
VirtualHost hook (PostVirtualHostModifyHook): Called for each virtual host after all routes are processed
Listener Processing Phase
HTTPListener hook (PostHTTPListenerModifyHook): Called for each HTTP listener after virtual hosts are configured
Final Translation Phase
Translation hook (PostTranslateModifyHook): Called once with all generated clusters, secrets, listeners, and routes
Hook Details
Route Hook (PostRouteModifyHook)
When called: After each individual route is generated from an HTTPRoute with extension filters
Input: Single route, hostnames, and extension resources from the HTTPRoute
Use cases: Add virtual host-level configuration, inject additional routes
HTTPListener Hook (PostHTTPListenerModifyHook)
When called: After listener is fully configured with all virtual hosts
Input: Complete HTTP listener and associated extension policies
Output: Modified listener
Use cases: Add listener filters, modify listener configuration, add authentication
Translation Hook (PostTranslateModifyHook)
When called: After all individual resources are generated and processed
Input: All clusters, secrets, listeners, and routes, plus extension policies
Output: Complete set of modified resources
Use cases: Global resource injection, cross-resource modifications, cleanup operations
Configuration
Extensions must specify which hooks they want to use in the extensionManager.hooks configuration:
extensionManager:hooks:xdsTranslator:post:- Route # Enable route modification hook- VirtualHost # Enable virtual host modification hook- HTTPListener # Enable HTTP listener modification hook- Cluster # Enable cluster modification hook- Translation # Enable global translation hook# Configure which resources to include in PostTranslateModifyHook# Default: true for clusters and secrets (for backward compatibility)# Default: false for listeners and routes (for backward compatibility)translation:listener:includeAll:trueroute:includeAll:true
This task sets up an example extension server that adds the Envoy Proxy Basic Authentication
HTTP filter to all the listeners generated by Envoy Gateway. The example extension server
includes its own CRD which allows defining username/password pairs that will be accepted by
the Envoy Proxy.
Note: Envoy Gateway supports adding Basic Authentication to routes using a SecurityPolicy.
See this task for the preferred way to configure Basic
Authentication.
Quickstart
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before
proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
The above command should succeed with status code 200.
Build and run the example Extension Server
Build and deploy the example extension server in the examples/extension-server folder into the cluster
running Envoy Gateway.
Build the extension server image
Note: The provided Makefile builds an image with the name extension-server:latest. You may need to create
a different tag for it in order to allow Kubernetes to pull it correctly.
make image
Publish the extension server image in your docker repository
Configure Envoy Gateway to use the Extension Server
Add the following fragment to Envoy Gateway’s configmap:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: envoy-gateway-config
namespace: envoy-gateway-system
data:
envoy-gateway.yaml: |
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
provider:
type: Kubernetes
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
extensionManager:
# Envoy Gateway will watch these resource kinds and use them as extension policies
# which can be attached to Gateway resources.
policyResources:
- group: example.extensions.io
version: v1alpha1
kind: ListenerContextExample
hooks:
# The type of hooks that should be invoked
xdsTranslator:
post:
- HTTPListener
service:
# The service that is hosting the extension server
fqdn:
hostname: extension-server.envoy-gateway-system.svc.cluster.local
port: 5005
EOF
After updating Envoy Gateway’s configmap, restart Envoy Gateway.
Testing
Get the Gateway’s address:
exportGATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
The extension server adds the Basic Authentication HTTP filter to all listeners configured by
Envoy Gateway. Initially there are no valid user/password combinations available. Accessing the
example backend should fail with a 401 status:
$ curl -v --header "Host: www.example.com""http://${GATEWAY_HOST}/example"...
> GET /example HTTP/1.1
> Host: www.example.com
> User-Agent: curl/7.81.0
> Accept: */*
>* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< www-authenticate: Basic realm="http://www.example.com/example"
< content-length: 58
< content-type: text/plain
< date: Mon, 08 Jul 2024 10:53:11 GMT
<
...
User authentication failed. Missing username and password.
...
Add a new Username/Password combination using the example extension server’s CRD:
This task provides instructions for configuring external processing.
External processing calls an external gRPC service to process HTTP requests and responses.
The external processing service can inspect and mutate requests and responses.
Envoy Gateway introduces a new CRD called EnvoyExtensionPolicy that allows the user to configure external processing.
This instantiated resource can be linked to a Gateway and HTTPRoute resource.
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before
proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
Create a new EnvoyExtensionPolicy resource to configure the external processing service. This EnvoyExtensionPolicy targets the HTTPRoute
“myApp” created in the previous step. It calls the GRPC external processing service “grpc-ext-proc” on port 9002 for
processing.
By default, requests and responses are not sent to the external processor. The processingMode struct is used to define what should be sent to the external processor.
In this example, we configure the following processing modes:
The empty request field configures envoy to send request headers to the external processor.
The response field includes configuration for body processing. As a result, response headers are sent to the external processor. Additionally, the response body is streamed to the external processor.
kubectl get envoyextensionpolicy/ext-proc-example -o yaml
Because the gRPC external processing service is enabled with TLS, a BackendTLSPolicy needs to be created to configure
the communication between the Envoy proxy and the gRPC auth service.
Checkout the Developer Guide to get involved in the project.
5 - Lua Extensions
This task provides instructions for extending Envoy Gateway with Lua extensions.
Lua extensions allow you to extend the functionality of Envoy Gateway by running custom code against HTTP requests and responses,
without modifying the Envoy Gateway binary. These comparatively light-weight extensions are written in the Lua scripting language using APIs defined here.
Envoy Gateway allows the user to configure Lua extensions using the EnvoyExtensionPolicy CRD.
This instantiated resource can be linked to a Gateway or HTTPRoute resource. If linked to both, the resource linked to the route takes precedence over those linked to Gateway.
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before
proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
Inline Lua: The extension defines the Lua script as an inline string.
ValueRef Lua: The extension points to an in-cluster ConfigMap resource that contains the Lua script in it’s data.
The following example demonstrates how to configure an EnvoyExtensionPolicy to attach a Lua extension to a HTTPRoute.
This Lua extension adds a custom header x-lua-custom: FOO to the response.
Lua Extension - Inline
This EnvoyExtensionPolicy configuration defines the Lua script as an inline string.
Save and apply the following resource to your cluster:
---apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyExtensionPolicymetadata:name:lua-inline-testspec:targetRefs:- group:gateway.networking.k8s.iokind:HTTPRoutename:backendlua:- type:Inlineinline:| function envoy_on_response(response_handle)
response_handle:headers():add("x-lua-custom", "FOO")
end
Verify the EnvoyExtensionPolicy status:
kubectl get envoyextensionpolicy/lua-inline-test -o yaml
Lua Extension - ValueRef
This EnvoyExtensionPolicy configuration defines the Lua extension in a ConfigMap resource.
Save and apply the following resources to your cluster:
---apiVersion:gateway.envoyproxy.io/v1alpha1kind:EnvoyExtensionPolicymetadata:name:lua-valueref-testspec:targetRefs:- group:gateway.networking.k8s.iokind:HTTPRoutename:backendlua:- type:ValueRefvalueRef:name:cm-lua-valuerefkind:ConfigMapgroup:v1---apiVersion:v1kind:ConfigMapmetadata:name:cm-lua-valuerefdata:lua:| function envoy_on_response(response_handle)
response_handle:headers():add("x-lua-custom", "FOO")
end
Verify the EnvoyExtensionPolicy status:
kubectl get envoyextensionpolicy/lua-valueref-test -o yaml
Testing
Ensure the GATEWAY_HOST environment variable from the Quickstart is set. If not, follow the
Quickstart instructions to set the variable.
Checkout the Developer Guide to get involved in the project.
6 - Wasm Extensions
This task provides instructions for extending Envoy Gateway with WebAssembly (Wasm) extensions.
Wasm extensions allow you to extend the functionality of Envoy Gateway by running custom code against HTTP requests and responses,
without modifying the Envoy Gateway binary. These extensions can be written in any language that compiles to Wasm, such as C++, Rust, AssemblyScript, or TinyGo.
Envoy Gateway introduces a new CRD called EnvoyExtensionPolicy that allows the user to configure Wasm extensions.
This instantiated resource can be linked to a Gateway and HTTPRoute resource.
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before
proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
The above command should succeed with status code 200.
Configuration
Envoy Gateway supports two types of Wasm extensions:
HTTP Wasm Extension: The Wasm extension is fetched from a remote URL.
Image Wasm Extension: The Wasm extension is packaged as an OCI image and fetched from an image registry.
The following example demonstrates how to configure an EnvoyExtensionPolicy to attach a Wasm extension to an EnvoyExtensionPolicy .
This Wasm extension adds a custom header x-wasm-custom: FOO to the response.
HTTP Wasm Extension
This EnvoyExtensionPolicy configuration fetches the Wasm extension from an HTTP URL.