Service Mesh (ASM) prend en charge les plug-ins Wasm dans les proxys Envoy pour le traitement personnalisé des requêtes. Ce tutoriel vous guide pas à pas dans la rédaction d'un plug-in Wasm en Rust qui inspecte les en-têtes de requête HTTP et autorise ou bloque les requêtes selon le résultat.
Le plug-in vérifie si une requête entrante contient l'en-tête allow: true. Si l'en-tête est absent ou défini sur une autre valeur, le plug-in renvoie un code HTTP 403 accompagné d'un message d'erreur personnalisé. Si l'en-tête est présent et défini sur true, la requête est transmise au service amont.
Fonctionnement
Un plug-in Wasm s'exécute au sein du proxy Envoy sous forme de module isolé. La spécification Proxy-Wasm définit une interface binaire d'application (ABI) standard pour la communication entre l'hôte du proxy et les modules Wasm. Étant donné que Proxy-Wasm est indépendant du proxy, les plug-ins construits sur cette ABI sont portables sur tout proxy implémentant la spécification.
Le SDK Rust Proxy-Wasm organise la logique du plug-in autour de trois types de traits :
| Trait | Rôle |
|---|---|
RootContext |
Gère le cycle de vie et la configuration du plug-in. Crée de nouveaux contextes HTTP ou de flux pour chaque requête. |
HttpContext |
Gère un seul cycle de requête/réponse HTTP. Implémentez des rappels tels que on_http_request_headers pour inspecter ou modifier le trafic. |
Context |
Fournit des fonctions utilitaires partagées (accès aux propriétés, minuteries, etc.) héritées par les contextes racine et HTTP. |
Lorsqu'Envoy reçoit une requête HTTP, il crée une nouvelle instance de HttpContext via le RootContext. Les rappels de HttpContext se déclenchent à chaque étape du traitement de la requête, offrant au plug-in un contrôle total sur les en-têtes, le corps et les trailers.
Informations contextuelles
Wasm offre des performances d'exécution proches du natif et s'exécute dans un sandbox sécurisé au niveau de la mémoire, ce qui le rend idéal pour étendre Envoy sans recompilation du binaire du proxy. Toutefois, les langages dotés d'un ramasse-miettes intégré peuvent engendrer une surcharge de performance dans Wasm. Pour cette raison, les langages avec gestion manuelle de la mémoire — C++ et Rust — sont recommandés.
Par rapport au C++, Rust propose un flux de compilation et de construction plus simple, bien que sa courbe d'apprentissage soit plus raide. Faites votre choix en fonction de la maîtrise de chaque langage au sein de votre équipe.
Prérequis
Avant de commencer, assurez-vous de disposer des éléments suivants :
Un cluster ajouté à une instance ASM version 1.18 ou ultérieure. Pour plus d'informations, consultez la rubrique Ajouter un cluster à une instance ASM
L'injection sidecar activée. Pour plus d'informations, consultez la rubrique Configurer la politique d'injection sidecar
Une passerelle ingress créée. Pour plus d'informations, consultez la rubrique Créer une passerelle ingress
L'application HTTPBin déployée et accessible. Pour plus d'informations, consultez la rubrique Déployer l'application HTTPBin
Une instance Container Registry Enterprise Edition créée. Pour plus d'informations, consultez la rubrique Créer une instance Container Registry Enterprise Edition
Vue d'ensemble du workflow
Le processus de bout en bout comprend cinq étapes :
Configurer l'environnement de développement — Installez la chaîne d'outils Rust et la cible de compilation Wasm.
Rédiger le plug-in — Créez un projet de bibliothèque Rust, définissez les dépendances et implémentez la logique d'inspection des en-têtes de requête.
Construire et empaqueter — Compilez le code Rust en binaire Wasm et empaquetez-le sous forme d'image OCI.
Déployer — Poussez l'image vers Container Registry et appliquez la ressource de plug-in Wasm dans ASM.
Vérifier — Envoyez des requêtes de test pour confirmer que le plug-in bloque et autorise le trafic comme prévu.
Étape 1 : Configurer l'environnement de développement
Installez la chaîne d'outils Rust via rustup. Pour obtenir des instructions, consultez la page Install Rust.
-
Ajoutez la cible de compilation
wasm32-wasi: La ciblewasm32-wasicompile le code Rust en WebAssembly avec accès à l'interface système WASI, attendue par le runtime Wasm d'Envoy. Si Rust est déjà installé, commencez par le mettre à jour :rustup target add wasm32-wasirustup update
Étape 2 : Rédiger le plug-in
Cette étape couvre trois parties : création du projet, configuration des dépendances et implémentation de la logique du plug-in.
Créer le projet
Créez un répertoire nommé rust-example, accédez-y et initialisez une nouvelle bibliothèque Rust :
mkdir rust-example && cd rust-example
cargo init --lib
Configurer les dépendances
Remplacez le contenu de Cargo.toml par ce qui suit :
[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"
Le type de crate cdylib produit une bibliothèque dynamique compatible avec le runtime Wasm. Les deux dépendances sont :
**
log** — Interface de journalisation Rust standard pour une sortie de journal structurée.**
proxy-wasm** — Le SDK Rust Proxy-Wasm, qui fournit les définitions de traits et les liaisons de fonctions hôte.
Implémenter la logique du plug-in
Le plug-in doit :
Enregistrer un
RootContextqui indique à Envoy que ce plug-in gère le trafic HTTP.Pour chaque requête HTTP entrante, créer un
HttpContextqui inspecte les en-têtes de la requête.Dans le rappel
on_http_request_headers, vérifier la présence de l'en-têteallow: true. Bloquer la requête avec un code HTTP 403 si l'en-tête est absent, ou la laisser passer s'il est présent.
Remplacez le contenu de src/lib.rs par :
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
}
}
}
}
Le code se décompose comme suit :
| Section | Objectif |
|---|---|
proxy_wasm::main! |
Macro de point d'entrée. Définit le niveau de journalisation sur Trace et enregistre HttpHeadersRoot en tant que contexte racine. |
HttpHeadersRoot + RootContext |
Renvoie ContextType::HttpContext pour indiquer à Envoy que ce plug-in traite le trafic HTTP. Crée une nouvelle instance de HttpHeaders pour chaque requête. |
HttpHeaders + HttpContext |
Implémente on_http_request_headers. Lit l'en-tête allow : renvoie Action::Continue si la valeur est "true", sinon envoie une réponse HTTP 403 et renvoie Action::Pause pour arrêter la requête. |
Compiler le plug-in
Compilez le plug-in avec la cible wasm32-wasi :
cargo build --target wasm32-wasi --release
Après une compilation réussie, le binaire Wasm se trouve à l'emplacement suivant :
target/wasm32-wasi/release/rust_example.wasm
Étape 3 : Construire une image OCI et la pousser vers Container Registry
Empaquetez le binaire Wasm sous forme d'image OCI afin qu'ASM puisse l'extraire et la charger.
-
Créez un
Dockerfileà la racine du projet :FROM scratch # Copy the compiled Wasm binary into the image as plugin.wasm ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm -
Construisez et poussez l'image. Pour connaître la procédure détaillée, consultez la rubrique Créer une image OCI du plug-in Wasm et la pousser vers l'instance Container Registry Enterprise Edition. Remplacez
<your-registry>et<tag>par l'adresse réelle de votre registre et le tag de votre image :docker build -t <your-registry>/wasm-rust-example:<tag> . docker push <your-registry>/wasm-rust-example:<tag>
Étape 4 : Appliquer le plug-in Wasm à la passerelle ingress
Configurez la ressource WasmPlugin dans ASM pour charger le plug-in dans le proxy Envoy de la passerelle ingress.
Pour la procédure complète, consultez la rubrique Appliquer le plug-in Wasm à la passerelle ingress. Assurez-vous que le champ url pointe vers l'image que vous avez poussée à l'étape 3.
Étape 5 : Vérifier le plug-in
Après le déploiement du plug-in, testez-le en envoyant des requêtes avec et sans l'en-tête allow: true.
Activer la journalisation de débogage
Utilisez le kubeconfig du cluster du plan de données pour activer les journaux de débogage Wasm sur le Pod de la passerelle :
kubectl -n istio-system exec <gateway-pod-name> -c istio-proxy -- \
curl -XPOST "localhost:15000/logging?wasm=debug"
Remplacez <gateway-pod-name> par le nom réel de votre Pod de passerelle.
Tester sans l'en-tête allow
Envoyez une requête sans l'en-tête allow :
curl <ASM-gateway-IP>/status/418
Résultat attendu :
Forbidden by ASM Wasm Plugin, rust version
Le plug-in bloque la requête et renvoie un code HTTP 403.
Consulter les journaux de la passerelle
Examinez les journaux du Pod de la passerelle pour repérer des entrées similaires à celles-ci :
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"}
Ces lignes de journal confirment que le plug-in s'est exécuté et a refusé la requête.
Tester avec l'en-tête allow
Envoyez une requête avec allow: true :
curl <ASM-gateway-IP>/status/418 -H "allow: true"
Résultat attendu :
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
La requête est transmise à HTTPBin, qui renvoie la réponse standard HTTP 418 teapot. Le plug-in fonctionne correctement.
Étapes suivantes
Pour rédiger un plug-in Wasm en Go plutôt qu'en Rust, consultez la version Go de ce tutoriel.
Explorez davantage d'exemples de plug-ins dans le dépôt du SDK Rust proxy-wasm, y compris la modification du corps de la réponse HTTP, le chargement de la configuration et l'authentification gRPC.