Building Secure Docker Images - 101

Prasoon Dwivedi
Walmart Global Tech Blog
7 min readApr 2, 2020

Docker containers have made the distribution of software easier and simplified resource sharing on a system. Loopholes in the container image configuration, either by default, or when customized by users can lead to security events.

In this article the basic steps one can take to build a secure Docker image have been summarized. This article is of interest to those, who use Docker images either off-the-shelf or build custom layers on top of the popular base images. This article will step-by-step guide you through the elementary security best practices for building secure Docker images and to evaluate off-the-shelf base images.

Image Source https://pixabay.com

1. Run the container as a non-root user

root is the default user inside a Docker container.

If you do not specify a user while starting a container, it will run by default as the user set in the image (last USER command in the Dockerfile) or the one inherited from the parent image file (specified by the FROM command in the Dockerfile).

If the user is neither specified while starting the container nor is present in the image, the container will run as root (uid 0).

It is recommended to create a non-root user to run the first process of the container. In addition to this, any non-required users must be deleted.

Run the following command to find out the username/userid. If a blank is returned it means the container is running as root.

docker ps — quiet — all | xargs docker inspect — format ‘{{ .Id }}: User={{ .Config.User }}’

Use USER command to set the user when running the image and any subsequent RUN, CMD and ENTRYPOINT instructions that follow it in the Dockerfile.

To inspect an image to see if a user is set to run the command use DOCKER HISTORY

docker history <Image Name/ID>

2. Remove unnecessary packages/software from the image

Having unnecessary packages in the Docker images not just bloats the size of the image, but it also increases the attack surface of the container. All the Docker images must be carefully examined for installed packages, and only the packages required for the functioning of the service(s) in the container must be included.

One can start building their Docker images by using minimalistic base images like Alpine, BusyBox, and others.

To list all the packages installed in a container run the following command:

docker exec <CONTAINER_ID> apk info

The above command is specific to the apkpackage manager. Based on the package manager of your image the above command must be modified.

One may also consider removing the package installer before using the image in the production environment.

Image Source https://pixabay.com

3. Scan and rebuild images to include security patches

Whether you are building your Docker image from scratch or you are building them on top of the third-party image, it is very important to have your images scanned for vulnerabilities as part of your continuous integration process. This includes scanning included packages, binaries, libraries, files, etc. against one or more well-known vulnerabilities databases.

There are many popular open-source Docker scanning tools available. Some of the most popular ones are Anchore-Engine, CoreOs-Clair, Dagda, Open-SCAP, etc.

4. Enable Docker Content Trust (DCT)

Docker Content Trust (DCT) uses digital signatures to validate integrity of the images being pulled from the remote Docker registries. Using DCT the image publisher, be it an individual or an organization can also sign their images. The consumers of the image can verify the integrity of the image by verifying the digital signatures to ensure that the images have not been tampered.

If DCT is enabled pull, run, and build instructions will only work with the trusted (signed) images.

echo $DOCKER_CONTENT_TRUST

Make sure that the output of the above command is 1, as it indicates that the DCT is enabled. By default DCT is disabled. To enable DCT run the following command.

export DOCKER_CONTENT_TRUST=1

DCT is important for building secure Docker images as it ensures the integrity of the base images and identity of their publisher. If for some reason you want to disable DCT while you pull, run or build an image the --disable content-trust flag with its value set to false may come in handy.

5. Use COPY instead of ADD in Dockerfile

Both COPY and ADD instructions are very similar in functionality. Both the instructions can be used to copy local files into the file system of the container image. ADD instruction has some more functionality like local tar file extraction and remote URL fetch.

Using ADD instruction in the Dockerfile introduces the risk of adding malicious files from remote URLs without scanning them.

Thus, the best use of ADD instruction is to copy tar archive into the image filesystem and auto-extract them. Where ever possible COPY must be used in place of ADD instruction.

One can inspect an image to check the use of ADD command in building the image by using the DOCKER HISTORY command.

docker history <Image Name/ID>

6. Do not store any secret in Dockerfile

