Service Mesh (ASM) supports the deployment of Wasm plug-ins within Envoy proxies to implement custom processing logic. The Proxy-Wasm community offers a Rust SDK for Wasm development. This topic explains how to write a Wasm plug-in in Rust for an Envoy proxy in ASM.
Background information
Wasm offers near-native binary execution efficiency and features a runtime sandbox, enhancing security. Currently, Wasm has some shortcomings, such as performance issues in languages with built-in garbage collection. Therefore, we recommend that you use languages that allow manual memory management, such as C++ and Rust. Compared to C++, Rust is currently more convenient in the compilation and build stages, although it has a slightly higher learning curve. You can choose based on your actual situation.
Prerequisites
A cluster is added to an ASM instance, and the ASM instance version is 1.18 or later. For more information, see Add a cluster to an ASM instance.
Sidecar injection is enabled. For more information, see Configure Sidecar injection policy.
An ingress gateway is created. For more information, see Create an ingress gateway.
The HTTPBin application is deployed and accessible. For more information, see Deploy the HTTPBin application.
A Container Registry Enterprise Edition instance is created. For more information, see Create a Container Registry Enterprise Edition instance.
Usage notes
This topic explains how to write a Wasm plug-in in Rust. After generating a Wasm binary, package it into an image. After building the image, upload it to the OCI image repository of the image service. After the upload is complete, configure the Wasm plug-in resource in ASM to apply it to the specified Envoy proxy.
This plug-in is used to determine whether the request header allow: true
exists in the request. If it does not exist, a 403 status code and a specified response body are returned. If it exists, the HTTPBin application is accessed normally.
Step 1: Prepare the development environment
Install rustup. For specific operations, see Install Rust.
Run the following command to install the toolchain required to compile the Wasm binary.
rustup target add wasm32-wasi
If it is already installed, run the following command to update Rust.
rustup update
Step 2: Write the plug-in
Create a new plug-in directory
rust-example
, switch to this directory, and run the following command.cargo init --lib
Add the following content to the generated Cargo.toml file.
[lib] # Declare that this is a dynamic library that can be called by C/C++ crate-type = ["cdylib"] [dependencies] log = "0.4.8" proxy-wasm = "0.2.2"
Add the following content to
src/lib.rs
.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; // Some basic utility functions. After adding this line, various utility functions can be called directly in self. // For example: self.set_property(path, value) 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 } } } }
Run the following command to compile the plug-in.
cargo build --target wasm32-wasi --release
After a successful compilation, a target file is generated, and the final WASM binary file is
target/wasm32-wasi/release/rust_example.wasm
.
Step 3: Create an OCI image and push it to Container Registry
Create a Dockerfile with the following content.
FROM scratch
# Copy the generated wasm binary file into the image and rename it to plugin.wasm.
ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm
For the steps to build and push the image, see Create an OCI image of the Wasm plug-in and push it to the Container Registry Enterprise Edition instance. Specify the image name and Tag.
Step 4: Apply the Wasm plug-in to the gateway
For specific operation steps, see Apply the Wasm plug-in to the ingress gateway. Ensure that the url configured in Wasm plug-in specifies the correct image address.
Step 5: Verify whether the plug-in is effective
Use the kubeconfig of the data plane cluster and run the following command to enable the debug log of the Wasm component of the gateway.
kubectl -n istio-system exec ${gateway pod name} -c istio-proxy -- curl -XPOST "localhost:15000/logging?wasm=debug"
Run the following command to access the HTTPBin application.
curl ${ASM gateway IP}/status/418
Expected output:
Forbidden by ASM Wasm Plugin, rust version
Check the gateway Pod logs. The log example is as follows.
2024-09-05T08:33:31.079869Z info envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1195 wasm log istio-system.header-authorization: #2 wasm-rust: on_http_request_headers thread=35 2024-09-05T08:33:31.079943Z info envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1195 wasm log istio-system.header-authorization: #2 wasm-rust: allow header not found or is not true, deny by default thread=35 {"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"}
Add the request header
allow: true
and access the httpbin application of the gateway again.curl ${ASM gateway IP}/status/418 -H "allow: true"
Expected output:
-=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
As you can see, the access is successful after adding the request header
allow: true
, and the plug-in is effective.