Service Mesh (ASM) supports the WebAssembly for Proxies specification, which lets you extend Envoy proxy behavior with portable plug-ins compiled to WebAssembly (Wasm). These plug-ins can run on various proxy servers. Wasm plug-ins run inside a memory-safe sandbox at near-native speed, and you can update them without restarting the proxy.
This topic walks through building a Go-based Wasm plug-in that enforces header-based authorization, packaging it as an Open Container Initiative (OCI) image, and deploying it to an ASM ingress gateway.
How Wasm plug-ins work in Envoy
Wasm plug-ins execute inside a sandboxed environment with a well-defined API for communicating with the host proxy. This architecture provides several benefits:
| Benefit | Description |
|---|---|
| Hot-reloadable | Update plug-ins without restarting the Envoy proxy, so traffic continues flowing |
| Fault-isolated | A plug-in crash does not bring the Envoy proxy down |
| Secure | The sandbox restricts plug-in capabilities to a well-defined API surface |
| Polyglot | Write plug-ins in C++, Go, and Rust |
For more details, see WebAssembly in Envoy and the proxy-wasm-go-sdk overview.
End-to-end workflow
The plug-in you build in this tutorial checks whether incoming requests contain the allow: true header. Requests without this header receive a 403 Forbidden response. Requests with the header pass through to the backend service.
Write plug-in logic in Go using the proxy-wasm-go-sdk.
Compile the Go code into a Wasm binary with TinyGo.
Package the binary as an OCI image and push it to Container Registry Enterprise Edition.
Create a
WasmPluginresource in ASM to apply the plug-in to an Envoy proxy.
Prerequisites
Before you begin, make sure that you have:
An ASM instance (v1.18 or later) with a cluster added. See Add a cluster to an ASM instance
Automatic sidecar proxy injection enabled. See Configure sidecar proxy injection policies
An ingress gateway deployed. See Create an ingress gateway
The HTTPBin application deployed and accessible. See Deploy the HTTPBin application
A Container Registry Enterprise Edition instance created (required for OCI image support). See Create a Container Registry Enterprise Edition instance
Step 1: Set up the development environment
Install the following tools on your local machine:
| Tool | Purpose | Reference |
|---|---|---|
| Go | Go compiler and toolchain | go.dev |
| Docker | Build and push OCI images | docker.com |
| TinyGo | Compile Go to Wasm (the standard Go compiler does not support Wasm output) | TinyGo install guide |
The plug-in depends on the proxy-wasm-go-sdk, which contains the full Go SDK API and additional examples.
Step 2: Write the plug-in code
Create a project directory and add a
main.gofile with the following content:Initialize the Go module and download dependencies:
go mod init go mod tidyCompile the code into a Wasm binary: This produces a
plugin.wasmfile in the current directory.tinygo build -o plugin.wasm -scheduler=none -target=wasi main.go
Step 3: Package and push the OCI image
In the same project directory, create a
Dockerfile:FROM scratch ADD ./plugin.wasm ./plugin.wasmBuild the image:
docker build -t header-authorization:v0.0.1 .Create an image repository in Container Registry Enterprise Edition. For detailed steps, see substeps 2.a and 2.b of Step 1 in Use the Coraza Wasm plug-in to implement WAF capabilities on an ASM gateway. In this example, the namespace is
test-ociand the repository name isheader-authorization.
Tag and push the image to your Container Registry Enterprise Edition instance. Follow the Push image to the registry instructions shown in the repository details page.
Step 4: Apply the Wasm plug-in to the ingress gateway
Create a Secret for image pulling. For background, see Step 2: Configure permissions to pull images. Replace the following placeholders with your actual values:
Placeholder Description <your-registry-domain>Domain name of the Container Registry Enterprise Edition instance <your-username>Registry username <your-password>Registry password kubectl create secret docker-registry -n istio-system wasm-secret \ --docker-server=<your-registry-domain> \ --docker-username=<your-username> \ --docker-password=<your-password>Create a file named
asm-plugin.yamlwith the following content: Replace<your-registry-domain>with the domain name of your Container Registry Enterprise Edition instance.apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: header-authorization namespace: istio-system spec: imagePullPolicy: IfNotPresent imagePullSecret: wasm-secret selector: matchLabels: istio: ingressgateway url: oci://<your-registry-domain>/test-oci/header-authorization:v0.0.1 phase: AUTHNConnect to the ASM instance using its kubeconfig file and apply the resource:
kubectl apply -f asm-plugin.yaml
Step 5: Verify the plug-in
Enable debug logging for the Wasm plug-in on the ingress gateway. Use the kubeconfig file of the data-plane cluster where the ingress gateway runs: Replace
<ingress-gateway-pod>with the name of the Pod running the ingress gateway.kubectl -n istio-system exec <ingress-gateway-pod> -c istio-proxy -- \ curl -XPOST "localhost:15000/logging?wasm=debug"Send a request without the
allowheader: Expected output:curl <ingress-gateway-ip>/status/418Forbidden by ASM Wasm PluginCheck the ingress gateway Pod logs. The log entry confirms the plug-in blocked the request:
2024-03-08T08:16:46.747394Z debug envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1168 wasm log istio-system.header-authorization: request header: 'allow' is , only true can passthrough thread=24 {"bytes_received":"0","bytes_sent":"28","downstream_local_address":"xxxxxxx","downstream_remote_address":"xxxxxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"780c8493-13e4-4f97-9771-486efe30347c","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-03-08T08:16:46.747Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_response_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.4.0","x_forwarded_for":"xxxxxx","authority_for":"xxxxxx"}Send a request with the
allow: trueheader: Expected output: The teapot response confirms that the HTTPBin application is accessible and the plug-in is working correctly.curl <ingress-gateway-ip>/status/418 -H "allow: true"-=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
Fix TinyGo memory leaks with nottinygc
Wasm plug-ins compiled with TinyGo may exhibit memory leaks. The proxy-wasm-go-sdk community recommends using nottinygc as a custom garbage collector to resolve this issue.
Add the following import to the top of
main.go: If this dependency is not yet available, rungo mod tidyto download it.import _ "github.com/wasilibs/nottinygc"Rebuild the Wasm binary with the custom GC flags: The
-gc=customflag replaces TinyGo's default garbage collector with nottinygc, and-tags='custommalloc nottinygc_envoy'enables the Envoy-optimized allocator.tinygo build -o plugin.wasm -gc=custom -tags='custommalloc nottinygc_envoy' \ -target=wasi -scheduler=none main.goRebuild the OCI image and push the updated version to Container Registry Enterprise Edition, then reapply the
WasmPluginresource to roll out the fix.