Understanding how to work with policies is a central component of using Anchore container image inspection and enforcement tools effectively. Anchore policies are how users represent which checks to execute on particular images, and how the results of the policy evaluation should be interpreted.
Why Is A Dockerfile Check Needed
A Dockerfile is a text file that contains all commands, in order, to build a Docker image. In short, it is the blueprint for the container image environment. Since a container is a running instance of an image, it makes sense to incorporate effective mechanisms to check for best practices and potential misconfigurations with the blueprint as early as possible.
Dockerfile Gate
The Dockerfile gate allows uses to perform checks on the content of the Dockerfile or Docker history for an image and make policy actions based on the construction of the image, not just it's content. Anchore is either given a Dockerfile or infers one from the Docker image layer history.
The actual_dockerfile_only Parameter
The actual versus history impacts the semantics of the Dockerfile gate's triggers. To allow explicit control of the differences, most triggers in this gate include a parameter: actual_dockerfile_only that if set to true or false will ensure the trigger check is only done on the source of data specified. If actual_dockerfile_only = true, then the trigger will evaluate only if an actual Dockerfile is available for the image and will skip evaluation if not. If actual_dockerfile_only is false or omitted, then the trigger will run on the actual Dockerfile if available, or the history data if the Dockerfile was not provided.
Triggers
Instruction: This trigger evaluates instructions found in the Dockerfile.
Example of policy looking for the presence of the ADD
instruction:
{
"action": "WARN",
"gate": "dockerfile",
"id": "c35b7509-b0de-4b7a-9749-47380a2f98f2",
"params": ,
"trigger": "instruction"
}
In the above example, if the actual Dockerfile contains the instruction ADD
a WARN action will result. Generally speaking, using the instruction COPY
versus ADD
is considered better practice. Read more about it here.
Effective User: This trigger processes all USER
directives in the Dockerfile or history to determine which user will be user to run the container by default (assuming no user is set explicitly at runtime). The detected value is then subject to a whitelist or blacklist filter depending on the configured parameters.
Running containers as root is generally considered to be a bad practice, however, adding a USER
instruction to the Dockerfile to specify a non-root user for the container to run as is a good place to start. If you do need to run as root, you can change the user to root at the beginning of the Dockerfile, then change back to the correct user with a second USER
instruction.
Example policy to blacklist root user:
{
"gate": "dockerfile",
"trigger": "effective_user",
"action": "stop",
"parameters":
}
Exposed ports: This trigger processes the set of EXPOSE
directives in the Dockerfile or history to determine the set of ports that are defined to be exposed (since it can span multiple directives). The detected value is then subject to a whitelist or blacklist filter depending on the configured parameters.
Example of a policy blacklisting ports 21 and 22:
{
"gate": "dockerfile",
"trigger": "exposed_ports",
"action": "warn",
"parameters":
}
no_dockerfile_provided: This trigger allows checks on the way the image was added, firing if the dockerfile was not explicitly provided at analysis time. This is useful in identifying and qualifying other trigger matches.
Conclusion and Example
Below is a short (and intentionally bad) example of why writing secure and efficient Dockerfiles is important. You can probably spot a good chunk of issues with the following Dockerfile:
FROM node:latest
## port 22 for testing only
EXPOSE 22 3000
RUN apt-get update
RUN apt-get install -y curl nginx
# LABEL maintainer="[email protected]"
ADD example.tar.gz /example
# HEALTHCHECK --interval=30s CMD node healthcheck.js
# USER node
Why is This Not so Great
FROM node:latest
: This doesn't always have to be bad, but it is something to make developers aware of. If we always use thelatest
tag, we run the risk of our build suddenly breaking if that image tag gets updated. To prevent this from occurring, using a specific tag will help to ensure immutability. Additionally, depending on use of your image, you may not need the fullnode:latest
image and its dependencies. Many trusted images havealpine
version which greatly reduce the total image size, thus reducing the possibility of vulnerabilities in packages, and increasing the time of build.- Example of differences is size (node:6 versus node:alpine)
# docker images
node 6 62905ac2c7de 12 days ago 882MB
node alpine ebbf98230a82 2 weeks ago 73.7MB
EXPOSE 22 3000
: We can see as stated in the comment above theEXPOSE
instruction, port 22 is only used for testing, therefore we can remove it when building our production ready images. Note the placement of theEXPOSE
instruction (close to the top).EXPOSE
is a cheap command to run, so it is typically best to declare it as late as possible.# LABEL maintainer="[email protected]"
: We aren't including aLABEL
instruction. It is generally considered a good practice to add labels for organization, automation, licensing information, etc.# HEALTHCHECK --interval=30s CMD node healthcheck.js
: NoHEALTHCHECK
instruction. Typically, this is useful for telling Docker to periodically check our container health status. Great article on why located here.# USER node
: No user-defined. I've explained above why this is important to include. Read more about it here.ADD example.tar.gz /example
: I've mentioned above why usingCOPY
instead ofADD
is considered better practice.
Making the Dockerfile Better
FROM node:6.16.0-alpine
LABEL maintainer="[email protected]"
RUN apt-get update && apt-get install -y
curl
nginx
COPY example.tar.gz /example
HEALTHCHECK --interval=30s CMD node healthcheck.js
USER node
EXPOSE 3000
Many of these mistakes can be checked and validated with Anchore policies and in particular the Dockerfile gate I've discussed in the previous sections. If you are already leveraging Anchore to inspect your container images, I strongly suggest diving into the Dockerfile gate and adjusting it to suit your needs. If not, feel free to take a look at Anchore and how conducting a deep image inspection coupled with flexible policies helps users gain insight into the contents of their Docker images and enforce security, compliance, and best-practice requirements.