All Products
Search
Document Center

Alibaba Cloud Service Mesh:Write a Wasm plug-in in Rust for an Envoy proxy in ASM

Last Updated:Mar 11, 2026

Service Mesh (ASM) supports Wasm plug-ins in Envoy proxies for custom request processing. This tutorial walks through writing a Wasm plug-in in Rust that inspects HTTP request headers and either allows or blocks requests based on the result.

The plug-in checks whether an incoming request contains the header allow: true. If the header is missing or set to any other value, the plug-in returns HTTP 403 with a custom error message. If the header is present and set to true, the request passes through to the upstream service.

How it works

A Wasm plug-in runs inside the Envoy proxy as a sandboxed module. The Proxy-Wasm specification defines a standard Application Binary Interface (ABI) for communication between the proxy host and Wasm modules. Because Proxy-Wasm is proxy-agnostic, plug-ins built against this ABI are portable across any proxy that implements the specification.

The Proxy-Wasm Rust SDK organizes plug-in logic around three trait types:

TraitRole
RootContextManages plug-in lifecycle and configuration. Creates new HTTP or stream contexts for each request.
HttpContextHandles a single HTTP request/response cycle. Implement callbacks such as on_http_request_headers to inspect or modify traffic.
ContextProvides shared utility functions (property access, timers, and others) inherited by both root and HTTP contexts.

When Envoy receives an HTTP request, it creates a new HttpContext instance through the RootContext. The HttpContext callbacks fire at each stage of request processing, giving the plug-in full control over headers, body, and trailers.

Background information

Wasm provides near-native execution performance and runs in a memory-safe sandbox, which makes it well suited for extending Envoy without recompiling the proxy binary. However, languages with built-in garbage collection may introduce performance overhead in Wasm. For this reason, languages with manual memory management -- C++ and Rust -- are the recommended choices.

Compared to C++, Rust offers a simpler compilation and build workflow, though it has a steeper learning curve. Choose based on your team's familiarity with each language.

Prerequisites

Before you begin, make sure that you have:

Workflow overview

The end-to-end process has five steps:

  1. Set up the development environment -- Install the Rust toolchain and the Wasm compilation target.

  2. Write the plug-in -- Create a Rust library project, define dependencies, and implement the request header inspection logic.

  3. Build and package -- Compile the Rust code to a Wasm binary and package it as an OCI image.

  4. Deploy -- Push the image to Container Registry and apply the Wasm plug-in resource in ASM.

  5. Verify -- Send test requests to confirm the plug-in blocks and allows traffic as expected.

Step 1: Set up the development environment

  1. Install the Rust toolchain through rustup. For instructions, see Install Rust.

  2. Add the wasm32-wasi compilation target: The wasm32-wasi target compiles Rust code to WebAssembly with access to the WASI system interface, which Envoy's Wasm runtime expects. If Rust is already installed, update it first:

       rustup target add wasm32-wasi
       rustup update

Step 2: Write the plug-in

This step covers three parts: creating the project, configuring dependencies, and implementing the plug-in logic.

Create the project

Create a directory named rust-example, switch to it, and initialize a new Rust library:

mkdir rust-example && cd rust-example
cargo init --lib

Configure dependencies

Replace the contents of Cargo.toml with the following:

[package]
name = "rust-example"
version = "0.1.0"
edition = "2021"

[lib]
# Build as a C-compatible dynamic library so Envoy can load it
crate-type = ["cdylib"]

[dependencies]
log = "0.4.8"
proxy-wasm = "0.2.2"

The cdylib crate type produces a dynamic library compatible with the Wasm runtime. The two dependencies are:

  • log -- Standard Rust logging facade for structured log output.

  • proxy-wasm -- The Proxy-Wasm Rust SDK, which provides the trait definitions and host function bindings.

Implement the plug-in logic

The plug-in needs to:

  1. Register a RootContext that tells Envoy this plug-in handles HTTP traffic.

  2. For each incoming HTTP request, create an HttpContext that inspects request headers.

  3. In the on_http_request_headers callback, check for the allow: true header. Block the request with HTTP 403 if the header is missing, or let it pass through if present.

Replace the contents of src/lib.rs with:

use log::info;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;

proxy_wasm::main! {{
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(HttpHeadersRoot) });
}}

struct HttpHeadersRoot;

// Inherit shared utility functions (property access, timers, etc.)
impl Context for HttpHeadersRoot {}

impl RootContext for HttpHeadersRoot {
    fn get_type(&self) -> Option<ContextType> {
        Some(ContextType::HttpContext)
    }

    fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
        Some(Box::new(HttpHeaders { context_id }))
    }
}

struct HttpHeaders {
    context_id: u32,
}

impl Context for HttpHeaders {}

impl HttpContext for HttpHeaders {
    fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
        info!("#{} wasm-rust: on_http_request_headers", self.context_id);

        match self.get_http_request_header("allow") {
            Some(allow) if allow == "true" => {
                Action::Continue
            }
            _ => {
                info!("#{} wasm-rust: allow header not found or is not true, deny by default", self.context_id);
                self.send_http_response(
                    403,
                    vec![("Content-Type", "text/plain")],
                    Some(b"Forbidden by ASM Wasm Plugin, rust version\n"),
                );
                Action::Pause
            }
        }
    }
}

The code breaks down as follows:

SectionPurpose
proxy_wasm::main!Entry point macro. Sets the log level to Trace and registers HttpHeadersRoot as the root context.
HttpHeadersRoot + RootContextReturns ContextType::HttpContext to tell Envoy this plug-in processes HTTP traffic. Creates a new HttpHeaders instance for each request.
HttpHeaders + HttpContextImplements on_http_request_headers. Reads the allow header: returns Action::Continue if the value is "true", otherwise sends an HTTP 403 response and returns Action::Pause to stop the request.

Compile the plug-in

Build the plug-in with the wasm32-wasi target:

cargo build --target wasm32-wasi --release

After a successful build, the Wasm binary is at:

target/wasm32-wasi/release/rust_example.wasm

Step 3: Build an OCI image and push it to Container Registry

Package the Wasm binary as an OCI image so ASM can pull and load it.

  1. Create a Dockerfile in the project root:

       FROM scratch
       # Copy the compiled Wasm binary into the image as plugin.wasm
       ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm
  2. Build and push the image. For detailed steps, see Create an OCI image of the Wasm plug-in and push it to the Container Registry Enterprise Edition instance. Replace <your-registry> and <tag> with your actual registry address and image tag:

       docker build -t <your-registry>/wasm-rust-example:<tag> .
       docker push <your-registry>/wasm-rust-example:<tag>

Step 4: Apply the Wasm plug-in to the ingress gateway

Configure the WasmPlugin resource in ASM to load the plug-in into the ingress gateway's Envoy proxy.

For the full procedure, see Apply the Wasm plug-in to the ingress gateway. Make sure the url field points to the image you pushed in Step 3.

Step 5: Verify the plug-in

After deploying the plug-in, test it by sending requests with and without the allow: true header.

Enable debug logging

Use the kubeconfig of the data plane cluster to enable Wasm debug logs on the gateway Pod:

kubectl -n istio-system exec <gateway-pod-name> -c istio-proxy -- \
  curl -XPOST "localhost:15000/logging?wasm=debug"

Replace <gateway-pod-name> with the actual name of your gateway Pod.

Test without the allow header

Send a request without the allow header:

curl <ASM-gateway-IP>/status/418

Expected output:

Forbidden by ASM Wasm Plugin, rust version

The plug-in blocks the request and returns HTTP 403.

Check gateway logs

Inspect the gateway Pod logs for entries similar to the following:

2024-09-05T08:33:31.079869Z  info  envoy wasm  wasm log istio-system.header-authorization: #2 wasm-rust: on_http_request_headers
2024-09-05T08:33:31.079943Z  info  envoy wasm  wasm log istio-system.header-authorization: #2 wasm-rust: allow header not found or is not true, deny by default
{"authority_for":"xx.xx.xx.xx","bytes_received":"0","bytes_sent":"43","downstream_local_address":"xx.xx.xx.xx:80","downstream_remote_address":"xx.xx.xx.xx:xxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"d5250d1a-54b3-406d-8bea-5a51b617b579","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-09-05T08:33:31.079Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_response_time":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.9.0-DEV","x_forwarded_for":"xx.xx.xx.xx"}

These log lines confirm the plug-in executed and denied the request.

Test with the allow header

Send a request with allow: true:

curl <ASM-gateway-IP>/status/418 -H "allow: true"

Expected output:

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

The request passes through to HTTPBin, which returns the standard HTTP 418 teapot response. The plug-in is working correctly.

What's next

  • To write a Wasm plug-in in Go instead, see the Go version of this tutorial.

  • Explore more plug-in examples in the proxy-wasm Rust SDK repository, including HTTP response body modification, configuration loading, and gRPC authentication.