O Service Mesh (ASM) oferece suporte a plug-ins Wasm em proxies Envoy para processamento personalizado de requisições. Este tutorial demonstra como escrever um plug-in Wasm em Rust que inspeciona cabeçalhos de requisição HTTP e permite ou bloqueia requisições com base no resultado.
O plug-in verifica se uma requisição recebida contém o cabeçalho allow: true. Se o cabeçalho estiver ausente ou definido com qualquer outro valor, o plug-in retorna HTTP 403 com uma mensagem de erro personalizada. Caso o cabeçalho esteja presente e definido como true, a requisição segue para o serviço upstream.
Como funciona
Um plug-in Wasm é executado dentro do proxy Envoy como um módulo isolado. A especificação Proxy-Wasm define uma Interface Binária de Aplicação (ABI) padrão para comunicação entre o host do proxy e os módulos Wasm. Como o Proxy-Wasm é independente de proxy, os plug-ins desenvolvidos com essa ABI são portáteis para qualquer proxy que implemente a especificação.
O SDK do Proxy-Wasm para Rust organiza a lógica do plug-in em torno de três tipos de trait:
|
Trait |
Função |
|
|
Gerencia o ciclo de vida e a configuração do plug-in. Cria novos contextos HTTP ou de stream para cada requisição. |
|
|
Trata um único ciclo de requisição/resposta HTTP. Implementa callbacks como |
|
|
Fornece funções utilitárias compartilhadas (acesso a propriedades, temporizadores, entre outras), herdadas pelos contextos raiz e HTTP. |
Quando o Envoy recebe uma requisição HTTP, ele cria uma nova instância de HttpContext por meio do RootContext. Os callbacks do HttpContext são acionados em cada etapa do processamento da requisição, concedendo ao plug-in controle total sobre cabeçalhos, corpo e trailers.
Informações de fundo
O Wasm oferece desempenho de execução próximo ao nativo e roda em um sandbox seguro para memória, o que o torna ideal para estender o Envoy sem recompilar o binário do proxy. No entanto, linguagens com coleta de lixo integrada podem introduzir sobrecarga de desempenho no Wasm. Por esse motivo, linguagens com gerenciamento manual de memória — C++ e Rust — são as escolhas recomendadas.
Em comparação ao C++, o Rust oferece um fluxo de trabalho de compilação e build mais simples, embora tenha uma curva de aprendizado mais íngreme. Escolha com base na familiaridade da sua equipe com cada linguagem.
Pré-requisitos
Antes de começar, certifique-se de ter:
Um cluster adicionado a uma instância do ASM versão 1,18 ou posterior. Para mais informações, consulte Adicionar um cluster a uma instância do ASM
Injeção de sidecar ativada. Para mais informações, consulte Configurar política de injeção de sidecar
Um gateway de entrada criado. Para mais informações, consulte Criar um gateway de entrada
A aplicação HTTPBin implantada e acessível. Para mais informações, consulte Implantar a aplicação HTTPBin
Uma instância do Container Registry Enterprise Edition criada. Para mais informações, consulte Criar uma instância do Container Registry Enterprise Edition
Visão geral do fluxo de trabalho
O processo completo consiste em cinco etapas:
Configure o ambiente de desenvolvimento — Instale o toolchain do Rust e o alvo de compilação Wasm.
Escreva o plug-in — Crie um projeto de biblioteca Rust, defina as dependências e implemente a lógica de inspeção de cabeçalhos de requisição.
Compile e empacote — Compile o código Rust para um binário Wasm e empacote-o como uma imagem OCI.
Implante — Envie a imagem para o Container Registry e aplique o recurso de plug-in Wasm no ASM.
Verifique — Envie requisições de teste para confirmar se o plug-in bloqueia e permite o tráfego conforme esperado.
Etapa 1: Configurar o ambiente de desenvolvimento
Instale o toolchain do Rust via rustup. Para instruções, consulte Instalar o Rust.
-
Adicione o alvo de compilação
wasm32-wasi. O alvowasm32-wasicompila código Rust para WebAssembly com acesso à interface de sistema WASI, exigida pelo runtime Wasm do Envoy. Se o Rust já estiver instalado, atualize-o primeiro:rustup target add wasm32-wasirustup update
Etapa 2: Escrever o plug-in
Esta etapa abrange três partes: criação do projeto, configuração de dependências e implementação da lógica do plug-in.
Criar o projeto
Crie um diretório chamado rust-example, acesse-o e inicialize uma nova biblioteca Rust:
mkdir rust-example && cd rust-example
cargo init --lib
Configurar dependências
Substitua o conteúdo de Cargo.toml pelo seguinte:
[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"
O tipo de crate cdylib gera uma biblioteca dinâmica compatível com o runtime Wasm. As duas dependências são:
**
log** — Fachada de logging padrão do Rust para saída de log estruturada.**
proxy-wasm** — O SDK do Proxy-Wasm para Rust, que fornece as definições de traits e bindings de funções do host.
Implementar a lógica do plug-in
O plug-in precisa:
Registrar um
RootContextque informe ao Envoy que este plug-in trata tráfego HTTP.Criar um
HttpContextpara cada requisição HTTP recebida, responsável por inspecionar os cabeçalhos da requisição.No callback
on_http_request_headers, verificar a presença do cabeçalhoallow: true. Bloqueie a requisição com HTTP 403 se o cabeçalho estiver ausente ou permita a passagem caso esteja presente.
Substitua o conteúdo de src/lib.rs por:
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
}
}
}
}
O código divide-se da seguinte forma:
|
Seção |
Propósito |
|
|
Macro de ponto de entrada. Define o nível de log como |
|
|
Retorna |
|
|
Implementa |
Compilar o plug-in
Compile o plug-in com o alvo wasm32-wasi:
cargo build --target wasm32-wasi --release
Após uma compilação bem-sucedida, o binário Wasm estará em:
target/wasm32-wasi/release/rust_example.wasm
Etapa 3: Criar uma imagem OCI e enviá-la ao Container Registry
Empacote o binário Wasm como uma imagem OCI para que o ASM possa baixá-la e carregá-la.
-
Crie um
Dockerfilena raiz do projeto:FROM scratch # Copy the compiled Wasm binary into the image as plugin.wasm ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm -
Compile e envie a imagem. Para etapas detalhadas, consulte Criar uma imagem OCI do plug-in Wasm e enviá-la à instância do Container Registry Enterprise Edition. Substitua
<your-registry>e<tag>pelo endereço real do seu registry e pela tag da imagem:docker build -t <your-registry>/wasm-rust-example:<tag> . docker push <your-registry>/wasm-rust-example:<tag>
Etapa 4: Aplicar o plug-in Wasm ao gateway de entrada
Configure o recurso WasmPlugin no ASM para carregar o plug-in no proxy Envoy do gateway de entrada.
Para o procedimento completo, consulte Aplicar o plug-in Wasm ao gateway de entrada. Certifique-se de que o campo url aponte para a imagem enviada na Etapa 3.
Etapa 5: Verificar o plug-in
Após implantar o plug-in, teste-o enviando requisições com e sem o cabeçalho allow: true.
Ativar logs de depuração
Use o kubeconfig do cluster do plano de dados para ativar logs de depuração Wasm no Pod do gateway:
kubectl -n istio-system exec <gateway-pod-name> -c istio-proxy -- \
curl -XPOST "localhost:15000/logging?wasm=debug"
Substitua <gateway-pod-name> pelo nome real do seu Pod de gateway.
Testar sem o cabeçalho allow
Envie uma requisição sem o cabeçalho allow:
curl <ASM-gateway-IP>/status/418
Saída esperada:
Forbidden by ASM Wasm Plugin, rust version
O plug-in bloqueia a requisição e retorna HTTP 403.
Verificar logs do gateway
Inspecione os logs do Pod do gateway em busca de entradas semelhantes às seguintes:
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"}
Essas linhas de log confirmam que o plug-in foi executado e negou a requisição.
Testar com o cabeçalho allow
Envie uma requisição com allow: true:
curl <ASM-gateway-IP>/status/418 -H "allow: true"
Saída esperada:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
A requisição passa pelo plug-in e chega ao HTTPBin, que retorna a resposta padrão HTTP 418 teapot. O plug-in está funcionando corretamente.
Próximos passos
Para escrever um plug-in Wasm em Go, consulte a versão deste tutorial em Go.
Explore mais exemplos de plug-ins no repositório do SDK proxy-wasm para Rust, incluindo modificação de corpo de resposta HTTP, carregamento de configuração e autenticação gRPC.