dockerfile-best-practice
This repository shows an example of a GitHub Actions workflow to detect anti-patterns in your Dockerfile. See "7 Easy-to-Follow Best Practices for Writing Dockerfile" for further details.
Posted on October 13, 2021
Dockerfile allows you to create container images in such a way as if you were writing shell scripts. This simplicity is excellent indeed, but it does NOT mean that you don't need to learn good practices for writing Dockerfile. Just a few practices will make your container images more optimized and secure.
This article shows a list of 7 best practices for writing Dockerfile along with some Shisho rules to detect the issues. You may not know Shisho, but it's okay because Shisho is extremely easy to use; all you need to do for checking your Dockerfile is just by running the following command:
curl https://raw.githubusercontent.com/security-aware-repo-examples/dockerfile-best-practice/master/rules/docker.shisho.yaml > docker.shisho.yaml && \
docker run -i -v $(pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest check ./docker.shisho.yaml .
If you're a user of GitHub Actions, you can copy this example workflow and the rule file in security-aware-repo-examples/dockerfile-best-practice to your repository. The workflow checks your repository with the rule, and it reports them to GitHub Code Scanning. The issues will appear at https://github.com/<your org>/<your repository>/security/code-scanning
.
Let's get started!
latest
tag for immutability
A latest
tag is used to create a docker image whose base is the latest version of another image. The use of latest
tag, however, might cause confusion and inconsistent behaviour among built images. It is better to pin the version of your base images if possible.
The following Shisho rule will detect the use of latest
tag:
version: '1'
rules:
- id: 'use-fixed-tag-sfor-immutablity'
language: dockerfile
message: |
The use of `latest` tag might cause confusion and inconsistent behavior in automated builds. It is better to pin the version of your base images.
patterns:
- pattern: FROM :[IMAGE]
- pattern: FROM :[IMAGE] as :[ALIAS]
- pattern: FROM :[IMAGE]:latest
- pattern: FROM :[IMAGE]:latest as :[ALIAS]
- pattern: FROM :[IMAGE]@:[HASH]
- pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
- pattern: FROM :[IMAGE]:latest@:[HASH]
- pattern: FROM :[IMAGE]:latest@:[HASH] as :[ALIAS]
(You can see the working example of this rule at Shisho Playground)
apt-get install
You may often run apt-get
inside your Dockerfile as follows:
RUN apt-get update
RUN apt-get install -y nginx <... and some more packages>
However, the example above has two issues:
RUN
is available, the second apt-get
might install old packages due to not running apt-get update
.apt-get
, resulting in larger image size.A simple way to address these issues is running apt-get update
and apt-get install
in a single RUN
instruction. The following examples show an idiomatic way to run them once while removing /var/lib/apt/lists/*
(i.e. apt caches) to reduce the image size more:
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
The following Shisho rule will enforce the use of this idiom:
version: '1'
rules:
- id: 'remove-cache-of-apt-get'
language: dockerfile
message: |
It is better to remove cache files of `apt-get` to keep your image slim.
pattern: |
RUN apt-get install :[X]
rewrite: |
RUN apt-get update && \
apt-get install :[X] && \
rm -rf /var/lib/apt/lists/*
(You can see the working example of this rule at Shisho Playground)
--no-install-recommends
flag of apt-get
It is even better to avoid to install any unnecessary tools by --no-install-recommends
of apt-get
command. The following Shisho rule may help the use of this flag:
version: "1"
rules:
- id: "use-no-install-recommends-flag-apt-get"
language: dockerfile
message: |
You can avoid to install any unnecessary tools by `--no-install-recommends` on `apt-get`.
patterns:
- pattern: |
RUN apt-get install :[...X]
- pattern: |
RUN :[...Y] apt-get install :[...X]
constraints:
- target: X
should: not-match
regex-pattern: ".*--no-install-recommends.*"
rewrite_options:
- |
RUN :[Y] apt-get install --no-install-recommends :[X]
(You can see the working example of this rule at Shisho Playground)
While container technology brings many advantages, such as portability or isolation, it also creates new threats from a security perspective. This section describes common docker security issues and explains how to avoid them.
Hardcoded secrets in your Dockerfile will be stored in resulting images. You should avoid to embed the secrets. You can inject environment variables at run-time instead.
version: '1'
rules:
- id: 'avoid-to-store-secrets-in-env'
language: dockerfile
message: |
Hardcoded secrets in your Dockerfile will be stored in resulting images. Please consider to stop embedding the secrets.
pattern: |
ENV :[...] :[KEY]=:[VALUE] :[...]
constraints:
- target: KEY
should: match-any-of
regex-patterns:
- "[sS][eE][cC][rR][eE][tT]"
- "[tT][oO][kK][eE][nN]"
# ... add as you like ...
(You can see the working example of this rule at Shisho Playground)
A docker image consists of multiple layers, and some of them are usually derived from the base image. Here exists a risk of supply chain attacks! It is better to use official images to reduce the risk as much as possible.
version: '1'
rules:
- id: 'use-docker-official-images'
language: dockerfile
message: |
It is better to use official images to reduce the risk of supply chain attacks.
patterns:
- pattern: FROM :[IMAGE]
- pattern: FROM :[IMAGE] as :[ALIAS]
- pattern: FROM :[IMAGE]::[TAG]
- pattern: FROM :[IMAGE]::[TAG] as :[ALIAS]
- pattern: FROM :[IMAGE]@:[HASH]
- pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
- pattern: FROM :[IMAGE]::[TAG]@:[HASH]
- pattern: FROM :[IMAGE]::[TAG]@:[HASH] as :[ALIAS]
constraints:
- target: IMAGE
should: match
regex-pattern: "/"
(You can see the working example of this rule at Shisho Playground)
If you already have a list of trusted base images, some slight changes will let you use them:
version: '1'
rules:
- id: 'use-trusted-base-images'
language: dockerfile
message: |
It is better to use trusted base images to reduce the risk of supply chain attacks.
patterns:
- pattern: FROM :[IMAGE]
- pattern: FROM :[IMAGE] as :[ALIAS]
- pattern: FROM :[IMAGE]::[TAG]
- pattern: FROM :[IMAGE]::[TAG] as :[ALIAS]
- pattern: FROM :[IMAGE]@:[HASH]
- pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
- pattern: FROM :[IMAGE]::[TAG]@:[HASH]
- pattern: FROM :[IMAGE]::[TAG]@:[HASH] as :[ALIAS]
constraints:
- target: IMAGE
should: not-match-any-of
regex-patterns:
- "^[^/]+$"
- "^image-name/you-trust$"
(You can see the working example of this rule at Shisho Playground)
curl <...> | sh
Many shell scripts and dockerfiles use curl <...> | sh
as an idiomatic way to run an external script inside computers/containers, but this idiom has a risk of remote code execution by attackers through MITM attacks or compromising the distributed script itself (see the report of recent codecov incident). It is better to check the integrity of what you download before running it as a shell script.
version: "1"
rules:
- id: "check-integrity-of-downloaded-shell-script"
title: Check the integrity of downloaded shell scripts
language: dockerfile
message: |
It is better to check the integrity of what you download before running it as a shell script.
pattern: |
RUN :[CMD]
constraints:
- target: CMD
should: match-any-of
regex-patterns:
- curl[^|^>]*[|>]
- wget[^|^>]*[|>]
(You can see the working example of this rule at Shisho Playground)
COPY
instead of ADD
ADD
instruction allows us to fetch resources over the network and extract an archive, but it may cause security issues such as Meet-in-the-Middle (MITM) attacks or Zip Slip vulnerabilities. It is better to use COPY
instead of ADD
if possible.
The following Shisho rule detects the use of ADD
instructions:
version: '1'
rules:
- id: 'use-copy-instead-of-add'
language: dockerfile
message: |
ADD instruction allows us to fetch resources over network and extract an archive, but it may cause security issues such as Meet-in-the-Middle (MITM) attacks or Zip Slip vulnerabilities.
pattern: ADD :[FROM] :[TO]
rewrite: COPY :[FROM] :[TO]
(You can see the working example of this rule at Shisho Playground)
In this article, I presented some best practices for writing Dockerfile and demonstrated how to check your Dockerfile is following the practices continuously by Shisho.
You can refer security-aware-repo-examples/dockerfile-best-practice to see the working example.
This repository shows an example of a GitHub Actions workflow to detect anti-patterns in your Dockerfile. See "7 Easy-to-Follow Best Practices for Writing Dockerfile" for further details.
If you have an interest in Shisho, I would be appreciated if you starred the following repository and gave me feedback at discussions!
Shisho is a lightweight static analyzer for developers.
You can try Shisho at our playground.
You can try shisho in your machine as follows:
echo "func test(v []string) int { return len(v) + 1; }" | docker run -i ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go
echo "func test(v []string) int { return len(v) + 1; }" > file.go
docker run -i -v $(pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go /workspace/file.go
When you'd like to run shisho outside docker containers, please follow the instructions below:
Run the following command(s):
# Linux
wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-unknown-linux-gnu.zip -O shisho.zip
unzip shisho.zip
chmod +x ./shisho
mv ./shisho /usr/local/bin/shisho
# macOS
wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-apple-darwin.zip -O shisho.zip
unzip shisho.zip
chmod +x ./shisho
mv ./shisho /usr/local/bin/shisho
Then you'll see a…
NOTE: I'm working on Shisho Cloud, a web service providing this kind of best practice checks for infrastructure-as-code and automated patch generation for detected issues. It's in a beta stage now and all features are available for free. Please try it!
Posted on October 13, 2021
Sign up to receive the latest update from our blog.