Exercise 6: Running as root or a non-root user

Objective

Learn how to manage running a container as a given user.

Containers run as root by default

As a normal user, you should not have access to certain files on the system, e.g. the /etc/sudoers file:

> wc /etc/sudoers
wc: /etc/sudoers: Permission denied

However, running a container with a volume mount gives you complete access to the system, because the container runs as root:

> docker run --rm -it --volume /etc:/mnt alpine:3.5 wc /mnt/sudoers
       97       496      3174 /mnt/sudoers

This means you can read, and potentially change, files on the host system that you should not have access to!

To prove this point further, try creating a file on a volume-mounted directory, and see what user it shows as on the host system:

> mkdir tmp
> cd tmp
> touch a-file.txt
> touch another-file.txt
> ls -l
total 0
-rw-rw-r--. 1 centos centos 0 Aug  5 16:12 a-file.txt
-rw-rw-r--. 1 centos centos 0 Aug  5 16:12 another-file.txt
> docker run --rm --volume `pwd`:/mnt alpine:3.5 touch /mnt/alpine-file.txt
> ls -l
total 0
-rw-rw-r--. 1 centos centos 0 Aug  5 16:12 a-file.txt
-rw-r--r--. 1 root   root   0 Aug  5 16:12 alpine-file.txt
-rw-rw-r--. 1 centos centos 0 Aug  5 16:12 another-file.txt

Note that the file created within the alpine container is owned by root!

Using volume-mounts into a container allows you not only to read and write as root, but also to mount directories that you cannot even see otherwise. The volume-mount is executed by the docker daemon, which runs as root, so it can fulfill the mount request even if you wouldn’t be able to see that directory as a normal user.

For example, in /etc/selinux/final there’s a hierarchy of files that users should not be able to see. List the /etc/selinux directory and you’ll see the final directory is protected. Attempt to list it’s contents as a normal user, and you’ll fail:

> ls -l /etc/selinux
total 8
-rw-r--r--. 1 root root  542 Jan 28  2019 config
drwx------. 3 root root   22 Aug  5 13:02 final
-rw-r--r--. 1 root root 2321 Oct 30  2018 semanage.conf
drwxr-xr-x. 8 root root  226 Aug  5 13:02 targeted
drwxr-xr-x. 2 root root    6 Oct 30  2018 tmp
> ls -l /etc/selinux/final
ls: cannot open directory /etc/selinux/final: Permission denied

However, if you know the path you want to examine under that tree, you can use a volume mount to access it in a container:

> docker run --rm -it --volume /etc/selinux/final/targeted/contexts:/mnt alpine:3.5 ls -l /mnt
total 0
drwx------    2 root     root             6 Aug  5 13:02 files

If you don’t find that scary, please go back and read this section again, until you do.

This is why many HPC centres won’t allow you to run docker images on their machines, which are shared among many users. There are alternatives, one of the best is singularity, which is essentially a drop-in replacement for docker which solves the security issues by only allowing you to run as the user you are on the host system.

If you’re only running on your own machines, and nobody else has access to them, there’s not much to worry about. If, however, you’re running a service of some sort (a web server or portal) then you need to consider that someone could exploit bugs in your service to gain access to the host via the container. Yes, it does happen!

The solution, then, is to run your service as a non-root user in the container, and make sure that user cannot escalate their privilege within the container.

Adding a non-root user to an image

You can add users to containers in much the same way as you would with any linux distribution. How exactly you do this is specific to the particular linux flavour you’re using. For the alpine distribution, you’ll need to install the user-management packages. There’s a dockerfile that does this, Dockerfile.user, in the tsi-cc/ResOps/scripts/docker directory of the tutorial repository, note that it creates both a user and a group, because if you don’t specify a group then the root group is used by default:

> cat Dockerfile.user
FROM alpine:3.5

RUN apk update && \
    apk add shadow && \
    groupadd muggles && \
    useradd -ms /bin/sh -G muggles dudley

USER dudley:muggles

Build an image from this, tag it as user. Now try running a container with that image and see if you can see the hidden files:

docker run --rm -it --volume /etc/selinux/final/targeted/contexts:/mnt user
> ls -l /mnt
ls: can't open '/mnt': Permission denied
total 0
> ls -ld /mnt
drwx------    3 root     root            19 Aug  5 13:02 /mnt
> id
uid=1000(dudley) gid=1000(muggles)

You can see that the volume-mount still succeeds, but you can’t see the files, because you don’t have permission. The dudley user has no way to become root within the container, so has no way to cheat. So far so good!

But that’s not the end of the story yet. Docker allows you to specify which user to run as in the container, from the command line. So a user can run the container as root by explicitly asking for it:

> docker run --rm -it --volume /etc/selinux/final/targeted/contexts:/mnt user ls -l /mnt
ls: can't open '/mnt': Permission denied
total 0
> docker run --rm -it --volume /etc/selinux/final/targeted/contexts:/mnt --user root user ls -l /mnt
total 0
drwx------    2 root     root             6 Aug  5 13:02 files

The bottom line is that if the user is allowed to run containers for themselves then they can access pretty much anything on your system as if they are the root user. If they only have access to a service that you are providing, such as a web server, then you can protect your system more by running the service as a non-root user in the container.

Conclusion

Docker has some significant security issues. There are measures you can take to mitigate them, but if the user can run containers on your infrastructure for themselves, there’s no foolproof method to prevent possible abuse.

Best practices

  • consider who has access to the host system when you install docker on a machine. Can you limit the set of users to reduce security exposure?
  • if you’re running a service, that users only access through the web or some other API, there are a few things you should do to protect it:
    • create an unprivileged user in the image, use that user to run the service
    • make sure your container only mounts volumes that you need from the host filesystem
      • be as restrictive as possible, don’t mount more than you need, and never from the system directories
      • mount volumes read-only if you can, to prevent unwanted changes to files
      • if you allow users to upload data, provide separate input and output volumes, don’t let them write in volumes where you store other files