How many times have I heard, “Docker is not safe !”
It can be true…unless you configure the tool properly !

I invite you to dive into the following features of Docker : User Namespaces activation and UID/GID translation.

Target audience: System administrators, Docker users
By Sébastien Aucouturier, Consultant DevOps @ ObjectifLibre

Securing Docker with userns-remap functionality

Without enabling these features, what is the problem?

Easily, a non-root user can use root container access to modify the host.
Here is how to do it:

$ whoami
seb
$ echo "#mytest" >> /etc/hosts
bash: /etc/hosts: Permission denied

# Run a container and mount host directory /etc onto docker /root/etc
$ docker run --rm -v /etc:/root/etc -it busybox

# Make some change on /root/etc/hosts
root@34ef23438542:/# echo "#mytest" >> /root/etc/hosts

# Exit from the container
root@34ef23438542:/# exit

# Check /etc/hosts
$ cat /etc/hosts

As you can see, it is incredibly easy !
But with the –userns-remap option, Docker allows you to avoid this security flaw.

A little refresher : Basic principle of a container

Docker, like all containerization solutions, uses the core of the operating system of the host, a container does not embed personalized or additional kernel.
All containers running on a machine share the host kernel.

And Docker uses linux kernel features, such as namespaces and cgroups to partition and isolate the different containers.

To avoid the pitfall talk above, we will use the Namespace USER feature and change its default behavior.

Enabling User Namespaces

# Create a user called "dockremap"
$ sudo adduser dockremap

# Setup subuid and subgid
$ sudo sh -c 'echo dockremap:500000:65536 > /etc/subuid'
$ sudo sh -c 'echo dockremap:500000:65536 > /etc/subgid'

Then, we will add the –userns-remap=default option to the docker daemon.

With a systemctl environment to handle services, the file you must modify is /etc/sysconfig/docker

OPTIONS="--userns-remap=default"

Or set up via the configuration file: /etc/docker/daemon.json containing :

{
"userns-remap": "default"
}

Don’t forget to restart the daemon after editing the file.

That’s it !

Note:
If you are using CentOS 7, User Namespaces are not enabled in the kernel by default.
You can enable it by executing the following command and reboot the system.

$ sudo grubby --args="user_namespace.enable=1" \
--update-kernel=/boot/vmlinuz-3.10.0-XXX.XX.X.el7.x86_64

$ sudo echo 31096 > /proc/sys/user/max_user_namespaces

Checking that the User Namespaces are working properly

If everything is set up correctly, you should no longer be able to change the files in the host’s /etc directory from the container.
Let’s test this.

# Create a container and mount host1's /etc to container's /root/etc
$ docker run --rm -v /etc:/root/etc -it busybox

# Check the owner of files in /root/etc, which should be "nobody nogroup".
root@d5802c5e670a:/# ls -la /root/etc/hosts
-rw-r--r-- 1 nobody nogroup 1153 Apr 16 10:28 /root/etc/hosts

# Try creating a file in /root/etc
root@d5802c5e670a:/# touch /root/etc/test
touch: cannot touch '/root/etc/test': Permission denied

# Try deleting a file
root@d5802c5e670a:/# rm /root/etc/hostname
rm: cannot remove '/root/etc/hostname': Permission denied

Cool, that is exactly what it was supposed to do ! But why ?

A little further on with the User NameSpaces

Docker configuration

When you configure Docker to use the feature userns-remap, you must specify a valid user and/or group in the system:

$id -u USER
1000
$ getent group USER
USER:x:1000:

The docker daemon will be started with the –userns-remap USER option.

Remarks:

– If you specify “default” instead of your ‘USER’, a user and a group “dockremap” are created and used for this purpose.
– Don’t forget to restart the daemon after editing the file.

System Configuration

In order to set up UID/GID translation, you need to modify the two files: /etc/subuid and /etc/subgid.
Each file works the same way, but one concerns the ID range user and the other the Group ID range.

Consider the following entry in /etc/subuid:

testuser:231072:65536

The root user (UID 0) of the container will be mapped to the system with at UID 231072, a file with UID 33 in the container will have UID 231105 at the host (231072 + 33 = 231105), with the translation set for a consecutive range of 65536 IDs.

