Docker accelerates your development and deployment cycles, letting you push code out faster than ever before. But it also comes with an unexpected set of security implications that you should be aware of.
- Container image authenticity
There are plenty of Docker images and repositories on the Internet doing all kinds of awesome and useful stuff, but if you are pulling images without using any trust and authenticity mechanism, you are basically running arbitrary software on your systems.
- Where did the image come from?
- Do you trust the image creator? Which security policies are they using?
- Do you have objective cryptographic proof that the author is actually that person?
- How do you know nobody has been tampering with the image after you pulled it?
Docker will let you pull and run anything you throw at it by default, so encapsulation won’t save you from this. Even if you only consume your own custom images, you want to make sure nobody inside the organization is able to tamper with an image. The solution usually boils down to the classical PKI-based chain of trust.
How to secure?
- The regular Internet common sense: do not run unverified software and / or from sources you don’t explicitly trust.
- Deploy a container-centric trust server using some of the Docker registry servers available in our Docker Security Tools list.
- Enforce mandatory signature verification for any image that is going to be pulled or running on your systems.
Deploying a full-blown trust server is beyond the scope of this article, but you can start signing your images right away.
Get a Docker Hub account if you don’t have one already.
Create a directory containing the following trivial Dockerfile:
# cat Dockerfile FROM alpine:latest
Build the image:
# docker build -t <youruser>/alpineunsigned .
Log into your Docker Hub account and submit the image:
# docker login […] # docker push <youruser>/alpineunsigned:latest
Enable Docker trust enforcement:
# export DOCKER_CONTENT_TRUST=1
Now try to retrieve the image you just uploaded:
# docker pull <youruser>/alpineunsigned
You should receive the following error message:
Using default tag: latest
Error: remote trust data does not exist for docker.io/<youruser>/alpineunsigned:
notary.docker.io does not have trust data for docker.io/<youruser>/alpineunsigned
Now that DOCKER_CONTENT_TRUST is enabled, you can build the container again and it will be signed by default.
# docker build --disable-content-trust=false -t <youruser>/alpinesigned:latest .
Now you should be able to push and pull the signed container without any security warning. The first time you push a trusted image, Docker will create a root key for you, you will also need a repository key for the image, both will prompt for a user defined password.
Your private keys are in the ~/.docker/trust directory, safeguard and backup them.
The DOCKER_CONTENT_TRUST is just an environment variable, and will die with your shell session but trust validation should be implemented across the entire process, from the images building, the images hosting in the registry through images execution in the nodes.
2. Docker security vulnerabilities present in the static image
Containers are isolated black boxes, if they are doing their work as expected it’s easy to forget which software and version is specifically running inside. Maybe a container is performing like a charm from the operational point of view but it’s running version X.Y.Z of the web server which happens to suffer from a critical security flaw. This flaw has been fixed long ago upstream, but not in your local image. This kind of problem can go unnoticed for a long time if you don’t take the appropriate measures.
How to Secure?
Picturing the containers as immutable atomic units is really nice for architecture design, from the security perspective however, you need to regularly inspect their contents:
- Update and rebuild you images periodically to grab the newest security patches, of course you will also need a pre-production test bench to make sure these updates are not breaking production.
- Live-patching containers is usually considered a bad practice, the pattern is to rebuild the entire image with each update. Docker has declarative, efficient, easy to understand build systems, so this is easier than it may sound at first.
- Use software from a distributor that guarantees security updates, anything you install manually out of the distro, you have to manage security patching yourself.
- Docker and micro service based approaches consider progressively rolling over updates without disrupting uptime a fundamental requisite of their model.
- User data is clearly separated from the images, making this whole process safer.
- Keep it simple. Minimal systems expect less frequent updates. Remember the intro, less software and moving parts equals less attack surface and updating headaches. Try to split your containers if they get too complex.
- Use a vulnerability scanner, there are plenty out there, both free and commercial. Try to stay up to date on the security issues of the software you use subscribing to the mailing lists, alert services, etc.
- Integrate this vulnerability scanner as a mandatory step of your CI/CD, automate where possible, don’t just manually check the images now and then.
There are multiple Docker images registry services that offer image scanning, for this example we decided to use CoreOS Quay that uses the open source Docker security image scanner Clair. Quay it’s a commercial platform but some services are free to use. You can create a personal trial account following these instructions.
Read More: Docker Security Tutorial
Once you have your account, go to Account Settings and set a new password (you need this to create repos).
Click on the + symbol on your top right and create a new public repo:
We go for an empty repository here, but you have several other options as you can see in the image above.
Now, from the command line, we log into the Quay registry and push a local image:
# docker login quay.io # docker push quay.io/<your_quay_user>/<your_quay_image>:<tag>
Once the image is uploaded into the repo you can click on its ID and inspect the image security scan, ordered by severity, with the associated CVE report link and also upstream patched package versions.