Wasm Labs

mod_wasm: run WebAssembly with Apache

By Jesús González
At 2022 / 10 15 mins reading

Apache httpd is a modular web server that powers 31% of all websites you access every day. One of its most compelling features is the ability to extend it with new modules. Developers can choose among different modules to add or remove features like CGI, TLS, PHP, and many others.

Today, we announce a new Apache module to run WebAssembly modules: mod_wasm. This module opens a new set of possibilities as many languages and projects can run securely in Apache.

What is WebAssembly?

WebAssembly (Wasm) is a binary instruction format designed for a lightweight virtual machine, typically called runtime. Different languages can be compiled into Wasm and run in any place where a runtime is available. This makes Wasm a polyglot format that can run applications from a wide variety of languages such as C, C++, Rust, Go, etc.

You may notice that all languages we mentioned are compiled languages. Fortunately, Wasm is not limited to that. Most existing interpreters for dynamic languages are developed in one of those compiled languages, so you can compile both the interpreter and the source code into a Wasm module. This approach allowed us to run WordPress and PHP in the browser several months ago.

Wasm was created to bring new languages into web browsers and the adoption was incredible. After that, people started to think on the capabilities of Wasm on the server side. We strongly agree with this mindset and today, we helped to bring Wasm to one of the most popular web servers in the world: Apache httpd.

If you want to know more about Wasm, we recommend this great article from TheNewStack.

Introducing mod_wasm

mod_wasm is an Apache httpd module to run Wasm modules. It allows you to reply to HTTP requests with applications compiled to Wasm. Internally, it uses the wasmtime runtime to configure, initialize, and run the Wasm modules. The rest of this section explains how it works internally. If you are just interested in using it, you can skip to the next one.

The module is composed of two libraries:

  • mod_wasm.so: this is the interface between the Apache C API and the Rust library that manages the Wasm runtime. It is responsible for the Apache configuration options and the bindings to connect the rust library with Apache.
  • libwasm_runtime.so: it receives HTTP requests from Apache, configures the Wasm module and runs it. Then, it parses the response and returns the control to mod_wasm.so.
A diagram showing how Apache HTTP server initiates mod_wasm.so. Then, mod_wasm.so talks with libwasm_runtime.so dinamically. Wasmtime is instantited from the libwasm_runtime.so

When you run an Apache httpd server with mod_wasm enabled, it preloads the Wasm module in memory. During the Apache initialization phase, mod_wasm.so reads the configuration and sends it to libwasm_runtime.so. Then, the module is read from the filesystem and preloaded in memory with wasmtime. This approach speeds up the request handling by not loading the Wasm module from scratch every time it receives a request.

The server is now ready to start processing requests. mod_wasm.so evaluates every request to process the ones that belongs to the configured path. Then, the request is processed and passed to the libwasm_runtime.so. This library configures a new wasmtime context with the HTTP headers and the request body and runs the module.

To pass this information, we use the WebAssembly System Interface (WASI), a modular system interface for WebAssembly. WASI allows you to configure common system interfaces such as environment variables, filesystem, and standard input / output (stdio). libwasm_runtime.so takes advantage of these features to configure HTTP headers as environment variables and pass the request body through the standard input (stdin).

Finally, the Wasm module returns all the data through the standard output (stdout). There, we process the response as with CGI by looking for the HTTP response headers at the top and the content right after.

This approach provides the ability to use multiple compiled and interpreted languages to serve content through Apache httpd. And now, let's jump right to the demo!

Try mod_wasm

To run mod_wasm you need to compile the module and configure it in Apache httpd. To skip these steps, we created a container image you can run directly. So, to try mod_wasm you only need a container image runtime such as docker or podman, and then run the following command:

docker run -p 8080:8080 \
projects.registry.vmware.com/wasmlabs/containers/httpd-mod-wasm:latest

Now, open your browser at http://localhost:8080/wasm-module-endpoint and see the result.

In case you prefer to watch it, here you can find a video of the demo:

Image with Video thumbail. This will link you to a youtube video

Though your browser is rendering a regular HTML page, it has been created dynamically using Python. What makes it special is that the Python script was executed inside a WebAssembly runtime. In the default example, you are running a Wasm module that includes the CPython interpreter. The current configuration mounts a Python script to reply to the request.

The Python interpreter was compiled using the amazing work from the Fermyon team. You can find the source code in the fermyon/wagi-python repository.

Run your own application

We included more Python examples and some written in Rust. You can find all the instructions in the README to run them. We included the compiled Wasm module for running them, but you can also check the source code of all the examples.

To run an application in mod_wasm, you need to consider:

  • The final output is a Wasm module with WASI interfaces enabled
  • The HTTP headers are available as HTTP_X environment variables
  • The module should print its result on the standard output. The result HTTP headers should be printed at the beginning in a key = value format, one per line, followed by two break lines before the response body

This means that any application that be configured with these constraints can work without any modification. This includes from fully featured applications to small scripts. This means you can run the same applications in the same Apache httpd environment, in a more secure way.

To get a taste for the source code, you have two Rust examples in the repository. We will include more soon!

Isolation and security

One of the questions you may ask yourself is why is this approach better than running the application directly as a service. It is a fair question. The key point here is security and how WebAssembly isolates the running application from the host service.

From the WebAssembly documentation:

WebAssembly describes a memory-safe, sandboxed execution environment [...]

To illustrate this WebAssembly statement, let's describe two different scenarios here:

  • On every request, we run a Python script using a local, native Python interpreter
  • On every request, we set the WASI context and run the same script inside the Wasm runtime

On the local Python interpreter scenario, the script has the same permissions as the Python interpreter. A bug in the code may allow an attacker to read or alter the filesystem in an unexpected way or in the worst case, run arbitrary code on the machine.

On the WebAssembly scenario, even when we run the same vulnerable script, it will not be able to access the host filesystem or run arbitrary code. Wasm capability-based security design ensures that by default, Wasm modules do not have access to host machine resources. Permissions are fine-grained and must be granted manually.

For example, to read a file inside a Wasm module with WASI, you still need to give permissions to read the file when running the module. This strong isolation design reduces the attack surface and mitigates common and dangerous vulnerabilities.

Accessing an arbitrary file demo

We also included a demo to showcase the Wasm capability-based security model. In this example, we reproduce the behaviour we described in the previous section. The server is configured with two different endpoints:

  • A CGI endpoint that runs a Python script with the local Python interpreter
  • A mod_wasm endpoint that runs the same Python script in Wasm

The Python script has a vulnerable query parameter that allows an attacker to read arbitrary files from the filesystem. You can also run this example by following the instructions in the repository.

Apache ❤️ WebAssembly

WebAssembly brings new capabilities to many different environments. The ability to run many languages in different environments with a variety of restrictions, and mitigate common security issues, is one of the capabilities we are most excited about.

Run the same applications and the same Apache httpd environment, in a more secure way

WebAssembly is a huge step forward in securing server side applications. It brings many new benefits not only to cutting edge technologies and frameworks, but also to already existing applications and services. In this article we talk about Python, but Ruby, PHP, Rust, Go, C, C++, and many others are part of it.

We are delighted to bring WebAssembly to a huge community like Apache. This mod_wasm release is our initial one. As such, it is still simple and can be improved in a variety of ways, hopefully with your help!

We have many more features planned, including support for other popular server-side languages as well as additional Wasm runtimes. We welcome your feedback and ideas for improvement as well as your code and documentation contributions.

If you are interested in our work, do not forget to follow us on @vmwwasm to get updates on our projects.

Do you want to stay up to date with WebAssembly and our projects?