by Toul DeGuia-Cranmer, DevOps Engineer at HP Inc. in Houston
Docker is now all the rage in the field of containerization and as a consequence is a target for attack. Although Docker does have some security defaults set right out of the box, those defenses may not be enough to prevent attacks. This article explains how to use Docker Bench for Security to tweak Docker settings in keeping with recognized best practices.
Docker Bench for Security is Docker's own script for checking code against dozens of best practices, including those for security. There are two main ways to use Docker Bench for Security, first to verify that the installation of Docker is following best practices, and second to confirm that each container running in the host is following best practices.
In this article we will go over both uses, starting with the configuration of Docker in a host OS.
I. Using Docker Bench Security to configure Docker to best practices
The machine you are using to view this article is probably Windows based, but most cloud-based microservices live in Linux. To avoid having to set up a server or virtual machine, I use Docker Playground so everyone can follow along.
Let's start by signing in.
Now that the terminal is active, let's check which Linux version is installed:
$ cat /etc/os-release
Great -- exactly the host OS we wanted to test.
Next, use the following command to run docker-bench-security in a container against the host.
If the browser doesn't let you use copy and paste -- which in my case it did not -- it may be best to stuff this next command (all on one line):
docker run -it --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /var/lib:/var/lib \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/lib/systemd:/usr/lib/systemd \
-v /etc:/etc --label docker_bench_security \
docker/docker-bench-security
...into a 'name_of_script.sh' file like this:
$ vim dockerBench.sh
...and then run it via $bash name_of_script.sh
If you can copy and paste, then just copy this chunk of code into the terminal and press enter.
Results should look like this:
Note: this may change in the future, if this Alpine playground is updated.
Let's address the [WARN] 1.5-1.7 and 1.11 that is triggered because the host OS does not have auditing software installed to track kernel-level processes.
To address this, install audit
. Audit is a Linux access monitoring and accounting subsystem that logs system operations at the kernel level. (If you are familiar with the Ubuntu flavor of Linux, then it is the same as auditd
. Both enable the auditing of Docker's files, directories, and sockets.)
Using Alpine's package manager, add Audit.
$ apk add audit && apk update
Now, it's time to add the audit.rules
$ vim /etc/audit/audit.rules
It will be a blank file, so go ahead and add the following:
-w := Tells Audit to watch the specified file or directory
-p wa := Tells Audit to log or write any changes to those files
Of course, you can add more if you wish.
Next, let's add openrc to our playground in order to restart the system.
$ apk add openrc --update
Restart the system so that audit is in place.
$ rc audit restart
That should do the trick. Don't worry if it says service not found
. This is due to the nature of the environment.
Run Docker Bench again.
$ bash dockerBench.sh
Now we see that those warning messages have been handled by the addition of the auditing tool.
Let's handle the next section of errors [WARN] 2.1, 2.4, 2.8, 2.11-.12, 2.14-.15, 2.17-.18
Open the daemon.json
$ vim /etc/docker/daemon.json
Add the following to the file:
// daemon.json
"icc": false,
"userns-remap": "default",
"log-driver": "syslog",
"disable-legacy-registry": true,
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true
Exit and save the file.
Notice that each phrase addresses a [WARN] from the previous run of dockerBench.sh
.
Your workflow is to either Google or look at the docs for each [WARN] message, then add the proper configuration.
Note: the log-driver should be configured to move the logs to a centralized 'syslog' server. This removes the logs from the Docker host and away from attackers who could alter them to hide tampering or delete them altogether. It is also possible to forward the logs by adding:
"log-opts": { "syslog-address": "udp://198.54.100.33:452}
<-- not real.
Use your own syslog server address.
Go ahead and restart docker.
$ rc docker restart
Again, it may say service not found
,
so run the dockerBench.sh
script to see that the changes have been incorporated.
$ bash dockerBench.sh
Now for the last easy-to-fix [WARN].
To enable Content Trust:
$ export DOCKER_CONTENT_TRUST=1
Run the dockerBench.sh
script to see that the [WARN] is gone.
Conclusion
By using Docker Bench for Security, we have audited the security of the Docker installation in this practice environment. Now you can do the same for the host OS running your Docker containers. Notice that you do not need to achieve 100%. In some cases, it may be impossible, or you have a good reason for why you do not have it configured that way. That's okay -- handle what you can.
II. Using Docker Bench Security for Container Hardening
Now let’s look at container hardening -- improving security for containers already running in your host OS. To follow this next part, all you need is Docker CE. Your own OS and preferred terminal/command line should work fine. For this demo I used Git Bash.
Pull a verified published image from Docker Hub.
$ docker pull django:latest
Run the image.
docker container run --detach -ti --name django_test django:latest
Confirm that it is running.
docker ps
Now run Docker Bench for Security.
$ winpty docker run -it --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v "//var/lib":/var/lib \
-v "//var/run/docker.sock":/var/run/docker.sock \
-v "//usr/lib/systemd":/usr/lib/systemd \
-v "//etc":/etc --label docker_bench_security \
docker/docker-bench-security
Results,
Let's stop the container and then address the issues. First get the container ID
docker ps
docker stop ${your-container-id}
From the results above you can see that many of the problems with our running container can be addressed by changing the way we issue the running command. Note each [WARN] is a flag that can be toggled when issuing the command. With that in mind, let's run the new docker run command for our test container, after doing some Googling and checking of the Docker docs for the proper flags.
docker container run --detach -ti -u 1000 --read-only -m 256mb --security-opt=no-new-privileges --security-opt apparmor=docker-default --cpu-shares=500 --pids-limit=1 --restart on-failure:5 --name django_test_cont_2 django:latest
Results,
It is not often the case in production environments that docker container run ...
is issued like it is above. Most likely there is use of a Docker-compose.yaml
. If that is the case, then rather than running one long docker container run
command, each one of the flags
can be addressed in the Docker-compose.yaml
file.
Similar to this code snippet:
// Docker-compose.yaml
version: '3'
services:
redis:
image: redis:alpine
deploy:
resources:
limits:
cpus: '0.50'
memory: 50M
reservations:
cpus: '0.25'
memory: 20M
CONCLUSION
Docker Bench for Security is a great security tool because it is made and maintained by the creators of Docker, and it is free. To view the official benchmarks that the tests are based upon, visit Docker CIS Benchmark.
The next steps for using this tool in your workflow may be to add Docker Bench for Security as a part of your Jenkins pipeline. For inspiration check out this article Continous Security Integration with Jenkins and Docker Bench.
In the next article Kube-bench a similar tool for the popular orchestration tool Kubernetes, which is probably more relevant in today's production environments.
More about Toul...
I interned at HP in 2018 while finishing a double-degree in Geophysics and Computer Science at the University of Houston. During undergrad, I studied abroad in Valencia, Spain and Stavenger, Norway. The highlight of my time in Europe was a month-long holiday through Barcelona, Paris, London, Stockholm, Cologne, and Amsterdam.
After graduation, I transitioned into my dream career as a DevOps Engineer at HP, Inc. where I am currently growing into a security focused DevOps Engineer. (Some would say that makes me a DevSecOps Engineer.) One of my first security improvement projects is auditing Docker and Kubernetes--two tools at the crux of our Continous Integration/Continous Deployment pipeline. By improving the security on those two tools in the toolchain I hope to further support Dion Weisler's claim of the world's most secure PC.
In my spare time, I like to apply my degree in Geophysics to suspenseful personal challenges.