Introducing Waxosuit
A secure, cloud-native exosuit for WebAssembly
I’ve been developing back-end systems for a long time. Distributed systems; complicated, resilient back-end applications; and microservices has been my specialty and passion for as long as I can remember. I wrote a book on Cloud Native Go, a book on building Microservices with ASP.NET Core, and dozens of other books on various technologies. But more important than the books is that I’ve been building this stuff and shipping to production for decades, so I’ve made countless mistakes and learned just as many lessons from them.
Frankly, I’m sick and tired of typing. I’m not sick of coding — just typing. This isn’t a complaint about my carpal tunnel (though that is uncomfortable), it’s a complaint about the notion that no matter what I build, when I build it, or how I build it, the vast majority of what I type is redundant, superfluous, or an elaborate copy/paste dance designed to dazzle and distract me from the reality that I am simply lifting, re-purposing, and dragging boilerplate with me wherever I go. That baggage weighs me down, and it weighs down development teams everywhere.
Starting well before I began work on Programming WebAssembly with Rust, I knew WebAssembly had a lot of potential. I was branded a pariah, however, because I felt from the beginning that WebAssembly could provide the most benefit in the cloud — even more so than making browser apps faster and more powerful.
One of the ideas that has been stuck in the back of my head these past two years is that not only can we run WebAssembly in the cloud, but that we can take advantage of its sandbox and so-called limitations to reduce the excessive boilerplate developers drag with them in service of a never-ending list of NFRs (Non-Functional Requirements). Since the first time I really understood how WebAssembly worked, I’ve been convinced that we could harness it to flip the service development effort ratio of logic to ceremony so that developers could return to spending 90% of their time writing pure business logic and only spend 10% of their time on the typical ceremonies required by every new project.
As I continued writing my WebAssembly book and building tons of microservices and functions in Go (and other languages), I was inspired by the de-centralized, JWT-based security model used by NATS 2.0. I knew then that I could take the power of WebAssembly, the security of ed25519 signatures and JWT tokens, the speed and power of a Rust-based Wasm interpreter, and build something that would solve my problems and hopefully the problems of many enterprise developers working in the cloud.
All of these ideas ultimately became the OSS project Waxosuit. I wanted to build an exoskeleton — or exosuit — that would provide secure capabilities to WebAssembly modules. I didn’t want my development teams to have to re-write, copy/paste, or cargo cult stale implementations of NFRs for things like logging, tracing, contextual tracing (e.g. OpenTracing/Jaeger), Application Performance Monitoring (APM), health checking (e.g. live/readiness probes), message broker client wrappers, key-value store client wrappers, HTTP server endpoint wrappers, and so on.
If I had a dime for every time I started a new microservice project that involved copy/pasting (and subsequently updating) a bunch of old wrapper code to deal with the items listed above (especially things like HTTP endpoints and RESTful routing!), I could afford not to care about these problems.
I want developers to be able to rapidly create a new, empty project and then immediately write code that states, “when this happens, execute this business logic, utilizing these loosely coupled, abstract capabilities.” I don’t want to care about whether this service will properly scale to zero (or how it will do so), I don’t want to write code that tightly couples me to a specific cloud provider’s serverless framework, and I’m literally exhausted from spending so much time on the NFRs and barely getting enough time to write and easily test my business logic. Finally, I want to be able to run and test that code anywhere, deterministically.
The radical proposition I put forward is that for the majority of people writing code in the enterprise for the cloud, they might embrace the constraints of operating within a WebAssembly module where capabilities are provided by the host in exchange for better (by default!) security, faster execution time, better container density (and thus higher cost savings), easier deployment, easier testing, and a simpler overall developer experience.
So what does it look like to build a WebAssembly module that runs inside Waxosuit? You can create a new Rust project (a Go guest SDK will be coming soon) with a cargo generate
template, and inside your lib.rs
file, you’ll define a call handler. This pattern of a single call handler/entry point should be familiar to serverless and FaaS developers.
You’re free from the burden of figuring out whether you’re standing up your own HTTP server endpoint, managing ports, managing client connections, or the myriad other details that usually add 6 months of development time after your “hello world” prototype worked. Just declare what types of messages your module handles, provide a handler, and you’re done.
Take a look at a sample that declares a handler for a service that exposes the aggregate details for an Internet of Things sensor:
use guest::prelude::*;call_handler!(handle_call);pub fn handle_call(ctx: &CapabilitiesContext,
cmd: &Command) -> Result<Event> {
match cmd.payload {
Some(ref p) => match p.type_url.as_ref() {
http::TYPE_URL_HTTP_REQUEST =>
get_sensor_details(ctx, p.value.as_slice()),
core::TYPE_URL_HEALTH_REQUEST => Ok(Event::success()),
_ => Ok(Event::bad_dispatch(&p.type_url)),
},
None => Ok(http::Response::bad_request().as_event(true, None)),
}
}
The most important piece of this code is the CapabilitiesContext (I haven’t yet written the Rust docs for this struct). This is a struct that provides the means through which your code interacts with the host runtime. If you want to make a key-value store request, you grab the KeyValueStore
trait by accessing ctx.kv()
. If you want to publish a message, just access the MessageBroker
trait with the ctx.msg()
function. If you want to access a private, enterprise-internal capability that your company has written, you can either provide your own strongly-typed wrapper around the guest SDK, or just access the Raw
capability via ctx.raw()
and send your own protocol buffer messages.
Each capability (Key-Value, HTTP server, HTTP client, Message broker, and so on) is dynamically loaded as a plug-in by the Waxosuit host runtime. This means that the host runtime process is only ever as big as you need it, and only ever loads the capabilities that have been granted to the WebAssembly module via the embedded, signed JWT.
Put another way, Waxosuit is a secure intermediary between your WebAssembly modules and capability plugins, which can be any combination of public OSS or private. An added benefit is developers don’t have to worry about threading — since Waxosuit handles all the dispatching and multiplexing in the host process, everything executed inside the Wasm module is single-threaded.
Lets say your company has some standards around accessing an internal capability. Rather than maintaining a client library in 30 different languages so all your enterprise developers will have access to that proprietary capability (a practice I have seen countless times in my career), you can create a single capability plugin, and expose that securely to WebAssembly modules, regardless of what language the developers used to build them.
With that one Waxosuit feature, you can now entitle individual workloads to use a specific enterprise capability with cryptographically secure verification. You don’t have to worry about which language the developers are using, since everything they build produces an immutable, signed WebAssembly file. This also means your capability team no longer needs to maintain 30 language-specific SDKs (which could have subtle, yet bug-inducing differences), they can just maintain a single Waxosuit plugin.
Waxosuit integrates with Open Policy Agent so that, after verifying the internal integrity of the JWT, it can optionally defer a “can this workload execute?” decision to OPA by passing it the module’s signed JWT.
Waxosuit isn’t going to be for everyone. It isn’t a panacea, nor does it solve every problem of enterprise cloud development. However, my hope is that it will resonate with developers who have run into the same problems I have throughout my career and they will start using it.
I only just recently published everything to GitHub, and the project is still in its infancy. The code is sparsely documented and tested and there are some missing features. I have a ton of work left to do on this, but in the spirit of open source, I wanted to get as many eyes on this as soon as I could. I would love feedback and for people to start exploring potential use cases for it and submitting PRs for where it falls short of its ambitions.
I will be rapidly filling in those gaps over the coming weeks and months, building out the features, docs, and samples. I will also be working on the enterprise registry server for Waxosuit-compliant modules (I’m calling it Gantry, and it will be built entirely out of Waxosuit modules) and would love to work with as many of you on this as possible!
Until then, stay tuned to the Waxosuit twitter for updates.