Enhancing the Security of Your Docker Workflow

Essential tools and practices for safeguarding containerized applications

Abraham Morales
Globant
7 min readMay 19, 2023

--

According to Dynatrace 2022 CISO Research Report and Aqua Security 2021 Cloud Security Report, almost 50% of the deployed containers present security vulnerabilities due to misconfigurations and bad practices.

Container Security is the process of implementing a security and compliance feature in all the stages of a container’s lifecycle. The security aspects include base images, the Dockerfile, the container runtime, and securing the Docker daemon.

In this article, we will focus on scanning the Dockerfile and the Docker image for secret leaks and common bad practices. We will show how to validate best practices for the Dockerfile (code linter) and how to scan Docker images for known vulnerabilities (vulnerability image scanner) and possible secret leaks (secret scanner).

The prerequisites are basic Docker knowledge, Docker CLI installed, and building and running containers. All the code shown here has been tested on a macOS environment but can be easily adapted to a Windows environment or Linux.

Hadolint: Code Linter

Code Linters aim to make your Dockerfiles compliant with the image-building best practices suggested by Docker. The one we are using is an open-source tool named Hadolint.

This tool automates the detection of Dockerfile issues. This helps your Docker images adhere to best practices and organizational standards.

The product can be downloaded like precompiled OSX, Windows, and Linux binaries. However, most of the time it is easy just to run it from a Docker container. Complete installation instructions can be found on the Hadolint main page, but for example, you can use Brew on OSX.

brew install hadolint

To scan your Dockerfile using the precompiled binaries, just run:

hadolint Dockerfile

To analyze your Dockerfile with Hadolint Docker distribution, run the following command:

docker run - rm -i hadolint/hadolint Dockerfile

Let’s take as an example the following Dockerfile:

FROM python
LABEL maintainer="agmc22mx@gmail.com"

RUN apt-get update
RUN apt-get install curl
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
RUN pip3 install pyyaml
RUN mkdir app && cd app

COPY testing.py ./
CMD python testing.py

After scanning the Dockerfile, you will see a list of all violation rules and their severity:

Generated output after running the scan

Rules are identified as numbers prefixed with either HL or SC. HL rules are part of Hadolint, whereas SC entries come from ShellCheck. Each check is given a severity from Error through to Info. A list of rules can be found on the Hadolint page, and best practices for writing Dockerfiles are here.

Hadolint can be configured through command line parameters, adding comments to the Dockerfile, and using a configuration file. You can pass a custom configuration file in the command line with the--config option.

Configuration files are written in YML format and look like this:

override:
error:
- DL3009
info:
- DL3006
label-schema:
maintainer: text
version: url
strict-labels: true

Scanning a Dockerfile using this configuration file is as simple as running:

hadolint --config hadolint.yaml Dockerfile
Generated output after running the scan using a configuration file

When running the scan using the configuration file, we can see, for example, that the rule DL3009 has changed from info to error level.

To fix bugs on the Dockerfile, we will make the following changes:

  • Add version to the base image.
  • Add version label.
  • Set WORKDIR before copying files to a relative destination.
  • Consolidate RUN statement.
  • Use arguments JSON notations for CMD and ENTRYPOINT arguments.
FROM python:3.10
LABEL maintainer="agmc22mx@gmail.com"
LABEL version="1.0"

WORKDIR /app
RUN apt-get update
# hadolint ignore=DL3008
RUN apt-get install -y curl install -y python3 python3-pip && pip3 install --no-cache-dir pyyaml==1.1
COPY testing.py ./
CMD ["python", "testing.py"]

After fixing the bugs on the Dockerfile, we will see a cleaner output if we rerun the scan.

Scanner Output after fixing the Dockerfile.

Output Formats

Several output formats are supported; just use the -format flag. The default is tty which emits colorized output to your terminal. Colors can be disabled using the -no-colorflag.

The following alternative formats are available:

  • json: formats the output into a JSON with a list of detected issues
  • checkstyle: formats the output into a Checkstyle-compatible format.
  • codeclimate: formats the output into a Code Climate compatible format.
  • gitlab_codeclimate: formats the output into a report compatible with GitLab’s integrated code quality.

