Among the many advantages that containerisation and microservices architecture has introduced is the ability “build once, run anywhere”. Unlike monolithic workloads, an application and all of its dependencies can be bundled together and isolated from the rest of the machine it’s running on. Done right, this brings a multitude of security benefits. From an operational standpoint, the containerised application has everything it needs, can be packaged as a container image with the assurance that it will run the same locally or on a cloud host. This also supports automation and deployment with orchestrators such as Kubernetes.
As part of a defence-in-depth strategy, multiple layers have to be secured from code to cloud, including source code (especially Containerfiles/Dockerfiles), as well as the build environment and production environment. Using threat modelling, we have to consider what can potentially go wrong at every layer and what we can do to mitigate these risks. There are of course malicious external attackers that we must defend against, but equally, inadvertent internal mistakes or misconfigurations can negatively impact the confidentiality, integrity and/or availability of our container workloads. As evident from the CNCF’s Financial User Group K8s threat model and attack trees there are also multiple threat vectors in a Kubernetes environment that have to be addressed and sit beyond the security boundary of the deployed container itself.
How for example can there be assurance that a pulled image is exactly the same as what was pushed to a registry? If a threat actor can tamper with an image or modify an image between build and deployment, this would allow for arbitrary code to be executed in the deployment. One way to do this would be to sign the container image and ensure that your admission controllers perform vital security checks on the container image before it is instantiated into a running container. This would ensure that signature validation can take place among many other checks, including disallowing privileged pods, running as root, and host volumes from being mounted.
Shifting left, we need to ensure the security of the build environment, and use tools in our CI/CD pipeline that include Containerfile/Dockerfile linters, image vulnerability scanners and secrets scanners. Privileges to edit Containerfiles/Dockerfiles should also be restricted to the necessary trusted individuals with all commits signed within a source code management and version control system.
We also need to restrict access to the image registry and ensure that immutable tags are used. Other measures we can take are to reduce the attack surface by using either a minimal base image such as Alpine or using distroless. Are checks in place to prevent containers running as the root user and are we running these as read-only where possible to ensure immutability? Are runtime security solutions (e.g. Falco) – often using eBPF – in place to ensure only expected executables are running inside containers? Are seccomp or AppArmor profiles being used? These are just a small handful of necessary checks among others that need to be in place and of course we also need to pay similar attention to the container hosts and underlying cloud infrastructure.
Fundamentally understanding how containers work can put us at a significant advantage in better understanding threat vectors and how to mitigate these, but putting together a checklist customised to your particular deployment environment as well as conforming to broader security principles such as least privilege, defence-in-depth, logging and monitoring, etc. can all have a major impact in ensuring the security of container workloads.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.