Rootless Docker

This article is a brief account of Rootless Docker (the feature introduced in v19.03).

...
Benson KuriaPublished on

Namespaces in Linux

Namespaces allow for the Linux kernel to arrange resources in such a way that every process uses its own set of resources without interfering with the resources of another process. Processes are run on separate resources not only for efficiency but also for security reasons. The namespaces allow for container technologies such as Docker. Docker makes use of the namespaces we discussed above to create a workspace that is isolated called a container. Docker works by creating namespaces set when the user runs a container. Each workspace that is created runs on a separate namespace and is limited to only that namespace. User namespace allows the process to gain root privileges within the namespace.

What is the role of a Rootless Docker?

Before introducing the rootless docker, users with access to docker could get root privileges to mount file systems or create namespaces by connecting to the Docker engine. This exposed the system to malicious attacks because the user could now bypass the system’s audit feature. With rootless Docker, the Docker engine can now be run without the root user and thus reduces the risk of attack by limiting access to root user privileges. In sum, a rootless docker allows the running of containers and the Docker daemon as a non-user to reduce the security vulnerabilities in the container runtime and the daemon.

How does it work?

The rootless Docker works by taking advantage of user namespaces. The user namespace maps the range of user IDs in such a way that the root user that is in the inner namespace maps to an unprivileged range in the parent namespace. The Docker engine provides --userns-remap flag to support the corresponding capability and thus provide better security to the container. The rootless Docker imitates this same process except for the Docker daemon being run in the remapped namespace.

Functionality in a Rootless Docker

An unprivileged user can do the following:

● Perform iptables rule management ● Create network namespaces ● Perform iptables tcpdump

However, the containers they create cannot connect to the internet because they cannot create veth pairs across the containers and host. To solve this, the rootless Dockers uses usermode network (SLiRP) to connect TAP device to an unprivileged user namespace.

Benefits of a Rootless Docker

The rootless Docker allows a non-root user to run Docker daemon, including the containers, on the host. Even if this is compromised the user will still not be able to gain the root user privileges. In the presentation hardening Docker daemon with rootless mode by Moby and BuildKit maintainer Akihiro Suda, he states that this does not completely insulate the Docker but greatly mitigates the risk of an attack. With the rootless Docker, an attacker would not be able to access the files owned by other users, perform ARP spoofing, or use undetectable malware to modify firmware and kernel.

Installation of Rootless Docker

You can get the installation script here. Ensure that you are not a root user while running the script.

Run this code:

$ curl -fsSL https://get.docker.com/rootless | sh

This will show the environment variables:

# Docker binaries are installed in /home/testuser/bin
# WARN: dockerd is not in your current PATH or pointing to /home/testuser/bin/dockerd
# Make sure the following environment variables are set (or add them to ~/.bashrc):
 
export PATH=/home/testuser/bin:$PATH
export PATH=$PATH:/sbin
export DOCKER_HOST=unix:///run/user/1001/docker.sock
 
#
# To control docker service run:
# systemctl --user (start|stop|restart) docker
#

Verify the rootless container

To verify that you now have a rootless container, perform the following steps. Run this code:

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ /home/moby/bin/dockerd-rootless.sh --experimental --storage-driver vfs

Run the following code in a different window:

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ docker version
$ docker run -d -p 8080:80 nginx
$ curl localhost:8080

Test the network connection performance

$ docker run  -it --rm --name=iperf3-server -p 5201:5201 networkstatic/iperf3 -s

The next step is testing the bandwidth of the network across the containers.

$ SERVER_IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" iperf3-server)
$ echo $SERVER_IP
 
$ docker run -it --rm networkstatic/iperf3 -c $SERVER_IP

Test the connection between the host and the containers.

$ HOST_IP=$(hostname --ip-address)
$ echo $HOST_IP
 
$ docker run -it --rm networkstatic/iperf3 -c $HOST_IP

Conclusion

The introduction of Rootless Docker will improve security in Docker daemon and container runtime. It is now harder to gain root user privileges, which can easily be used by an attacker to modify firmware, alter other users’ files, or perform ARP spoofing. This was all realized by taking advantage of namespaces. While this does not completely protect the Docker daemon and container, it is a big step forward and will lock out would-be attackers.