Docker Bench for Security - Container Hardening and Auditing Host Security

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.

Docker Playground

Now that the terminal is active, let's check which Linux version is installed:

$ cat /etc/os-release

cat_alpine

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

docker_bench_against_host

...and then run it via $bash name_of_script.sh

bash_dockerbench

If you can copy and paste, then just copy this chunk of code into the terminal and press enter.

Results should look like this:

audit_warning docker_daemon docker_daemon_configuration_file container_image_and_build

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.

audit_warning

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:

audit_rules

-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.

audit_pass

Let's handle the next section of errors [WARN] 2.1, 2.4, 2.8, 2.11-.12, 2.14-.15, 2.17-.18

docker_daemon

Open the daemon.json

$ vim /etc/docker/daemon.json

docker_daemon_before

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:

container_image_and_build

$ export DOCKER_CONTENT_TRUST=1

Run the dockerBench.sh script to see that the [WARN] is gone.

docker_content_trust

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,

container_hardening_before

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,

container_hardening_after

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.