Demo OS-level vulnerabilities made possible by a vulnerability in a library installed on the operating system, and used by an application: https://github.com/lirantal/goof-container-breaking-in
In this challenge you should find and fix vulnerabilities in docker images that you test and build in a local environment, or from your CI.
Starting point is this goofy project called docker-goof
which uses an old version of Node.js runtime as a docker image tag.
- Clone the project: https://github.com/snyk/docker-goof
- Inside it you will find a
Dockerfile
with the image to use - Find how many vulnerabilities (low, medium, and high) the image has. Are there any interesting vulnerabilities?
Hint
We'll get started with scanning a docker image using the Snyk CLI which is free to use and scan.
You can use the open source Snyk CLI to scan the image.
- Install Snyk on Windows
- Install Snyk on macOS
- Install Snyk on Linux and other self-contained executables for Windows and macOS.
See Snyk CLI install instructions to get started for the CLI
To scan an image you'll need to pull the docker image to your host locally and then scan it:
$ docker pull <image>
$ snyk test --docker <image>
- Did you find vulnerabilities in the Node.js runtime as well? give an example of one of them and which version of Node.js will fix it.
- How would you fix these vulnerabilities?
- Find which official Node.js image you could switch to in order to lower your vulnerabilities footprint without disrupting the product and development teams too much.
Hint
If the Snyk CLI is provided with a Dockerfile it will give you a remediation advice so you can make a conscious decision of which image you could move to in order to lower the security vulnerabilities foot-print.
What happens if you provide the Snyk CLI with the Dockerfile as well?
snyk test --docker <image> --file=Dockerfile
Solution
snyk test --docker node:10.4.0 --file=Dockerfile
Don't forget - Lowest vulnerabilities & ease of upgrade wins the challenge!
Find and fix vulnerabilities in docker images in a Docker Hub registry or others.
Bonus challenge - how do you monitor your docker images on a container registry and do you have an actionable advice as to which image and tag you should change to in order to lower the footprint of your image's security vulnerabilities?
Your team needs to update to the latest version of Node.js 10 LTS to get fixes and keep up to date with security vulnerabilities.
You do the obvious:
docker pull node:10
- Can you think of some downsides to pulling the image this way?
When you pulled the image you specified the image name and a tag. But what's the implications of using a tag like that?
Each Docker image can have multiple tags, which are variants of the same images. The most common tag is latest, which represents the latest version of the image. Image tags are not immutable, and the author of the images can publish the same tag multiple times. This means that the base image for your Docker file might change between builds. This could result in inconsistent behavior because of changes made to the base image.
source: 10 Docker Image Security Best Practices
The above best practices document stresses that we should be as specific as possible in our tags, and ideally we'd pull in the image by its SHA256 reference.
- Pull in the image based on its SHA256
Hint 1
If you pulled the node:10
image, take a look at the output
Hint2
You're looking for the Digest
key in the output of docker pull
Solution: how to pull the image by its SHA256
docker pull node@sha256:bdc6d102e926b70690ce0cc0b077d450b1b231524a69b874912a9b337c719e6e
Reminder: if you're coming here from previous challenges, did you remember to turn Docker's Content Trust policy off? Right, you need to do that:
export DOCKER_CONTENT_TRUST=0
How do you help your devops and developer engineers to validate the proper configuration of a Dockerfile
when they are building them? Aaaaaaaaa-utomation!
We'll visit a couple of static code analysis tools to help us find out issues in a Dockerfile
, or what us in the JavaScript land like to call - linters. There are a couple I recommend, and you're welcome to try both of them:
- Dockle - Puts a focus on scanning the image through its layers.
- Hadolint - Statically analyze the Dockerfile
For this challenge we'll work with the project in the bad-defaults/
directory.
Use any of the linters above and test the Docker file/docker image.
Only after you found issues with either or both of the above linters should you continue to the issues below in order to fix them.
- Build the bad defaults image
Hint
docker build -t best-practices .
- Check if the container is running with the root user. If so, that's not good.
Solution
docker run -it --rm best-practices:latest sh
Inside the container run:
whoami
- Fix the issue so that the container uses a non-privileged user
Solution
Update the Dockerfile to make use of the built-in `node` user:
...
USER node
CMD node index.js
Now build the container and run again to check which is user is being used.
Did the Dockerfile
smell somewhat fishy to you in the previous challenge?
It was smelly of secrets and tokens!
I am specifically referring to this entry in the Dockerfile
:
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
- First assignment, can you think about why this is bad? After all, we're building the image in an internal environment and only we pass that secret token at build time. No one else sees it.
Hint 1
The .npmrc
file contains sensitive information, such as a token which is used for read/write for private packages on a registry. If the container is compromised, users will be able to access it.
Can you think of a simple vulnerability in an application that will allow a malicious attacker to easily get to the .npmrc
file?
You can build it yourself and try:
export NPM_TOKEN=<npm token>
docker build -t best-practices --build-arg NPM_TOKEN=$NPM_TOKEN .
Now login to the container and validate the value of .npmrc
:
docker run -it --rm best-practices sh
- Can you think of some other ways to fix this issue of secrets leaking? Below are hints for 2 "solutions". They might prove like a good idea for you but we explain why they shouldn't be followed.
Bad practice 1
You remember to remove the token, such as:
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
RUN npm install
RUN rm .npmrc
Nice, but not really good.
Every RUN
creates another layer, all of which are later inspect-able and leave a trace. This means that if the image itself is ever leaked or made public then sensitive data exists inside it in the form of the .npmrc
file.
Bad practice 2
You understand the concept of Docker layers so you put all of this into one command to make sure there's no trace, something like this:
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc && \
npm install && \
rm .npmrc
However, Docker has this thing called commits history which it uses to save metadata about the way the image was built and this is why you should never really use environment variables such as build arguments for sensitive storage such as passwords, API keys, tokens.
Read more about Docker history here.
Hint: for proper solution
What if you could create a Docker image without the .npmrc
file in it?
Solution
Let's use multi-stage builds to fix it!
Update the Dockerfile
so that the first image is used as a base to install all of our npm dependencies and build what is required. To do that, update the FROM instruction as follows:
FROM bitnami/node:latest AS build
As well as remove the CMD
instruction which isn't needed.
Then have another section in the Dockerfile for the "production" image, which should use the app directory which is now ready for use from the previous build image.
Following is an example:
FROM bitnami/node:latest
RUN mkdir ~/project
COPY --from=build /app/~/project ~/project
WORKDIR ~/project
CMD node index.js
An example of a full multi-stage Node.js docker image build Dockerfile:
FROM node:12 AS build
RUN mkdir ~/project
COPY app/. ~/project
WORKDIR ~/project
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
RUN npm install
FROM node:12-slim
RUN mkdir ~/project
COPY app/. ~/project
COPY --from=build /~/project/node_modules ~/project/node_modules
WORKDIR ~/project
CMD node index.js
Docker image security best practices workshop © Liran Tal, Released under CC BY-SA 4.0 License.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.