If reinforced windows are our 'application security', and burglar alarms are our 'container monitoring', then runtime security would be never having bought anything worth stealing.
This is part #1 of the Guide to Pragmatic Container Security - today focussing on Runtime Security. In this post we’ll review the most common threats our container workloads face, and propose the 8 most-impacting controls you can implement to reduce the risk associated with compromised container workloads.
Our first step is understanding the types of attacks seen in the wild mounted from compromised container workloads:
With this in mind, we can now outline our threat-informed controls to mitigate these attacks.
For those pretending to be in a rush, here’s the quick list of recommendations to maximise runtime security impact:
root
/var/run/docker.sock
And with that facade of helpfulness out of the way, let’s take a gentle dip into impact-based container runtime security.
Control #1: Don’t run privileged containers:
Overview: Privileged containers run with full uninhibited access to their host machines - allowing for effortless container breakout.
Risk mitigated:
Preventing privileged containers from being deployed hugely reduces the likelihood of a successful container breakout, as if a privileged container is compromised it’s game over
How to implement:
--privileged
flagsecurityContext
setting privilegedHow to mature:
To mature this control, you can implement policies to block running privileged containers:
docker run
and compose.yaml
from
creating privileged containersControl #2: Use default SecComp security profile
Overview: This is a Linux security module used to set limits on the syscalls available to a container, restricting a processes ability to interact with the kernel
Risk mitigated:
mount
syscall!How to implement:
DefaultProfile
) in Docker, but will not run as
default in KubernetesHow to mature:
<shameless plug>
I’m actually working on
a tool for auto-profiling and deploying tailored SecComp
profiles for containers running in Kubernetes Pods - k8seccomp
(if you every try doing this manually for a multi-node
cluster you’ll realise how annoying this can be - never mind
AppArmor too!)Control #3: Use default AppArmor security profile
Overview: This is a Linux security module used to set mandatory access control for container processes (restricting container’s access to low-level resources like files and processes)
Risk mitigated:
How to implement:
In this case we’re just going to use the default profile chosen by your container runtime (which is often based on this template)
Within Kubernetes clusters:
runtime/default
value when defining your
AppArmor annotationWithin Docker:
docker run --security-opt "apparmor=docker-default"
security_opt: apparmor:docker-default
How to mature:
Control #4: Don’t add unnecessary capabilities
Overview: When running containers we can specify additional Linux
capabilities for the container to be granted, ranging from the innocuous CAP_SYS_NICE
to the more suspect CAP_SYS_ADMIN
Risk mitigated:
How to implement:
--cap-add
flag
being used, and audit those capabilities being introduced into
the container workloadHow to mature:
Control #5: Don’t run containers as root
Overview: This gives root-level access within the container, increasing the attack surface upon a successful container compromise
Risk mitigated:
How to implement:
docker run --user:1000:3000
How to mature:
Control #6: Don’t mount /var/run/docker.sock
Overview: This gives full Docker access to the container, allowing it to interact with Docker resources. Used commonly to create privileged containers that can then be hopped to, for privilege escalation
Risk mitigated:
How to implement:
docker run -v /var/run/docker.sock:
volumes: /var/run/docker.sock:
volumes
and
volumeMounts
specified in Pod manifests that
reference the /var/run/docker.sock
pathHow to mature:
Control #7: Don’t mount unnecessary host directories
Overview: Through freely mounting host directories to a container, we’re effectively breaking down the isolation of the container. This can lead to the host being modifiable from within the container, which can be significant in the case of a compromised container.
Risk mitigated:
How to implement:
docker run -v
or
volumes:
in Docker, and audit these closely.volumes.hostPath
and
volumeMounts
in Kubernetes Pod manifest files to
audit unnecessary host directory mounts for containers running
in KubernetesHow to mature:
Control #8: Use Docker content trust for pulling images
Overview: Docker Content Trust allows us to ensure the integrity of images we pull from our container registry through usage of cryptographic signatures.
Risk mitigated:
How to implement:
We first need to mandate signing our images. This is done through use of Docker’s Notary service.
Create a signing key and a signer entity with:
docker trust key generate <repo>
to
generate a key-pair for a repositorydocker trust signer add <name> <repo>
to add a signer for a repositoryThen when pushing an image to your registry, sign it:
docker push <repo>/<image>:<tag>
docker trust sign <repo>/<image>:<tag>
This will add a digital signature to that image tag in
your container registry, which can be used in subsequent
docker pull
requests to validate the integrity of
these images:
DOCKER_CONTENT_TRUST=1
in your command
line (for running images via docker run
or in your
manifest files if deploying containers via Kubernetes)docker pull <repo>/<image>:<tag>
will now mandate that the image tag must be digitally signed,
and will validate this signature before allowing the image to be
deployed as a containerHow to mature:
Thus marks the end of our foray into pragmatic container runtime security controls. Implementing these recommendations will mark a huge step in reducing the likelihood of container-mounted attacks in your environment, and will set the foundations to continue building effective and threat-driven container security controls into your organisation.
The end.