Some Docker Security Basics
A few weeks ago, I finished my bachelor thesis about a security analysis of the Docker infrastructure and last week I published the slides to the presentation. Since probably not many people will read the complete thesis, I thought it would be useful to give a short overview of how to improve the security of Docker with a few simple steps. Of course, this overview does not cover all points that can be found in the thesis, so if you are more interested, please refer to the publication.
Host Machine
First of all, Docker does not provide complete isolation! This fact is even mentioned in its documentation:
One primary risk with running Docker containers is that the default set of capabilities and mounts given to a container may provide incomplete isolation, either independently, or when used in combination with kernel vulnerabilities.
Therefore, the security of Docker begins with securing the host machine.
Keep the Linux Kernel and the host machine up-to-date.
The Linux Kernel is one of the most complex software ever made which can be seen, among other things, from the fact that it comprises over 25 million lines of code. Security holes in the kernel are not as rare as one might think due to the thousands of eyeballs looking at the code on a daily basis. For example, between January 2010 and March 2011, 141 vulnerabilities in the Linux Kernel were reported to CVE databases. Furthermore, recent research towards hardware bugs (e.g., Meltdown, Spectre, ...) are often updated with Kernel patches. Therefore, keep your host system up-to-date!
Keep Docker (and its components) up-to-date
Docker is equally vulnerable to security flaws. For example, CVE-2014-6408 allowed processes inside containers to bypass the isolation provided by Docker. Furthermore, CVE-2015-3630, CVE-2016-8887, or CVE-2016-8867 lead to access to certain directories on the host. Therefore, apply the patches for all components of Docker (Daemon, runc, ...).
Check log files
Log files provide an insight in (long-term) trends. Check them regularly.
Use a Mandatory Access Control (MAC) mechanism like AppArmor or SELinux
In addition to standard DAC features of Linux, MAC tools can provide an additional layer of defense. For instance, specify profiles for certain containers with AppArmor which helps to restrict its permission. In Docker's documentation, there is a helpful article that describes the basics of using AppArmor to protect containers. Moreover, consider to blacklist certain Linux system calls with seccomp.
Isolate containers with namespaces
You can (and should) your containers with the least amount of privileges needed to fulfill their task. Using namespaces, you can instruct a container to run as an unprivileged user on the host machine. There is a good article in the documentation which covers everything you need to know about isolating containers with namespaces.
Docker Engine/Daemon
Docker itself can be misconfigured in many ways that lead to a vulnerable system.
Don't expose the daemon
Docker Daemon receives commands from Docker Client. The client is usually the command line tool when you type, e.g., docker run -i -t ubuntu /bin/bash. This command is sent via a REST API and using a Unix socket to the daemon which often (but not always) runs on the same machine as the client. Both, the client and the daemon, often run with root privileges. Therefore, these components need to be treated very carefully. For example, if you want to bind the daemon to a TCP port, always enable TLS authentication. Otherwise, everyone in the network (maybe even the whole internet) may send messages to your daemon which runs with high privileges!
[Update 2019-08-03] Rootless mode
A new rootless mode was introduced in Docker 19.03. I have no experience at all with this new feature, but the documentation looks very promising.
Docker Images
Images are the basis for every container. When you create and use vulnerable images, your containers will also be insecure.
Use the USER command in Dockerfiles
Earlier in this post, user namespaces were mentioned as one measure to decrease the privileges of containers. A similar result can be achieved by using the USER command in Dockerfiles if your process inside the container does not need root privileges. First, create a user and a group with e.g., RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres. Then, use USER postgres:postgress to change the user and group of the container. Note that you need to specify a group, too:
Warning: When the user doesn’t have a primary group then the image (or the next instructions) will be run with the root group.
Use minimal base images
Minimal base images (like Alpine Linux) provide less tools for attackers which already took control over the process inside a container. With fewer tools, it is harder for attackers to escalate their privileges.
Only use official images from Docker Hub
Official images are scanned against CVE databases and are regularly maintained. In contrast, unofficial images are not scanned and, often, are never updated. Of course, official images are never immune to vulnerabilities but if you can, you should always prefer official images.
Don't use hardcoded secrets
Often, images are shared with Docker Hub. If you use hardcoded secrets such as passwords or private keys, they inevitably will be exposed if you push your image to Docker Hub. Therefore, consider using a solution like Docker Secrets.
Image distribution and deployment pipelines
Docker's security must always be examined in the context of its environment.
Use Docker Content Trust
Images are transferred in untrusted networks like the internet. Therefore, one needs mechanisms to ensure the integrity and authenticity of images. This can be achieved using Content Trust.
Protect each account and service in the deployment pipeline
Dockerfiles are often pushed to Github, from where they are pulled by a service that builds images using the dockerfiles. Then, another service might be responsible for starting and managing containers with the resulting images. Deployment pipelines often consist of several components and services. Every single component must be secured, with particular attention being paid to the security of accounts. Use strong passwords and if possible 2FA, because a single vulnerability can compromise the whole chain.