Introduction
We will introduce MirageOS, the target audience, and the scope of this handbook.
MirageOS
MirageOS is a library operating system, where for each service a separate binary - a so-called unikernel - is used. MirageOS is not a generic purpose operating system. Each unikernel has a single purpose and is executed as a virtual machine.
Each unikernel is tailored to its need, and only contains the pieces of code that are strictly required for its operation: our firewall unikernel doesn't include user management, a file system. The firewall is only a single process: there is no process management and no userland vs kernel space process switching, and also no virtual memory included.
A MirageOS unikernel is minimal in size - this means that the resource consumption is low since only a tiny binary is kept in memory, and no unnecessary code is executed (i.e. a cronjob indexing the files on your file system).
In addition, MirageOS unikernels are developed in the memory-safe and statically typed programming language OCaml, which catches lots of bugs at compile time (static type system) and reduces the attack vectors (spatial and temporal memory safety issues - buffer overflow and underflows).
It is crucial to have a programming language that enforces memory safety in the setting with single address space, otherwise an attacker (from a bug in one library) could access all the memory of the other libraries. MirageOS comes with some C code, which is compiled with defense-in-depth features.
The benefits of MirageOS unikernels are (a) lower resource consumption, (b) smaller attack surface, (c) avoidance of memory safety attacks, and (d) reduced operational complexity.
Target audience and requirements
The reader of this handbook are persons who want to operate MirageOS unikernels. There won't be any OCaml code in this handbook. You don't need an OCaml compiler for this handbook. Only the operational aspects are discussed, for example how to setup networking.
Of course, you as an OCaml or MirageOS developer can use your custom unikernel instead of the stock ones provided.
You will need a hypervisor, supported are Linux kvm, FreeBSD BHyve, and OpenBSD VMM.
It is suggested to have a x86 laptop or server with one of the operating systems and the required hardware support for virtualization.
Within the handbook, we will link to reproducible binaries of the specific unikernels, available from builds.robur.coop. The unikernels are built on a daily basis using the latest releases of OCaml packages.
Our focus on running MirageOS as a hardware-supported virtual machine (also
named hvt
) has multiple reasons:
- from a security point of view, it leverages the CPU virtualization features, and only uses a tiny API to the host operating system,
- we are able to ship binaries that run on either of the hypervisors, no need for custom binaries depending on the hypervisor.
- it is our preferred target, and we run it since nearly a decade!
Other MirageOS targets
MirageOS supports more targets we will only discuss briefly in this section. If you want to read more, take a look at the Solo5 documentation
Unix
The default target of MirageOS for development is unix -- this uses the normal network stack by default (via the sockets API). It is nice for development, but security-wise it is not a good solution for deployment.
Linux seccomp
The Linux seccomp target uses restrictive seccomp rules and allows only a few
system calls to be executed. This does not require a hypervisor, and can be
executed similar to a container. From a security perspective, the attack surface
is slightly bigger - it is the seccomp implementation. From the network
operations perspective, this is the same as what we discuss in this handbook.
Since we don't provide pre-built unikernel binaries for seccomp, you'll need
to compile them yourself. And instead of executing solo5-hvt
, you need to run
solo5-spt
.
Xen
MirageOS was originally developed by some people who also worked on Xen. Xen is a great hypervisor, but very different from the modern ones, which rely on CPU features. The Xen target is used with MirageOS, but the network setup is very different, thus we won't focus on that in this handbook.
Muen separation kernel
The Muen separation kernel is developed in Ada/SPARK - which includes a verification framework. It is a great project, and if you plan to deploy high-assurance solutions, you should dive deeper into Muen. Since its operation and configuration is custom, we won't discuss it further in this handbook.
Virtio
Virtio allows to run virtual machines on the cloud, where the hypervisor is managed for you by some company. This allows to deploy MirageOS onto the cloud. Since the configuration again is very different, we don't go into further details. But we plan to write a chapter on virtio for this handbook.
Conclusion
We briefly discussed the different MirageOS backends, and have the focus on the hypervisor backend.
This handbook is not complete, suggestions are welcome. The initial chapters is focused on network setups that were encountered when testing unikernels, e.g. at our annual MirageOS retreat.
Please file suggestions as an issue on GitHub, or a pull request.
Hello, networked world
In this chapter we will install everything necessary to run a unikernel, run the unikernel, and communicate with it from the host system.
Tender
To get started with a networking unikernel, you first need to get the solo5 tender. This is the executable that runs in the host operating system, allocates the resources for the unikernel, and handles the hypercalls. Commonly used is qemu, or bhyve - but these are pretty large binaries including emulation for e.g. a floppy disk drive. We have very minimal binaries to run as tender.
Debian and Ubuntu
For Debian and Ubuntu systems, we provide package repositories. Browse the
apt package repository whether your
distribution is periodically built, and add it to /etc/apt/sources.list
. We
use "ubuntu-20.04" as an example. Please
open an issue if your
distribution is not supported.
$ curl -fsSL https://apt.robur.coop/gpg.pub | gpg --dearmor > /usr/share/keyrings/apt.robur.coop.gpg
$ echo "deb [signed-by=/usr/share/keyrings/apt.robur.coop.gpg] https://apt.robur.coop ubuntu-20.04 main" > /etc/apt/sources.list.d/robur.list # replace ubuntu-20.04 with e.g. debian-11 on a debian buster machine
$ apt update
$ apt install solo5
FreeBSD
We also provide a package repository for FreeBSD. Please open an issue if your release is missing.
$ fetch -o /usr/local/etc/pkg/robur.pub https://pkg.robur.coop/repo.pub # download RSA public key
$ echo 'robur: {
url: "https://pkg.robur.coop/${ABI}",
mirror_type: "srv",
signature_type: "pubkey",
pubkey: "/usr/local/etc/pkg/robur.pub",
enabled: yes
}' > /usr/local/etc/pkg/repos/robur.conf # Check https://pkg.robur.coop which ABI are available
$ pkg update
$ pkg install solo5
Other distributions
For other distributions and systems we do not (yet?) provide binary packages. You may be able to find a "solo5" packate as part of your distribution.
Source installation
You can also download the latest source release and compile it (the latest release is 0.8.1 at the time of writing):
$ curl -o solo5-v0.8.1.tar.gz -fsSL https://github.com/Solo5/solo5/releases/download/v0.8.1/solo5-v0.8.1.tar.gz
$ tar xzf solo5-v0.8.1.tar.gz
$ cd solo5-v0.8.1
$ ./configure.sh
$ make V=1
$ sudo make install
Get your Unikernel
Download the basic website unikernel binary.
$ curl -o website.hvt -fsSL https://builds.robur.coop/job/static-website/build/latest/main-binary
Run your unikernel
The unikernel requires a network interface for communication. You can think of
it as a network cable that you need to plug into the unikernel. As usual, a
cable has two ends -- one that we plug into the unikernel, and the other we
have at the host system. On Linux/Unix systems, such virtual cables are
implemented by a network device called tap
. You need to create one on the
host system, and configure it, to communicate with the unikernel guest system,
Linux:
$ sudo ip tuntap add tap0 mode tap
$ sudo ip tuntap set dev tap0 up
FreeBSD:
$ doas sysctl net.link.tap.up_on_open=1
$ doas ifconfig tap create #will return the device name, i.e. tap0
Executing the unikernel:
$ sudo solo5-hvt --net:service=tap0 -- website.hvt
You should see the output of the unikernel starting. To communicate to the website unikernel, you can use a web browser. But hold on, we first need to configure IP connectivity on the host system. We do that by assigning an IP address to the configured tap interface:
Linux:
$ sudo ip addr add 10.0.0.1/24 dev tap0
FreeBSD:
$ doas ifconfig tap0 10.0.0.1/24
From the command-line, you should now be able to communicate with the unikernel:
$ ping -c 2 10.0.0.2
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.052 ms
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.165 ms
And from the web browser as well, type "http://10.0.0.2" as URL.
Communication to/from external Internet
What are post-routing and pre-routing rules and why do I need them?
note: pre-routing only on external traffic (sysctl & post-routing rule) vs local
How can the unikernel communicate to the external world? An example may be the traceroute unikernel
How can the external world communicate to the unikernel?
Bridges
When multiple unikernels are deployed, a bridge interface may be useful. Then they can talk to each other
Also, in a server setup a bridge can be used with the physical interface attached to it.
Albatross
What is it? It sets up the tap device for you :) It doesn't setup firewall rules, though
Virtio
How to use virtio with MirageOS
DHCP
Describe and explain a setup with DHCP.
Open questions
- How to run hvt on windows (WSL)?
- How to run unikernels on macOS
- How to test virtio on a cloud provider without paying money?