Skip to content

lirantal/docker-images-security-workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Docker Image Security workshop

1. Challenge: Find the most vulnerability-free image

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.

  1. Clone the project: https://github.com/snyk/docker-goof
  2. Inside it you will find a Dockerfile with the image to use
  3. 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.

Install the Snyk CLI

You can use the open source Snyk CLI to scan the image.

See Snyk CLI install instructions to get started for the CLI

Scan a docker image for security vulnerabilities

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>

  1. 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.
  2. How would you fix these vulnerabilities?
  3. 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!

Registry workflow

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?

2. Challenge: Don't steal my immutability!

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
  1. Can you think of some downsides to pulling the image this way?

Use fixed tags for immutability

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.

  1. 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

3. Challenge: Really bad practices by default

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:

  1. Dockle - Puts a focus on scanning the image through its layers.
  2. 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.

Container is running as root

  1. Build the bad defaults image
Hint
docker build -t best-practices .

  1. 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

  1. 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.


4. Challenge: All your secrets belong to me!

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
  1. 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

  1. 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

Author

Docker image security best practices workshop © Liran Tal, Released under CC BY-SA 4.0 License.

License

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.