So to format the output, we can run hadolint -format <the_format> Dockerfile; for example:

hadolint --format json  Dockerfile
Formatting output to JSON

Grype: Static Image Vulnerability Scanning

Image scanning works by parsing through the container image file, and then it is compared to public security vulnerability databases to see if any known vulnerabilities exist. The one we are using is an open-source tool named Grype.

Grype is an open-source vulnerability scanner for container images and filesystems managed by security company Anchore; it can be downloaded and installed for OSX and Linux or built directly from sources. Complete installation instructions can be found on the Grype main page, but for example, on OSX, you can use Brew to install Grype:

brew tap anchore/grype
brew install grype

To scan a Docker image after building it, run the following:

docker build -t abrahamntd/basic-python:latest .
grype abrahamntd/basic-python:latest
Generated output after running the scanning image

The above command scans for vulnerabilities in the container. Each package scanned within an image will be listed, and the output includes the following:

  • NAME: Name of the package.
  • INSTALLED: Version number installed in the image.
  • TYPE: Type of package
  • SEVERITY: Severity for each vulnerability.

The generated output from the scanner can be presented in different formats:

  • table: Default format (basically a table).
  • cyclonedx: XML report compatible with the CycloneDX 1.2 specification.
  • json: A JSON object containing detailed information.
  • template: This option allows us to specify the output format using Gotemplates.

For example:

grype abrahamntd/basic-python:latest -o json
Formatting output to JSON

When Grype performs a scan for vulnerabilities, it does, using a vulnerability database. This database is stored on your local filesystem. By default, Grype automatically manages this database and checks for updates to be sure that every scan uses up-to-date vulnerability information.

SecretScanner: Standalone secrets scanner

Secrets are sensitive pieces of information such as credentials, SSH keys, tokens, and TLS certificates. These should not be baked into Docker images without being encrypted since unauthorized users can extract the secrets from the image by examining the layers, for example.

One of the most common mistakes is leaking Secrets via Docker images or the file system. We will use an open-source secret scanner named SecretScanner from Deepfence to identify the secret on our container images. It will help us to detect when we are leaking Secrets through the Docker images. According to their official documentation:

SecretScanner can finds unprotected secrets in container images or file systems.

SecretScanner is a standalone tool that retrieves and searches container and host filesystems, matching the contents against a database of approximately 140 secret types.

Let’s introduce some secrets to our Dockerfile and run the scanner:

FROM python
LABEL maintainer="agmc22mx@gmail.com"RUN apt-get update
RUN apt-get install curl
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
RUN pip3 install pyyaml
RUN mkdir app && cd appCOPY testing.py ./
COPY id_ssh id_ssh
COPY id_ssh.pub id_ssh.pub
ENV DATABASE_PASSWORD "my_password"
CMD python testing.py

Let’s build the Docker image and then scan it to detect possible secret leaks. To build the image, run the following command:

docker build -t abrahamntd/basic-python:latest .

Now, scan the Docker Image using the Docker distribution of SecretScanner, and run the following command:

docker run -it --rm -v $(pwd):/home/deepfence/output -v /var/run/docker.sock:/var/run/docker.sock deepfenceio/deepfence_secret_scanner:latest -image-name abrahamntd/basic-python:latest
Output from the scanner shows the secret that was found

The report includes information like the Image Lager IDand Matched Rule Namethat describes the secret it’s detected and also provides Severity, Severity Score, and Matched Contents.

Conclusion

In this article, we have explored three tools that can enhance the security of Docker containers and address common issues. Misconfigured Docker containers can lead to sluggish performance, excessive resource usage, and challenging maintenance. To mitigate these problems, we discussed the utilization of three specific tools.

Firstly, Hadolint enables the scanning of Dockerfiles, ensuring adherence to best practices and avoiding common mistakes. Secondly, Grype facilitates scanning Docker images to identify any known vulnerabilities. Lastly, we explored the utilization of deepfence/SecretScanner, which aids in detecting secret leaks.

By incorporating these tools into your Docker processes, you can bolster security and maintain a more efficient and secure container environment.

You can check the source code for this article here.

References

--

--

Abraham Morales
Globant

Just a DevOps engineer and passionate about technology in general