Istio proxies can be extended with WebAssembly (Wasm) plugins, allowing custom logic to be injected at the edge of your service mesh.

Let’s see this in action. Imagine we have a simple HTTP service that returns "Hello, world!". We want to add a Wasm plugin to this service that intercepts incoming requests and adds a custom X-Service-Name header to every response.

First, we need to compile our Wasm plugin. For this example, we’ll use a simple Rust program:

// src/lib.rs
use proxy_wasm::traits::{Context, HttpContext};
use proxy_wasm::types::*;

#[no_mangle]
pub extern "C" fn _start() {
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::register_plugin(Box::new(MyHttpFilter));
}

struct MyHttpFilter;

impl Context for MyHttpFilter {}

impl HttpContext for MyHttpFilter {
    fn on_http_request_headers(&mut self, _: u32) -> Action {
        // Get the host and the request path
        let host = self.host().unwrap_or_else(|| "".to_string());
        let path = self.path().unwrap_or_else(|| "".to_string());
        log::info!("Received request for: {} {}", host, path);

        // Add the custom header to the response
        self.send_http_response(
            200,
            Some(vec![(&b"x-service-name"[..], &b"my-custom-service"[..])]),
            Some(b"Hello from Wasm!\n"),
        );
        Action::Pause // Pause the request processing until we send a response
    }

    fn on_http_response_headers(&mut self, _: u32) -> Action {
        // This callback is not strictly necessary for this example as we send the response
        // directly in on_http_request_headers.
        Action::Continue
    }
}

To compile this into a Wasm module, you’d typically use cargo build --target wasm32-unknown-unknown --release with the appropriate Rust toolchain. This produces a target/wasm32-unknown-unknown/release/my_filter.wasm file.

Next, we need to configure Istio to use this Wasm plugin. We’ll create an IstioOperator configuration or a WasmPlugin custom resource. For simplicity, let’s assume we’re using IstioOperator and adding the Wasm plugin configuration directly.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    extensionProviders:
    - name: my-wasm-plugin
      wasm:
        url: oci://docker.io/your-docker-repo/my-wasm-plugin:latest # Or a local path if using node
        # If using a local path (e.g., from a ConfigMap mounted to the Istio CA agent or Istiod)
        # url: "/path/to/your/my_filter.wasm"

If you are using a remote OCI registry, you’ll need to push your compiled Wasm module to it. For example, using docker buildx or a similar tool to build and push a Wasm image.

Now, we need to tell Istio’s sidecar proxy to load and use this plugin for specific workloads. This is done via ProxyConfig or annotations on your Kubernetes Deployment. A common way is using ProxyConfig within the IstioOperator:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      proxyMetadata:
        # Enable Wasm injection for all workloads
        WASM_ENABLED: "true"

And then, we can apply this Wasm plugin to a specific VirtualService or Gateway:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: my-app-vs
spec:
  hosts:
  - "my-app.example.com"
  gateways:
  - my-app-gateway
  http:
  - route:
    - destination:
        host: my-app-service
        port:
          number: 8080
    # Apply the Wasm plugin to this route
    headers:
      request:
        add:
          x-wasm-plugin: "my-wasm-plugin" # This refers to the name defined in extensionProviders

Let’s refine the VirtualService to directly apply the Wasm plugin. The headers field in VirtualService is typically for manipulating HTTP headers before they reach the Wasm plugin or after they are processed by it, not for directly enabling a Wasm plugin. The correct way to associate a Wasm plugin with a route is typically through wasm fields in HTTPRoute or TCPRoute (depending on the Istio version and API) or via ProxyConfig applied to the workload.

A more direct way to apply the Wasm plugin to a specific route in modern Istio versions (e.g., 1.10+) might look like this, using the wasm block within an HTTPRoute:

apiVersion: networking.istio.io/v1beta1 # Use appropriate API version
kind: VirtualService
metadata:
  name: my-app-vs
spec:
  hosts:
  - "my-app.example.com"
  gateways:
  - my-app-gateway
  http:
  - route:
    - destination:
        host: my-app-service
        port:
          number: 8080
    # Apply the Wasm plugin to this route
    wasm:
      pluginName: "my-wasm-plugin" # Refers to the name in extensionProviders

After applying these configurations and restarting your application’s pods (to pick up the new sidecar configuration), when you send a request to my-app.example.com, the Istio sidecar will intercept it. It will load your Wasm plugin, execute its on_http_request_headers function, which in turn sends a response directly with the X-Service-Name: my-custom-service header. The original application handler won’t even be invoked in this specific Wasm plugin configuration because we Pause the request and send a response.

The core mental model is that Istio’s Envoy proxies are extended by a Wasm runtime. This runtime can load and execute Wasm modules, which are compiled from languages like Rust or C++. These modules hook into the Envoy request/response lifecycle through defined callbacks (e.g., on_http_request_headers, on_http_response_trailers). You configure which Wasm plugins to load and where to apply them (per-route, per-gateway, or globally) through Istio’s networking resources.

The surprising truth is that the Wasm plugin doesn’t just add to the existing proxy logic; it can replace or short-circuit it entirely. In our example, by calling send_http_response within the on_http_request_headers callback and returning Action::Pause, the Wasm plugin completely bypasses the upstream service, serving a response directly from the proxy. This is a powerful pattern for implementing custom authentication, rate limiting, or request/response manipulation without modifying application code.

The key levers you control are the Wasm plugin’s logic itself (written in a Wasm-compatible language), the url pointing to the Wasm module (OCI registry or local path), and the pluginName used in Istio’s networking resources to associate the plugin with specific traffic. You can also control the Wasm runtime’s logging level and its lifecycle management through Istio’s configuration.

The next concept you’ll likely encounter is managing Wasm plugin lifecycle and versioning, especially when dealing with updates or rollbacks.

Want structured learning?

Take the full Istio course →