This post is based on material from Docker in Practice, available on Manning’s Early Access Program. Get 39% off with the code: 39miell

Introduction

If you’re unsure of how to secure Docker for your organisation (given that security wasn’t part of its design), I thought it would be useful to itemise some of the ways in which you can reduce or help manage the risk of running it.

The Two Sides

In this context there are two sides to security from the point of view of a sysadmin, ‘outsider’ and ‘insider’:

‘Outsider’ – preventing an attacker doing damage once they have access to a container

‘Insider’ – preventing a malicious user with access to the docker command from doing damage

‘Outsider’ will be a familiar scenario to anyone who’s thought about security.

‘Insider’ may be a new scenario to some. Since Docker gives you the root user on the host system (albeit within a container), there is the potential to wreak havoc on the host by accident or design. A simple example (don’t run this at home kids – I’ve put a dummy flag in anyway) is:

docker run --dontpastethis --privileged -v /usr:/usr busybox rm -rf /usr

Which will delete your host’s /usr folder. If you want people to be able to run docker, but not with the ability to do this level of damage, there are some steps you can take.

Some measures, naturally, will apply to both. Also some are as much organisational as technical.

Insiders and Outsiders

Run Docker Daemon with –selinux

If you run your Docker daemon with the –selinux flag it will do a great deal to prevent those in containers you from doing damage to the host system by creating its own security

This can be set in your docker config file, which usually lives in /etc under /etc/docker or /etc/sysconfig/docker

Defending Against Outsiders

Remove capabilities

Capabilities are a division of root into 32 categories. Many of these are disabled by default in Docker (for example, you can’t manipulate iptables rules in a Docker container by default)

To disable all of them you can run:

docker run -ti --cap-drop ALL debian /bin/bash

Or, if you want to be more fine-grained start with nothing, and then re-introduce capabilities as needed:

docker run -ti --cap-drop=CHOWN --cap-drop=DAC_OVERRIDE \ --cap-drop=FSETID --cap-drop=FOWNER --cap-drop=KILL \ --cap-drop=MKNOD --cap-drop=NET_RAW --cap-drop=SETGID \ --cap-drop=SETUID --cap-drop=SETFCAP --cap-drop=SETPCAP \ --cap-drop=NET_BIND_SERVICE --cap-drop=SYS_CHROOT \ --cap-drop=AUDIT_WRITE \ debian /bin/bash

Run ‘man capabilities’ for more information.

Defending Against Insiders

The main problem with giving users access to the docker runtime is that they could run with –privileged and wreak havoc, even if you have selinux enabled.

So if you’re sufficiently paranoid that you want to remove the ability for users to run Docker, some problems arise:

– How to prevent users from effectively running docker with privileges?

– How to allow users to build images?

udocker is a highly experimental and as-yet incomplete program which only allows you to run docker containers as your own (already logged-in) user id.

It’s small enough for security inspection (just a few lines of code: https://github.com/docker-in-practice/udocker/blob/master/udocker.go, forked from https://github.com/ewindisch/udocker) and potentially very useful where you want to lock down what can be run.

To run:

$ git clone https://github.com/docker-in-practice/udocker.git $ apt-get install golang-go $ go build $ id uid=1001(imiell) gid=1001(imiell) groups=1001(imiell),27(sudo),132(docker) ./udocker fedora:20 whoami whoami: cannot find name for user ID 1001 $ ./udocker fedora:20 build-locale-archive permission denied FATA[0000] Error response from daemon: Cannot start container 6ba3db7094a20c9742a3289401dcf915e03a2906d4e44dbbed42e194de13fd44: [8] System error: permission denied

Compare normal docker:

$ docker run fedora:20 id uid=0(root) gid=0(root) groups=0(root)

If you then lock down the docker runtime to be executable only by root, you disable much of docker’s attack surface.

Docker build on audited server (and private registry)

One solution to allow you to build without access to the docker runtime may be to allow people to submit Dockerfiles via a limited web service which takes care of building the image for you.

It’s relatively easy to knock up a server that takes a Dockerfile as a POST request, builds the image with a web framework such as python-flask, and then deposits the resulting image for post-processing. Or you could even use email as a transport, and email them back a tar file of the checked image build :)

You can also do your static Dockerfile and image checking here before allowing promotion to a privately-run registry. For example you could:

Enforce USERs in images

If you have a build server that takes a Dockerfile and produces an image, it becomes relatively easy to do tests.

The first static check I implemented was checking that the image had a valid :

– There is at least one USER line

– The last USER line is not root/uid0

Run in a VM

The Google approach. Give each user a locked-down VM on which they can run and do what they like, and define ingress and egress at that level.

This can be a pragmatic approach. Some will object that you lost a lot of the benefits of running Docker at scale, but for many developers running tests or Jenkins servers and slaves this will not matter.

Future Work

User namespaces

Support for the mapping of users from host to container is being discussed here:

https://github.com/docker/docker/issues/7906

Further Reading

There’s lots more going on in this space. Here’s some highlight links:

Comprehensive CIS Docker security guide

Docker’s security guide

GDS Docker security guidelines

Dan Walsh (aka Mr SELinux) talk on Docker security

This post is based on material from Docker in Practice, available on Manning’s Early Access Program. Get 39% off with the code: 39miell