Never has it been so important to protect the fundamental security of your software. With more of our everyday lives happening on the internet, and with exponentially increasing amounts of data being exchanged between devices and services, protecting the people who use your software (not to mention your business) is, well, really freaking important. The idea of capability-based computing is something that the Suborbital team has been exploring heavily since work started on our Atmo server application environment last year, and this post will go into detail about what it is, why it’s important, and how we achieve it.
Capability-based computing is the ability to selectively grant components in your application the ability to perform specific tasks. You can think of it like getting a key card that can only access certain rooms in a building. Until recently, running software in the cloud has meant giving your code access to a huge swath of the “outside world” because it’s likely running in a Virtual Machine (VM) or a Container (such as Docker). These virtualisation technologies grant a fair amount of protection for the host (the actual operating system and hardware running your software), but it doesn’t do much to prevent the code running inside the container from getting into all sorts of trouble outside of the host.
By adopting the practice of capability-based computing, we give individual parts of our application the ability to access the exact things they need to get their jobs done. This may sound familiar, as you may have found yourself configuring a network or firewall to allow certain machines to access particular resources, but capability-based computing goes a step further by restricting the code at a much lower level. If you use the network to restrict access to sensitive resources, then malicious code is being stopped somewhere in the middle of its attempt to do something nasty. The code is allowed to make system calls, open network connections, and begin executing its nefarious plan, only to be stopped partway to its destination by a mechanism completely outside of the VM or container (or even physical host) that it’s running on. That’s a fair bit of leeway that we shouldn’t be allowing!
Contrast this with capability-based computing, where the program itself cannot even attempt to execute a malicious task. This is made possible by the unique security properties of WebAssembly’s sandboxing architecture, which uses a deny-by-default approach to executing code. When you run a WebAssembly module, it is executed within a ‘runtime’ such as wasmTime or WasmEdge, and any time the code wants to do something with the “outside world” (i.e. outside of the runtime), the request is funnelled through the runtime and handled by the host.
In our Atmo server application environment (and the underlying Reactr scheduler), capabilities are granted to your functions using the
Runnable API. We went to great lengths to develop a capability system that is secure and flexible by only granting capabilities to functions as needed. For example, if you have a function that interacts with your payment provider, you may enable the HTTP capability and add “api.paymentprovider.com” as an allowed host. This would allow the function to make HTTP requests to that host, but no others.
We achieve this by creating a per-invocation
context each time you run a function. Atmo will use your application directive to determine exactly which capabilities a particular function has been given access to, configure the capability (such as the HTTP client) based on your provided rules, and only then will it bind the context to the WebAssembly runtime and execute the function. When the function makes an HTTP request, it is passed to the capability, evaluated to determine if it passes the configured rules, and then allowed to execute. Atmo does this in an extremely efficient manner, and because every single “outside world” action is passed through the bound context, malicious activity is stopped before it even has a chance to start up. Atmo is dynamically enhancing the WebAssembly sandbox to make it smarter, safer, and easier to use.
Using these techniques to build more secure software will take some adjustment. The inherent benefits of architecting your software around small, composable modules, and controlling fine-grained access for those modules is one of the best ways to enforce the principles of least privilege and zero-trust, so we think the long-term benefits far outweigh the cost of changing our mindsets and building software using the newest and most secure development techniques.
We believe this new style of secure computing is going to be essential in keeping everyone safe on the internet, preventing malicious actors from gaining harmful access to your systems, and improving the security of the software supply-chain by limiting the impact of dependencies that have been illicitly tampered with. If you have any questions about these practices, reach out to us on Twitter. If you want to learn more about next-generation web service development, visit our homepage and sign up for the Launch Pad newsletter below!