Photo by Andrey Sharpilo on Unsplash
Most companies I have seen deploy Docker images in at least one project or service. Docker is great because it makes stuff reproducible by specifying the environment to a big degree. However, you still have to think about security. Let’s have a closer look!
Host Security
All Docker containers run on a host system. The host needs to be secure AND the container needs to be secure.
There are various vulnerability scanning, auditing, and hardening tools for Linux systems:
- Lynis: Execute
sudo apt-get install lynis && sudo lynis audit system, wait for a couple of minutes, and you get a pretty nice report indicating what you can do to harden your system. - SELinux: Provides Mandatory Access Control (MAC) as a kernel module. Thomas Cameron gave an introduction to SELinux. The key point for SELinux and AppArmor is the access control policy. Linux, by default, uses Discretionary Access Control (DAC). SELinux and AppArmor enforce MAC. Learn more about the differences. Luc Juggery gave a nice introduction to SELinux & Docker.
- AppArmor: Provides MAC as a service. It distinguishes unconfined and confined processes. It ignores unconfined processes. Confined processes may only do what they are allowed to do according to the AppArmor profile of that process. Seth Arnold gave a nice talk about AppArmor 3.0. Again, Luc Juggery wrote a hands-on guide for AppArmor & Docker.
- Docker Daemon: Run the daemon as a non-privileged user. Especially not as root.
You should run regular checks against vulnerability databases. If they find an issue, you need an effective way to get notified, e.g. by posting to a Slack channel.
You could also use an OS that is optimized for containers, e.g. Google's Container-Optimized OS (COS).
There are many more things to say about the host system, but that is not the focus of this article. If you’re interested, I’ll write a follow-up π
Base Image
The base image is the foundation of your Docker image. Within your Dockerfile,
you define the base image with FROM. For me, it typically is
python:3.8.7-slim-buster or similar. You
need to ask yourself:
- Do I trust the base image’s author to have good intentions?
- Do I trust the base image’s author to have a secure development setup so that malware isn’t uploaded unintentionally, e.g. by leaking the credentials to the account or password re-use?
You should also scan your base image for vulnerabilities. Even for very
standard images, there are often vulnerabilities. Some can be fixed by
directly running an update (e.g. RUN apt-get update && apt-get upgrade),
others don’t have an update within the repository. But pretty often you also
don’t need all the installed stuff.
Be aware that Alpine only shares vulnerabilities that they have already fixed. So the scan might look better for them, although they are not better. Alpine images are smaller, though. So the attack surface is smaller.
Harden Your Image
Hardening is the process of reducing the attack surface or increasing the difficulty to find and use existing vulnerabilities. It reduces the blast radius any ticking bomb in your system could have.
Copy only necessary files
You can use the .dockerignore file to make sure that some files are not added.
Run as a non-privileged user in the container
By default, the code you execute within a Docker container runs with the user ID 0 — with root. It is recommended not to do that. You can change that in multiple ways:
Within the Dockerfile — I prefer that one:
RUN groupadd -r noroot && useradd -r -g noroot noroot
USER noroot
When you start the container:
$ docker run -u 1000 -it python:3.9.1-buster bash
I have no name!@a70ba4f24042:/$ echo $UID
1000
In Kubernetes via runAsUser in the securityContext
(docs).
Multi-Stage Builds
If an attacker gets access to your container, you want them to have as few tools there as possible. Use multi-stage builds for that. Build your code in a build-container and use the built artifact in another container. As a bonus, your image size will be smaller.
The Docker docs give a very good example:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
Harden Your Containers
Read-Only Root File System
This depends on how you run the Docker image, but if you use docker run, you can add the --read-only flag. This makes the root file system read-only. This means that if an attacker gets into the system, they cannot store anything on disk or change any of the executables. They can still change the memory.
You should also be aware that some pretty standard tasks like creating a temporary file obviously don’t work anymore:
$ sudo docker run -it --read-only python:3.9.1-buster
Python 3.9.1 (default, Jan 12 2021, 16:45:25)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tempfile
>>> a = tempfile.mkdtemp()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/tempfile.py", line 348, in mkdtemp
prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
File "/usr/local/lib/python3.9/tempfile.py", line 118, in _sanitize_params
dir = gettempdir()
File "/usr/local/lib/python3.9/tempfile.py", line 287, in gettempdir
tempdir = _get_default_tempdir()
File "/usr/local/lib/python3.9/tempfile.py", line 219, in _get_default_tempdir
raise FileNotFoundError(_errno.ENOENT,
FileNotFoundError: [Errno 2] No usable temporary directory found in ['/tmp', '/var/tmp', '/usr/tmp', '/']
You can work around this issue by mounting /tmp as a volume:
$ sudo docker run -it --mount source=myvol2,target=/tmp --read-only python:3.9.1-buster
Python 3.9.1 (default, Jan 12 2021, 16:45:25)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tempfile; a = tempfile.mkdtemp()
$ sudo docker run --rm -i -v=myvol2:/tmp/v busybox find /tmp/v
/tmp/v
/tmp/v/tmpbhw8djco
Even better is using a tmpfs mount (an in-memory file system):
$ sudo docker run -it --tmpfs /tmp --read-only python:3.9.1-buster
Limit Capabilities
You can limit the Linux kernel capabilities:
$ docker run --cap-drop all -it python:3.9.1-buster bash
root@3c568219116e:/# groupadd -r noroot
groupadd: failure while writing changes to /etc/gshadow
You can then grant the ones your application needs:
$ docker run --cap-drop all --cap-add CHOWN -it python:3.9.1-buster bash
root@3c568219116e:/# groupadd -r noroot
groupadd: failure while writing changes to /etc/gshadow
In Kubernetes, this is done via capabilities in the securityContext
(docs).
no-new-privileges
You might want to always set --security-opt=no-new-privileges. It prevents
container processes from gaining new privileges
(docs).
In Kubernetes, this is called allowPrivilegeEscalation
(docs).
Scanning for vulnerabilities
Clair by quay seems to be a commonly used tool to scan containers for vulnerabilities. I haven’t used it so far, though.
Inter-Container Communication
A key thought of “defense in depth” is to make every single step as hard as possible for an attacker. If something is not strictly necessary for the application to run, it is not allowed. Restricting the way the containers communicate with other containers is one part of that.
Scenario how an attacker is blocked by a controlled network communication / inter container communication. Image by Martin Thoma
Most companies have a lot of different microservices running in containers. Some of the containers need to communicate, others don’t need it. Maybe two have vulnerabilities as shown in the image above. The backend has a vulnerability that allows the attacker to get into the container and another service might suffer from the same issue. But there is no direct way the attacker can communicate with the other vulnerable service and thus harm is prevented.
Have a look at Docker container networking or Kubernetes network policies.
Conclusion
Container Security is an extremely broad field. The NIST Application Container Security Guide is way more extensive than this article; the OWASP Docker Cheat Sheet is of similar length. Tsvi Korren gave a pretty good presentation about container security:
In security, it is hard to recommend what to do. For maximum security, you want to do everything. But a very short and actionable guide would be:
- Make sure you use a well-known, trusted, maintained base image.
- Install only software you need, copy only files you use. Try multi-stage builds if you need software to build the software.
- Use a non-root user.
- Restrict privileges / inter-container communication.
- Use a read-only file system.
- Get a workflow that automatically scans for vulnerabilities and alerts you if anything new was found.
More in this series
In this series about application security (AppSec), we already explained some of the techniques of the attackers π and also techniques of the defenders π:
- Part 1: SQL Injections ππ
- Part 2: Don’t leak Secrets π
- Part 3: Cross-Site Scripting (XSS) ππ
- Part 4: Password Hashing π
- Part 5: ZIP Bombs π
- Part 6: CAPTCHA π
- Part 7: Email Spoofing π
- Part 8: Software Composition Analysis (SCA) π
- Part 9: XXE attacks ππ
- Part 10: Effective Access Control π
- Part 11: DOS via a Billion Laughs π
- Part 12: Full Disk Encryption π
- Part 13: Insecure Deserialization ππ
- Part 14: Docker Security π
- Part 15: Credential Stuffing ππ
- Part 16: Multi-Factor Authentication (MFA/2FA) π
- Part 17: ReDoS π
The following articles are about to come:
- Part 18: Secure Messaging π
- Part 19: Cryptojacking π
- Part 20: Backups π
- Part 21: Cryptotrojans π
- Part 22: Single-Sign-On π
- Part 23: Clipboard Hijacking π
- Part 24: Certificates π
- Part 25: Race Condition Attacks in Blockchains π
- Part 26: Mobile Device Management (MDM) π
- Part 27: Server-Side Request Forgery (SSRF) π
- Part 28: Network Separation π
- Part 29: Social Engineering (including Phishing) π
- Part 30: Virtual Private Networks (VPNs) π
- Part 31: CSRF π
Let me know if you are interested in more articles around AppSec / InfoSec!