Hardening Kaniko build process with Linux capabilities

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.

capdefault-edit.png

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.

capdropall-edit.png

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

capdropsome-edit.png

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.