Notes:
– “testuser”, here corresponds to the value assigned to userns-remap: “ẗestuser”

It is possible to assign several ranges to a given user or group. by adding several non-overlapping mappings.
However, Docker only uses the first five mappings, in accordance with the kernel limits set to only five entries in /proc/self/UID_map and /proc/self/GID_map.

testuser:200000:20
testuser:250000:65500

UID Docker 0 <=> UID 200000 on the host
UID Docker 19 <=> UID 200019 on the host
UID Docker 20 <=> UID 250000 on the host

Let’s give it a try

$ cat /etc/subuid
seb:1000:1
seb:100000:65535

cat /etc/subgid
seb:982:1
seb:100000:65535

#Run a container
$ docker run --rm -it -v "$(pwd)/test:/test/" -it busybox

root@02a5bcc1757c:/#cd /test
root@02a5bcc1757c:/test#touch dockerrootfile.
root@02a5bcc1757c:/test# ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:25 dockerrootfile

# as file belongs to root: UID=GID=0
root@02a5bcc1757c:/test# ls -ln
total 0
-rw-r--r--. 1 0 0 0 Jun 11 16:25 dockerrootfile

#Check it on the host
ls - ln
total 0
-rw-r--r--. 1 1000 982 0 Jun 11 18:25 dockerrootfile

The UID 0 and GID 0 file in docker is assigned UID 1000 and GID 982.
On the host, using /etc/subuid and /etc/subgid, remember?

And as seen on our host UID seb = 1000, GID of docker = 982, we find the explanation:

$ ls -l
total 0
-rw-r--r--. 1 seb docker 0 Jun 11 18:25 dockerrootfile

Now let’s try with the user: www-data (UID 33) in the container

root@02a5bcc1757c:# chmod 777 /test
root@02a5bcc1757c:# su -s /bin/sh www-data
www-data@02a5bcc1757c:# touch www-data-file
www-data@02a5bcc1757c:/test$ ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:36 rootfile
-rw-r--r--. 1 www-data www-data 0 Jun 11 16:38 www-data-file

www-data@02a5bcc1757c:/test$ ls -ln
total 0
-rw-r--r--. 1 0 0 0 Jun 11 16:36 dockerrootfile
-rw-r--r--. 1 33 33 0 Jun 11 16:38 www-data-file

# on host:
$ ls -l
total 0
-rw-r--r--. 1 seb docker 0 Jun 11 18:36 dockerrootfile
-rw-r--r--. 1 100032 100032 0 Jun 11 18:38 www-data-file

ls - ln
total 0
-rw-r--r--. 1 1000 982 0 Jun 11 18:36 dockerrootfile
-rw-r--r--. 1 100032 100032 0 Jun 11 18:38 www-data-file

Once again the UID and the GID have been modified in accordance with the parameters of the /etc/subuid and /etc/subgid files.

Let’s create a file on the host machine :

# on host
$ touch www-data-file-from-host
# in container :
www-data@02a5bcc1757c:/test$ ls -l
total 0
-rw-r--r--. 1 root root 0 Jun 11 16:36 rootfile
-rw-r--r--. 1 www-data www-data 0 Jun 11 16:38 www-data-file
-rw-r--r--. 1 root nogroup 0 Jun 11 16:41 www-data-file-from-host

In the container, it belongs to root:nogroup as expected.
If on the host it belongs to seb:seb, in the container : the UID 1000 of seb becomes 0: root, seb’s GID 1000 becomes 65534: nogroup because not included in ID translation.

This is the end

That’s it ! After this read, I hope that you will be able to understand how to secure docker’s containers.
As a reminder, a container should never be launched with the default root user, but with a user specific to the service being deployed.

To complete this article, we advise you to read the book “ContainerSecurity” by Liz Rice: https://info.aquasec.com/container-security-book .

If you use an orchestrator like Kubernetes, using userns-remap is not ‘yet’ implemented, but a proposal in this direction is under study:
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/node-usernamespace-remapping.md

In the meantime, other security mechanisms have been implemented through PodSecurityPolicies and OPA (Open Policy Agent) :

https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces
https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/

But that’s another story.