Welcome back to Securing the Docker Ecosystem - our three-part series on improving the security posture of your Docker ecosystem. In each article, we address one particular aspect of Docker through a security lens.
The first article is about preventing and mitigating potential Docker Daemon security issues, while the third one dives into the Docker runtime.
In this second article, we will focus on Docker container builds and how you can make them more secure by correctly implementing proven procedures. But before we get into the how let’s look at the what: Docker Container Builds and the common security concerns around them.
The build phase involves building Docker images from a Dockerfile and a set of files (called context).
Why is it important to consider security at the build phase? After all, it is the Daemon that is the critical element of the Docker architecture. Right? Well, yes, the Daemon is certainly important, but it is by no means the only important element of the entire Docker ecosystem. In fact, some technical experts go so far as to say that Docker security begins at the image. An image is a read-only template with instructions for creating a Docker container.
The security and quality of Docker images available for assembling applications is a concern because often, developers are unable to verify the origin and the integrity of images available for download, say, from the Docker Hub registry. They’ll initially start by downloading a container base image, and then download and use libraries, and then add their own code… they have accessed a “supply chain” that may well have vulnerabilities in any part of the stack that touches the network. And in the rush to containerisation, they may leave their networks vulnerable to security risks from these insecure images.
“… internal users could download a popular Docker image, insert malicious code or back doors, then re-upload the image with a similar name and gain access to production containers”
– Jay Lyman, 451 Research
In 2015, BanyanOps found that about 40% of Docker images distributed through Docker Hub had high-priority vulnerabilities. At the time, the repository stored about 95,000 Docker images. By June 2020, the size of this repository had grown to 3.5 million images. What does this have to do with the security risks in the Docker build phase?
Consider these facts based on a vulnerability analysis carried out by a trio of computer security researchers from the Norwegian University of Science and Technology (NTNU):
Further, the researchers found the most common and severe vulnerabilities in the JavaScript libraries and Python packages.
Admittedly, the study was limited to a small sample set, but it still delivers a good lesson - don’t ignore the security risks of images in the Docker Container Build! -. Docker and container technologies are only a few years old, so security technologies and best practices - not to mention the documentation around them - are constantly evolving. So instead of relying on them to improve the security posture of your Docker container build, it’s important to follow some security strategies and thumb rules as well.
One of these strategies, the Docker Content Trust (DCT) feature in the Docker Engine, addresses some concerns around image and application container security by allowing developers to verify the integrity and the publisher of all data received from a registry over any channel. Essentially, DCT provides the ability to use digital signatures for data sent to and received from remote Docker registries, so developers can verify image publishers and ensure that images have not been tampered with. This strategy is included towards the end of this article.
Let’s get started with 5 security best practices for building secure Docker images and evaluating third-party image safety and compliance.
Keeping your images clean and lean will help to increase the maintainability and security of your container pool. As a general rule, make sure that you:
Query the list of packages using the package manager in your container.
The below example lists the software installed by the Alpine Linux’s package manager.
$ docker exec CONTAINER_ID apk info
Add this command to your Dockerfiles to remove the SUID and SGID bits from the executables.
RUN find / -perm /6000 -type f -exec chmod a-s {} \; || true
latest
tag and do not upgrade the system’s packages at build time.Docker builds and runs the container with the privileges declared in the last USER
command in the Dockerfile. If this is not set, it defaults to the high-privileged “root” user.
Follow the principle of “least privilege”, and drop the container privileges as soon as possible in the build process by adding the USER
command to your Dockerfiles.
To identify all the containers that run as “root”:
$ docker ps -a -q | xargs docker inspect -f '{{.Id}}: User {{if .Config.User}}{{.Config.User}}{{else}}root{{end}}'
To check if an image has been built using the USER
command:
$ docker history IMAGE_ID | grep USER
Secret strings, such as credentials and authentication tokens, should never be stored in Dockerfiles or application code.
Contrary to popular belief, it is also unsafe to pass them as environment variables at build time using --build-arg
. The build-time environment variables were not designed to handle secrets, and sensitive material can be easily disclosed by running the docker history
command against the image.
Juggling build-time variables between ARG
and ENV
commands in the Dockerfile is also an anti-pattern. Once a variable is defined with ENV
, it persists in the running container and can be read by anyone who gains access to the container with the right privileges.
Review your Dockerfiles and CI configuration to make sure no secrets are passed at build time using environment variables.
Inspect your images to identify any ARG
command that declares variables with suspicious names.
$ docker history IMAGE_ID | grep ARG
From an architectural point of view, secrets management is outside Docker’s scope and should be delegated to some dedicated platform component by using AWS IAM, Kubernetes Secrets, or external vaults.
However, starting with Docker version 18.09 and API version 1.39, Docker provides a neat --secret
argument to the docker build
command to secure the passage of sensitive material at build time.
Read more about Docker secrets build information here.
Avoid using ADD
in Dockerfile to copy files from the context into the image at build time.
Unlike COPY
, the Dockerfile ADD
command can fetch remote resources via URL at build time. This could be abused in systems that dynamically generate Dockerfiles, such as Docker-based Continuous Integration or Platform Management systems, to download and install malicious code at build time.
Check if an image has been using ADD
.
$ docker history IMAGE_ID | grep ADD
If possible, stick to using the COPY
command.
Docker does not verify the integrity and publisher of images in the registry by default. This leaves Docker operators vulnerable to running untrusted and potentially malicious images.
With the release of version 1.8, Docker introduced Content Trust, which supports digital signing and authentication of images. Do consider enabling this feature in your corporate Docker build pipeline.
Trust for an image tag is managed through the use of Docker Content Trust signing keys. Back up the root key somewhere safe. Since it is only required to create new repositories, it makes sense to store it offline in hardware.
Within the Docker CLI, you can sign and push a container image with the docker trust
command syntax. To sign an image, a Docker Registry with a Notary server attached is a pre-requisite.
To sign a Docker Image, you will need a delegation key pair, which can be generated either locally, using docker trust key generate
, or by a certificate authority.
Content Trust is disabled by default in the Docker Client.
To enable it, set the DOCKER_CONTENT_TRUST
environment variable to 1
. This prevents the client from working with unsigned images.
Read more about Content Trust.
Would you prefer to resolve issues (“after the fact”) or avert them?
Assign developers to put out fires, or prevent these fires in the first place?
Spend time and money fixing problems lower down the SDLC pipeline, or create a process whereby potential problems are caught and fixed fast and before the crash?
Smart business leaders always select Door #2. Here’s why!
Security bugs during the Docker container build phase tend to increase over time. Fixing them later slows down development, stresses out your technical team, and increases remediation costs. A more efficient way to reduce security issues downstream is to prevent them, or at least identify and remediate them as early as possible. And this can only happen if your developers and technologists have the right skills and knowledge - for which real-world, hands-on training is absolutely vital.
SecureFlag provides world-class training to teach developers modern secure coding practices through 100% hands-on exercises. They learn defensive programming based on real-world vulnerabilities and famous breaches, while you get a more robust SDLC toolkit to help you meet your business and technology goals.
The risks are real, and old-fashioned training (multiple-choice questions anyone?!) just does not cut it. For a free demo of our unique training approach for 10+ technologies, get in touch with us today.