This page provides tips for writing Service Extensions plugins that are correct, perform well, and are well isolated. Correctness is critical because plugins run in a constrained engine sandbox with a limited API surface. Performance is critical because plugins run during end-user requests, with small amounts of resources. Isolation is provided at the project level.
Getting started
Start from the samples
A good way to get started is browsing our plugin code samples. These are examples of common plugin patterns, such as path or query parsing, header rewrites, custom logging, and custom authentication.
Use supported APIs
Plugins must be compiled against the Proxy-Wasm
binary interface (ABI). Service Extensions supports a subset of the Proxy-Wasm ABI
that includes HTTP header and body mutations, local responses, custom logging,
and plugin configuration. Proxy-Wasm also supports a small subset of
WASI preview 1,
including stdout
and stderr
for logging, clock_time_get
and random_get
.
Service Extensions doesn't support timers, custom metrics, shared data, shared queues, or outbound network calls.
Run functional tests and benchmarks
To evaluate correctness and performance, we provide a local plugin tester tool that can run, test, and benchmark your plugin. You invoke this tool in a Docker container, passing a plugin binary and a text proto of inputs and expectations. See the local tester documentation and test input example.
Correctness
Don't rely on clocks
For security reasons, clock time is set at context creation (for a plugin or a request) and kept frozen during plugin invocations. This means that WebAssembly plugins must not sleep; a sleep times out because time doesn't advance. It also means that plugins can't measure their own execution time, though this information is available in Cloud Monitoring.
Treat header names as case-insensitive
According to HTTP semantics, HTTP header field names must be treated as case-insensitive. Header case written by a plugin can be changed before being sent to clients or backends.
Performance
Compile for execution speed
Precompile regular expressions
Avoid copying data into HTTP contexts
Compile for execution speed
Compile your WebAssembly code to achieve the best execution speed by using
build option -O3
(for C++) or opt-level=3
(for Rust).
Precompile regular expressions
Avoid compute-heavy operations on the per-request path (in HTTP handlers). Instead, perform any work that's not request-specific at plugin configuration time, and pass any precomputed state into each HTTP request context (also known as stream context).
In particular, precompile any regular expressions at plugin configuration time. Our regex code sample shows how to achieve this.
Avoid copying data into HTTP contexts
When sharing data between the root context and HTTP contexts, avoid expensive
copies or clone calls. Instead, share pointers or references. In C++, this can be
done with std::shared_ptr
or raw pointers and references because the root
context outlives any HTTP context. In Rust, values can be shared by using
std::rc::Rc
.
Security
Isolate workloads by project
On Google's infrastructure, plugins owned by the same Google Cloud project can run in the same secure sandbox. This means that the WebAssembly runtime is the only security barrier between plugins in the same project.
Use separate Google Cloud projects for workloads that need security separation. This practice also provides separation of resources and permissions.
Restrict secrets on Media CDN
By design, Media CDN runs on a fleet of hardware operated outside of Google's physical control. When writing security-related plugins for Media CDN, use dedicated hostnames or subdomains, and configure plugins with signing keys that are rotated frequently.