Hardening Kaniko build process with Linux capabilities
Build images inside Kubernetes/containers? Wide privileges in default configuration? How to secure Kaniko? Can we take things a notch higher?
Introduction to Kaniko
Kaniko is a tool to build container images from a Dockerfile, inside a container or Kubernetes cluster. More information on usage, here.
Kaniko doesn't depend on a Docker daemon and executes each command within a Dockerfile completely in userspace. This enables building container images in environments that can't easily or securely run a Docker daemon, such as a standard Kubernetes cluster.
There are tons of tutorials on the internet on how to use Kaniko. Rather than focusing on regular usage, we will take things a notch higher by hardening Kaniko build process to ensure better security.
Introduction to security concerns
- Kaniko runs as a container to build images
- Kaniko runs as an unprivileged container
- But that container runs as a root user
Running container as a root user is not recommended but here kaniko will be used for building the images (unpacking other images, changing permissions, etc), so kaniko requires a certain level of privileges. Hence, the root user is mandatory.
Internal working
Running container as the root user is a concern when the container is having a wide range of Linux capabilities that can lead to compromise of the host machine. To prevent this attack, we will drop all the capabilities for this kaniko container. But as discussed above, Kaniko requires a specific set of capabilities to perform operations. Let's try to explicitly add those capabilities to Kaniko container, so it will have the ability to build an image.
I have tried building multiple Dockerfiles
performing different sets of operations. Post review, I found the following capabilities are required for kaniko to build an image.
- CHOWN
- SETUID
- SETGID
- FOWNER
- DAC_OVERRIDE
Hands-on experience
I used a simple Dockerfile
for testing.
FROM alpine
ENTRYPOINT ["/bin/sh", "-c", "echo hello"]
Default capabilities list
Build the image
docker run --name capdefault -v $(pwd)/Dockerfile:/Dockerfile -v $(pwd):/kaniko-context -it gcr.io/kaniko-project/executor:latest -f /Dockerfile -c /kaniko-context --no-push
Capture the PID of the above process
ps -ef | grep capdefault
Review the bounding set capabilities for that process. CapBnd
will help.
grep Cap /proc/<PID>/status
Decode the CapBnd
value to view the list of capabilities associated with that process.
capsh --decode=<CapBnd_Value>
Finally, we can see a wide range of Linux capabilities associated with the container with default settings that can open a gateway to numerous attacks & privilege escalations.
Dropped all capabilities
Try dropping all the capabilities & building the image.
docker run --name capdropall --cap-drop=all -v $(pwd)/Dockerfile:/Dockerfile -v $(pwd):/kaniko-context -it gcr.io/kaniko-project/executor:latest -f /Dockerfile -c /kaniko-context --no-push
When all capabilities are dropped, kaniko won't be able to build an image.
Dropped all capabilities & added only required capabilities
As discussed in the Internal working section, drop all capabilities and add the capabilities that are required only for building images.
docker run --name capdropsome --cap-drop=all --cap-add CHOWN --cap-add=SETUID --cap-add=SETGID --cap-add=FOWNER --cap-add=DAC_OVERRIDE -v $(pwd)/Dockerfile:/Dockerfile -v $(pwd):/kaniko-context -it gcr.io/kaniko-project/executor:latest -f /Dockerfile -c /kaniko-context --no-push
Conclusion
A wide variety of Dockerfiles should be passed to kaniko as unit tests to understand the required list of capabilities for Kaniko. If we feel any additional privileges are required to build specific Dockerfiles, we can add those capabilities to the Kaniko build container explicitly.