Harden Docker with CIS – (P5) Container Images and Build File Configuration

So in this post of the Harden Docker with CIS series, we’ll focus on the Container Images and Build File Configuration section of the CIS Docker benchmark. This is a rather short section. However, many of the critical things are here. They may seem simple but have a deep impact on the overall container security. Surprisingly these are very pieces of information/settings that are missing is mostly all the images and containers. Anyway, enough of rambling, let’s being.

Scored CIS Controls

CIS ControlDescription
4.1Ensure that a user for the container has been created 
4.5Ensure Content trust for Docker is Enabled 
4.6Ensure that HEALTHCHECK instructions have been added to container images 

Not Scored CIS Controls

CIS ControlDescription
4.2Ensure that containers use only trusted base images 
4.3Ensure that unnecessary packages are not installed in the container 
4.4Ensure images are scanned and rebuilt to include security patches 
4.7Ensure update instructions are not use alone in the Dockerfile 
4.8Ensure setuid and setgid permissions are removed 
4.9Ensure that COPY is used instead of ADD in Dockerfiles 
4.10Ensure secrets are not stored in Dockerfiles 
4.11Ensure only verified packages are are installed 

As evident, most of the controls in this section are ‘Not scored’; however, they are important and should be understood well and cared for. As most of these things are under the developer’s control, thus offer the maximum surface area of improvement.

4.1 Ensure that a user for the container has been created

This is one of those controls which everyone seems to miss out on while creating or building images. When you build an image from a Dockerfile. The container that is spawned from that image file will be running as root. And that container has almost all of the host system’s capabilities, as much as a root user would on that same host system.

To remedy this situation we should work towards building our Dockerfile with USER defined in them.

To verify if there are containers running as root, you can run the following command.

docker ps --quiet | xargs --max-args=1 -I{} docker exec {} cat /proc/1/status | grep '^Uid:' | awk '{print $3}'
Code language: Bash (bash)

This will spit out UID of the user running in the container. Most probably it will be 0, but if it is other than 0, kudos!

This is what my sample Dockerfile looks like

FROM debian:10.7-slim COPY script.sh / USER 9999:9999 ENTRYPOINT ["/script.sh"]
Code language: Dockerfile (dockerfile)

This is the script.sh file

#! /bin/bash sleep 3600
Code language: Bash (bash)

The key point to note is, the addition of the USER 9999:9999 in the Dockerfile will ensure that my script.sh is run as that user. If there is any potential for privilege escalation/escape from the container, the attacker will attain the user id of 9999, which maps absolutely nothing on the host system. Celebrations!

Output of the verification command for my container came out to be 9999

$ docker ps --quiet | xargs --max-args=1 -I{} docker exec {} cat /proc/1/status | grep '^Uid:' | awk '{print $3}' 9999
Code language: Bash (bash)

4.5 Ensure Content trust for Docker is Enabled

The content trust provides the ability to use digital signatures for data sent to and received from remote Docker registries. These signatures allow client-side verification of the identity and the publisher of specific image tags and ensure container images’ provenance.

If you are ware of Notary, Docker content trust is Notary for just Docker Hub. This feature is not available and will not work on private registries/Other Docker Trusted registries.

This is an essential security measure in preventing supply chain attacks. This ensures that the image you pulled from the Docker registry is, in fact, the one that you were supposed to pull and not something else.

4.7 Ensure that HEALTHCHECK instructions have been added to container images

This is one of those controls which I have rarely seen in any of the images. However, this ensures the container is up and running and is, in fact, running as expected. Let’s look at an example and see what this looks like in practice.

Dockerfile for this example

FROM python:3-alpine COPY script.sh / HEALTHCHECK --interval=10s --timeout=10s CMD wget http://localhost:8080 -O - || exit 1 ENTRYPOINT ["/script.sh"]
Code language: Dockerfile (dockerfile)

Script.sh for this example

#! /bin/sh python -m http.server 8080
Code language: Bash (bash)

Basically, in this example, Docker will use the HEALTHCHECK command to ensure the container is up and running and is returning the expected output. The command can be as complex as required; however, this is a PoC.

Now let’s look at the controls which are Not scored

4.2 Ensure that containers use only trusted base images

This is pretty straightforward, don’t use ANY X, Y, and Z image over the internet as your base image for production builds. Use the ones that are marked as trusted by the Docker registry or your company policy. This ensures that the images you’re hosting your workload on are safe images and will **probably** not contain any malware.

Probably - Supply chain attacks are never fun to deal with. SUNBURST and SolarWinds is one example. 
Circa December 2020

4.3 Ensure that unnecessary packages are not installed in the container

Simple logic, the more the number of unused applications on the system, the wider the attack surface, and nobody on the defense side wants a huge attack surface. So, use minimal OSes (Alpine, etc.), or go Distroless. Either one of these options should be suitable for any production build. This control can even be clubbed with multistage builds and ensure only required packages are transferred to the last build stage.

4.4 Ensure images are scanned and rebuilt to include security patches

This is one of those good habits that every container developer should have, scan images for vulnerabilities, and update the image with the latest version if there is an update available. For scanning images, Trivy, Clair, etc., can be used from an Open Source perspective, Prisma Cloud Compute, etc., can be used from the premium category.

Scanning images using multiple sources makes much more sense, but this is just a recommendation. Implementing just one would be a big enough hassle.

4.7 Ensure update instructions are not used alone in the Dockerfile

You should use update instructions together with install instructions and version pinning for packages while installing them. This prevents caching and force the extraction of the required versions. If not done, you’ll get stale/cached packages into the container, and it will verify difficult to identify the problem. As an alternate, you could use the --no-cache flag during the docker build process to avoid using cached layers.

4.8 Ensure setuid and setgid permissions are removed

Removing setuid and setgid permissions in the images can prevent privilege escalation attacks within containers.

To verify existing setuid and setgid binaries, following command can be run to get a list.

docker run <Image_ID> find / -perm /6000 -type f -exec ls -ld {} \; 2> /dev/null
Code language: Bash (bash)

4.9 Ensure that COPY is used instead of ADD in Dockerfiles

COPY Simply copies the files from the source/host to the image/container. However, ADD can do many operations on that source file, such as extraction, downloading from URL, etc. Thus avoid using ADD in Dockerfile.

Reference: https://docs.docker.com/engine/reference/builder/#add

4.10 Ensure secrets are not stored in Dockerfiles

Please don’t store passwords in the Dockerfile. Use a vault, environment variables, file mounting, etc., to transfer the container’s credentials.

4.11 Ensure only verified packages are installed

Packages with no known provenance could potentially be malicious or have vulnerabilities that could be exploited. Avoid using packages that are no longer maintained and are from unofficial sources. It may be tough to get the job done without the package, but the security risk will never be worth it.

This completes our “Container Images and Build File Configuration” section of the CIS Docker Benchmarks. We’ll continue with the other sections in future posts.

If you have questions or need help setting things up, reach out to me @jtnydv