Best practices for plugins

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

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.