All Products
Search
Document Center

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

Last Updated:Oct 21, 2024

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

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

  1. Install rustup. For specific operations, see Install Rust.

  2. 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

  1. Create a new plug-in directory rust-example, switch to this directory, and run the following command.

    cargo init --lib
  2. 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"
  3. 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
                }
            }
        }
    }
  4. 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

  1. 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"
  2. Run the following command to access the HTTPBin application.

    curl ${ASM gateway IP}/status/418

    Expected output:

    Forbidden by ASM Wasm Plugin, rust version
  3. 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"}
  4. 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.