istio/istio
Wasm and EnvoyFilter
Active contributors: hzxuzhonghu, howardjohn, ramaraochavali
What users do
Extend the data plane beyond what stable Istio APIs offer:
WasmPlugin— load a Wasm binary into the proxy; runs as an HTTP filter.EnvoyFilter— patch arbitrary Envoy config with surgical precision.
These are the escape hatches. The recommendation is "use stable APIs first; reach for these only when the stable APIs don't cover your case."
WasmPlugin
The WasmPlugin CRD references a Wasm binary by URL or OCI image and configures it for a workload set:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: my-filter
namespace: my-app
spec:
selector:
matchLabels:
app: my-app
url: oci://example.com/my-filter:v1
phase: AUTHN
pluginConfig:
foo: barHow it gets to the proxy
The Wasm binary is not served inline through xDS. Instead:
- The
WasmPluginreconciles into aEnvoyFilter-equivalent ECDS (Extension Config Discovery Service) entry. - ECDS resources reference a URI (
http://,oci://,file://). - Istiod compiles the listener with a placeholder filter that loads its config from ECDS.
- The proxy's pilot-agent fetches the actual binary from the URI (the agent's "Wasm fetch loop") and stores it on disk.
- Envoy loads the binary from disk.
This indirection avoids streaming MB-sized binaries through xDS and lets the proxy cache them across restarts.
Where it's implemented
- Schema:
extensions/v1alpha1/wasm.proto. - Reconciliation:
pilot/pkg/networking/core/extension/wasmplugin.go. Builds the ECDS entries. - xDS surface:
pilot/pkg/xds/ecds.go. The ECDS generator. - Agent fetcher:
pkg/wasm/. Fetch, verify, cache. - Image format: OCI artifact format with the
application/vnd.wasm.config.v1+jsonmanifest type. Compatible with Docker registries.
Phases
WasmPlugin.spec.phase:
| Phase | When the filter runs |
|---|---|
AUTHN |
Before AuthN filters (rare) |
AUTHZ |
Between AuthN and AuthZ |
STATS |
Before stats — useful for custom metrics |
UNSPECIFIED_PHASE |
Just before the router |
Order within a phase is by priority (higher → earlier).
Trade-offs
- Wasm runtime cost — WAVM/V8 compile is slow at startup. Each Envoy worker thread compiles independently. Long startup tails on cold restarts.
- Memory — each thread holds its own Wasm VM instance.
- Debugging — Envoy's Wasm logs are noisy. Print-line debugging from a Wasm plugin requires the host's logging via the
proxy_logABI. - API churn — proxy-wasm spec is stable, but tooling (
tinygo, Rust SDK) evolves quickly.
EnvoyFilter
The bare-metal escape hatch. EnvoyFilter lets users patch any aspect of the Envoy config Istio generates.
Anatomy of a patch
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: custom-headers
spec:
workloadSelector:
labels:
app: my-app
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(handle)
handle:headers():add("x-custom", "value")
endHow it's applied
The patcher is in pilot/pkg/networking/core/envoyfilter/. After Istio builds the standard config, it walks each EnvoyFilter and applies its patches:
applyToselects what type of node (LISTENER, CLUSTER, ROUTE, FILTER_CHAIN, HTTP_FILTER, NETWORK_FILTER, ROUTE_CONFIGURATION, VIRTUAL_HOST, BOOTSTRAP).match.contextfilters by proxy role (SIDECAR_INBOUND, SIDECAR_OUTBOUND, GATEWAY, ANY).match.proxy.proxyVersionfilters by Envoy version regex.patch.operationis one of MERGE, ADD, REMOVE, INSERT_BEFORE, INSERT_AFTER, INSERT_FIRST, REPLACE.
Trade-offs
- Tightly coupled to Envoy version — if the upstream Envoy proto for your filter changes between Istio releases, your
EnvoyFiltersilently breaks. Test against new Istio versions. - No validation of filter contents — Istio doesn't know your filter's schema. A typo lands as a runtime config error on the proxy, not a CRD validation error.
- Privileged use —
EnvoyFiltercan patch the cluster manager and bootstrap, which can break istiod ↔ proxy communication. Limit who can apply them.
When to use which
| Need | Use |
|---|---|
| HTTP filter logic in a high-level language | WasmPlugin |
| Custom metrics with bespoke dimensions | WasmPlugin (Stats phase) |
| Patch in a known Envoy filter (Lua, ext_authz, RBAC variants) | EnvoyFilter |
| Tweak listener / cluster behavior | EnvoyFilter |
| Bootstrap / startup config tweak | EnvoyFilter, applyTo BOOTSTRAP |
Anything covered by Telemetry, AuthorizationPolicy, RequestAuthentication, VirtualService, DestinationRule, Gateway, Sidecar |
The stable API |
Key source files
| File | Purpose |
|---|---|
pilot/pkg/networking/core/extension/wasmplugin.go |
WasmPlugin → ECDS |
pilot/pkg/xds/ecds.go |
ECDS generator |
pkg/wasm/ |
Agent-side Wasm fetcher/cache |
pilot/pkg/networking/core/envoyfilter/patch.go |
EnvoyFilter applier |
pilot/pkg/networking/core/envoyfilter/cluster_patch.go |
Cluster patches |
pilot/pkg/networking/core/envoyfilter/listener_patch.go |
Listener patches |
pilot/pkg/networking/core/envoyfilter/route_patch.go |
Route patches |
pilot/pkg/model/envoyfilter.go |
EnvoyFilter resolution |
See also
- systems/networking-core — the patch model in context.
- systems/xds — ECDS streams.
- features/observability — where
WasmPluginis most often used (custom stats).
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.