Service Mesh (ASM) admite plugins de Wasm en los proxies Envoy para el procesamiento personalizado de solicitudes. Este tutorial explica cómo escribir un plugin de Wasm en Rust que inspecciona las cabeceras de las solicitudes HTTP y permite o bloquea las peticiones según el resultado.
El plugin comprueba si una solicitud entrante contiene la cabecera allow: true. Si la cabecera falta o tiene cualquier otro valor, el plugin devuelve un error HTTP 403 con un mensaje personalizado. Si la cabecera está presente y su valor es true, la solicitud se reenvía al servicio upstream.
Funcionamiento
Un plugin de Wasm se ejecuta dentro del proxy Envoy como un módulo aislado en un sandbox. La especificación Proxy-Wasm define una Interfaz Binaria de Aplicación (ABI) estándar para la comunicación entre el host del proxy y los módulos de Wasm. Dado que Proxy-Wasm es independiente del proxy, los plugins compilados con esta ABI son portables a cualquier proxy que implemente la especificación.
El SDK de Rust para Proxy-Wasm organiza la lógica del plugin en torno a tres tipos de traits:
|
Trait |
Función |
|
|
Gestiona el ciclo de vida y la configuración del plugin. Crea nuevos contextos HTTP o de flujo para cada solicitud. |
|
|
Maneja un único ciclo de solicitud/respuesta HTTP. Implementa callbacks como |
|
|
Proporciona funciones de utilidad compartidas (acceso a propiedades, temporizadores y otras) que heredan tanto los contextos raíz como los HTTP. |
Cuando Envoy recibe una solicitud HTTP, crea una nueva instancia de HttpContext a través de RootContext. Los callbacks de HttpContext se activan en cada etapa del procesamiento de la solicitud, lo que otorga al plugin control total sobre las cabeceras, el cuerpo y los trailers.
Contexto
Wasm ofrece un rendimiento de ejecución casi nativo y se ejecuta en un sandbox con seguridad de memoria, por lo que resulta ideal para extender Envoy sin recompilar el binario del proxy. Sin embargo, los lenguajes con recolección de basura integrada pueden introducir sobrecarga de rendimiento en Wasm. Por este motivo, se recomienda utilizar lenguajes con gestión manual de memoria, como C++ y Rust.
En comparación con C++, Rust ofrece un flujo de trabajo de compilación y construcción más sencillo, aunque su curva de aprendizaje es más pronunciada. Elija la opción que mejor se adapte a la experiencia de su equipo con cada lenguaje.
Requisitos previos
Antes de empezar, asegúrese de contar con lo siguiente:
Un clúster agregado a una instancia de ASM versión 1.18 o posterior. Para obtener más información, consulte Add a cluster to an ASM instance
La inyección de sidecar habilitada. Para obtener más información, consulte Configure sidecar injection policy
Una puerta de enlace de entrada creada. Para obtener más información, consulte Create an ingress gateway
La aplicación HTTPBin desplegada y accesible. Para obtener más información, consulte Deploy the HTTPBin application
Una instancia de Container Registry Enterprise Edition creada. Para obtener más información, consulte Create a Container Registry Enterprise Edition instance
Descripción general del flujo de trabajo
El proceso integral consta de cinco pasos:
Configurar el entorno de desarrollo: instale la cadena de herramientas de Rust y el destino de compilación de Wasm.
Escribir el plugin: cree un proyecto de biblioteca en Rust, defina las dependencias e implemente la lógica de inspección de cabeceras de solicitud.
Compilar y empaquetar: compile el código de Rust en un binario de Wasm y empáquelo como una imagen OCI.
Desplegar: envíe la imagen a Container Registry y aplique el recurso del plugin de Wasm en ASM.
Verificar: envíe solicitudes de prueba para confirmar que el plugin bloquea y permite el tráfico según lo esperado.
Paso 1: Configurar el entorno de desarrollo
Instale la cadena de herramientas de Rust mediante rustup. Para consultar las instrucciones, visite Instalar Rust.
-
Agregue el destino de compilación
wasm32-wasi: El destinowasm32-wasicompila código de Rust a WebAssembly con acceso a la interfaz del sistema WASI, tal como lo requiere el entorno de ejecución de Wasm de Envoy. Si ya tiene Rust instalado, actualícelo primero:rustup target add wasm32-wasirustup update
Paso 2: Escribir el plugin
Este paso abarca tres partes: crear el proyecto, configurar las dependencias e implementar la lógica del plugin.
Crear el proyecto
Cree un directorio llamado rust-example, cámbiese a él e inicialice una nueva biblioteca de Rust:
mkdir rust-example && cd rust-example
cargo init --lib
Configurar las dependencias
Reemplace el contenido de Cargo.toml con lo siguiente:
[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"
El tipo de crate cdylib genera una biblioteca dinámica compatible con el entorno de ejecución de Wasm. Las dos dependencias son:
**
log**: fachada de registros estándar de Rust para la salida estructurada de registros.**
proxy-wasm**: el SDK de Rust para Proxy-Wasm, que proporciona las definiciones de traits y los enlaces a funciones del host.
Implementar la lógica del plugin
El plugin debe realizar las siguientes tareas:
Registrar un
RootContextque indique a Envoy que este plugin maneja tráfico HTTP.Crear un
HttpContextpara cada solicitud HTTP entrante que inspeccione las cabeceras de la petición.En el callback
on_http_request_headers, buscar la cabeceraallow: true. Debe bloquear la solicitud con HTTP 403 si la cabecera no existe, o permitir su paso si está presente.
Reemplace el contenido de src/lib.rs con:
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
}
}
}
}
El código se desglosa de la siguiente manera:
|
Sección |
Propósito |
|
|
Macro del punto de entrada. Establece el nivel de registro en |
|
|
Devuelve |
|
|
Implementa |
Compilar el plugin
Compile el plugin con el destino wasm32-wasi:
cargo build --target wasm32-wasi --release
Tras una compilación exitosa, el binario de Wasm se encontrará en:
target/wasm32-wasi/release/rust_example.wasm
Paso 3: Construir una imagen OCI y enviarla a Container Registry
Empaquete el binario de Wasm como una imagen OCI para que ASM pueda descargarla y cargarla.
-
Cree un archivo
Dockerfileen la raíz del proyecto:FROM scratch # Copy the compiled Wasm binary into the image as plugin.wasm ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm -
Construya y envíe la imagen. Para consultar los pasos detallados, vea Create an OCI image of the Wasm plug-in and push it to the Container Registry Enterprise Edition instance. Reemplace
<your-registry>y<tag>con la dirección real de su registro y la etiqueta de la imagen:docker build -t <your-registry>/wasm-rust-example:<tag> . docker push <your-registry>/wasm-rust-example:<tag>
Paso 4: Aplicar el plugin de Wasm a la puerta de enlace de entrada
Configure el recurso WasmPlugin en ASM para cargar el plugin en el proxy Envoy de la puerta de enlace de entrada.
Para consultar el procedimiento completo, vea Apply the Wasm plug-in to the ingress gateway. Asegúrese de que el campo url apunte a la imagen que envió en el Paso 3.
Paso 5: Verificar el plugin
Después de desplegar el plugin, pruébelo mediante el envío de solicitudes con y sin la cabecera allow: true.
Habilitar el registro de depuración
Utilice el kubeconfig del clúster del plano de datos para habilitar los registros de depuración de Wasm en el Pod de la puerta de enlace:
kubectl -n istio-system exec <gateway-pod-name> -c istio-proxy -- \
curl -XPOST "localhost:15000/logging?wasm=debug"
Reemplace <gateway-pod-name> con el nombre real de su Pod de puerta de enlace.
Probar sin la cabecera allow
Envíe una solicitud sin la cabecera allow:
curl <ASM-gateway-IP>/status/418
Salida esperada:
Forbidden by ASM Wasm Plugin, rust version
El plugin bloquea la solicitud y devuelve HTTP 403.
Comprobar los registros de la puerta de enlace
Inspeccione los registros del Pod de la puerta de enlace en busca de entradas similares a las siguientes:
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"}
Estas líneas de registro confirman que el plugin se ejecutó y denegó la solicitud.
Probar con la cabecera allow
Envíe una solicitud con allow: true:
curl <ASM-gateway-IP>/status/418 -H "allow: true"
Salida esperada:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
La solicitud pasa a HTTPBin, que devuelve la respuesta estándar HTTP 418 (tetera). Esto confirma que el plugin funciona correctamente.
Pasos siguientes
Si prefiere escribir un plugin de Wasm en Go, consulte la versión en Go de este tutorial.
Explore más ejemplos de plugins en el repositorio del SDK de Rust para proxy-wasm, donde encontrará casos como la modificación del cuerpo de respuestas HTTP, la carga de configuración y la autenticación gRPC.