Passwords, API keys, private keys, and other secrets must never be part of the Dockerfile. Secrets in Dockerfile can easily be exposed by simple commands like DOCKER HISTORY and once exposed can be exploited by an adversary.

Dockerfile should never be used to pass around secrets. To make sure that no secret is part of the Dockerfiles one easy way is to use DOCKER HISTORY command and carefully examine the contents of it.

docker history <Image Name/ID>

7. Install verified packages and use trusted base images

Install packages only from the trusted repositories. In addition to this, use checksums and digital signatures to verify the authenticity and integrity of downloaded packages.

While using a FROM instruction always use a tag. Let us dig deep a little into this recommendation.

FROM alpine

The above command is no good as it will always pull the latest tag, which is expected to change frequently and may be unstable as well.

FROM alpine:3.11

The above command is still better as it pins the base image to a certain version. One can still expect it to change to include minor updates and security fixes. If one wants to use the same image every time then they can use digests with the FROM instruction. Using digest will also protect the image from tampering and corruption.

FROM alpine@sha256:ddba4d27a7ffc3f86dd6c2f92041af252a1f23a8 e742c90e6e1297bfa1bc0c45

Also, just like the base images while downloading the software packages pin the version, check for the integrity and authenticity of the packages using checksum and digital signatures. This will protect the packages from accidental corruption and MITM if the packages are downloaded over an insecure channel (HTTP).

8. Remove setuid and setgid permission from the image

setuid (Set User ID on execution) is a special type of permission in Unix which permits users to execute certain programs with elevated privileges. When an executable file’s setuid permission is set, users may execute that program with a level of access that matches the user who owns the file.

setgid (Set Group ID on execution) similar to the setuid, the only difference is — when the executable with setgid permission runs, it runs as if it were a member of the same group in which the file is a member.

Removing setuid and setgid permission from the binaries prevents them from being misused for the privilege escalation attacks.

To find all executable files in a Docker image with setuid and setgid permission set run the below command:

docker run <Image Name/ID> find / -perm +6000 -type f -exec ls -ld {} \; 2> /dev/null

The maintainer of the image must carefully examine the list of all the files returned as output to the above command and then must remove the setuid and setgid permission from all the executables where it is not required.

The executable binaries can also be DEFANGED while building the image, by including the following in the Dockerfile.

FROM alpine:3.11

RUN find / -perm +6000 -type f -exec chmod a-s {} \; || true

9. Carefully use update instruction in Dockerfile

If you use package update instruction like apt-get update or apk update (depending on the base image and package manager) in your Docker file, make sure that they are never present alone in a single Dockerfile line.

If the package update instruction is present in a single line in the Dockerfile, then the same cached update layer will be used. This will prevent any fresh update to be part of subsequent builds.

To mitigate this problem review your Dockerfile and remove any update instruction present alone. If you do not have access to the Dockerfile you can use DOCKER HISTORY command to verify that there are no solo update instructions.

docker history <Image Name/ID>

One can also use you--no-cacheflag to eliminate any cached layers while building Docker images.

10. Add HEALTHCHECK to Docker image

The HEALTHCHECK instruction can be used to check if a container is still running or not. If a Docker image has healthcheck specified then the images will also have health status in addition to the normal status. The health status is initially starting. Whenever a health check passes, it becomes healthy (whatever state it was previously in). After a certain number of consecutive failures, it becomes unhealthy.

Based on the health of the containers Docker can provision new containers to replace the unhealthy containers.

HEALTHCHECK --interval=5m --timeout=3s \
CMD curl https://localhost:8443/version -k || exit 1

Including the above HEALTCHCHECK instruction to the Docker file will check every five minutes if the web-server running on port 8443 is able to serve the version API within three seconds.

Availability is one of the three pillars of security (the other two being Confidentiality and Integrity), and HEALTHCHECK instruction can be used to make sure the services running in Docker containers in always up.

To make sure HEALTHCHECK instruction is present in the Docker image run the following command:

docker inspect --format='{{ .Config.Healthcheck }}' <Image Name/ID>

--

--

Prasoon Dwivedi
Walmart Global Tech Blog

Software Security Enthusiast. Views here are my own and does not represent my employer.