Part I: Deployment and Container Orchestration
Part II: Container Security
Part III: Container Deployment
Part IV: Introduction to Kubernetes
Part V: Kubernetes Administration
Part VI: Kubernetes Security Checklist
To continue the series and follow up on Part I which was about deployment and container orchestration, here we dive deeper into container security. Containers offer significant advantages over traditional virtual machines and bare metal servers. Unlike VMs, containers utilize operating-system-level virtualization and share the host kernel, resulting in smaller sizes, faster deployment, and efficient resource management. However, container security should be a top priority.
Containers have become synonymous with application development. But how did we get to this point and what exactly does a container offer that traditional bare metal servers and virtual machines were lacking?
The two most prominent mechanisms for virtualization are traditional virtual machines (VMs) and containers. Both of these technologies allow us to maximize the utility of a physical server by sharing compute resources. However, the way in which VMs and containers share these resources are quite different:
Containers are not a new concept; much of what they utilize to build and run a container runtime environment has been around in the Linux kernel for many years. Most of the core tenets of what make up a container are available right out of the box in Linux.
In order to begin assembling what looks like a container from scratch in Linux, start with the use of a namespace. Namespaces come in different flavors:
In Kubernetes, namespaces are a particularly helpful way to create logical separation within a cluster.
Control groups (cgroups) simply limit the amount of resources a collection of processes may use – such as CPU, memory, and disk I/O. Cgroups help ensure containers do not consume more resources than permitted, especially in a Kubernetes environment.
Containers donʼt just run on the same host; they also share the same kernel. Isolating containers has to do with limiting their access to resources as needed. It is an extremely important security control, as you wouldnʼt want an attacker that compromises a container to be able to escape the container and get access to the host or other containers.
Container isolation can be achieved through various sandboxing techniques:
Both SELinux and AppArmor have a log-only mode which is useful for creating and testing new profiles. Creating a SELinux profile requires in-depth knowledge of the files needed by the application. Overall, Seccomp, SELinux and AppArmor work at a very low level. Creating a complete profile from scratch can be rather cumbersome.
Maintaining the profile can be equally hard, as even a small change in an application may require significant changes to the profile in order to work. At the same time, using all three tools is highly recommended from a security perspective. If you donʼt have the resources or the expertise to build custom profiles, it is always better to use the default ones rather than not using them at all.
An image is essentially a snapshot or template of a container in the form of an immutable file. A ‘running’ image can be considered a container.
There are several threats associated with container images:
An image works great if you are building and launching containers on your laptop. But how do you distribute the image to other individuals or systems in the CI/CD pipeline? This is where a registry comes into the picture.
A registry is simply a location used to store and distribute container images. The registry can either be open to the public or private, and is tightly integrated with the Command Line Interface (CLI) tool. It serves as a target for running push and pull commands.
You can use trusted public registries or run your own private registry. Only images that are stored on those registries should be allowed. If you choose to run a private registry, make sure you secure it by placing it behind a firewall and controlling who can upload and download images from it. You may be tempted to allow access to your entire team. This may be convenient, but donʼt forget the principle of least privilege as this way you are actually increasing your attack surface.
Due to their layered nature and extensive use of third-party packages, container images are inherently dangerous to pull into a trusted environment and run blindly. For example, if a given layer in an image contains a version of OpenSSL that is susceptible to the Heartbleed attack, you can identify all upstream software with vulnerabilities simply by looking for images built with that layer. Images can be patched quickly by simply replacing the layer containing the vulnerability and rebuilding the container to use up-to-date, fixed packages.
There are a number of ways to handle the scanning of container images. Ideally the task is a pass/fail step in your Continuous Integration and Continuous Delivery (CI/CD) pipeline, where images with known vulnerabilities are rejected before deployment. You can run vulnerability scans even during development so that you can fix issues that arise before pushing the code to a repository. Several image registries have vulnerability scanning built in. It is a good idea to periodically scan all images in the image registry, as new vulnerabilities are continually discovered. Identify and replace running containers based on vulnerable images.
Other command line tools – such as Trivy, Clair or Anchore –allow scanning automation to easily run in a CI/CD build pipeline. During such scans you should be analyzing the content and composition of images to detect apart from vulnerabilities, security issues, and misconfigurations. Scan the operating system, libraries running within the container and their dependencies. A good idea is to also check if there are any secrets stored in your images.
The following recommendations can help you safeguard your container images against threats like the ones we have described here:
We have not yet gotten to the section about orchestration and Kubernetes security in this series, but it is important to call out early on that container security is not Kubernetes security. You can run containers without using Kubernetes to orchestrate them. See these key pre-requisites to Kubernetes security and see more detail here with a full explainer.
One thing to note is that many third-party tools for Kubernetes are installed via container images, so container security that checks images running in production, not just prior to production, is also relevant for understanding CVEs and dangers in those images, like the recent CVEs for Fluid and Bare Metal Operator.
The fact that a Docker image is stored in a public registry does not automatically mean that it is safe to use and free of vulnerabilities. In 2020 Prevasion conducted an analysis of 4 million container images stored at Docker Hub. Of these, 51% contained packages or dependencies that had at least one critical vulnerability. There were also 6000 images that contained malware, crypto-mining software, trojans, and other types of malicious software.
In early 2021, Codecov experienced a breach that affected a large number of company CI/CD build pipelines. Codecov gives developers tools to help ensure tests are efficient and CI is streamlined. Their own Docker build process was compromised: an attacker took advantage of the script called “bash-uploader,” and the image was distributed through normal channels to users. The unknowing developers who ran this version of Codecov were in for a surprise.
The Codecov CI plugin siphoned secrets, environment variables, AWS account tokens, and more straight from their CI systems (including Github Actions and other tools.
Never blindly pull images and run them in highly sensitive environments. Always inspect the image if possible, scan it for vulnerabilities, and watch how it behaves in a sandbox environment in a running state (for example, look for outbound network connections and side- loaded binaries).
Containers offer advantages such as efficient resource utilization, faster deployment, and easy scalability. By sharing the host operating system's kernel, containers enable greater flexibility and faster startup times compared to virtual machines. However, container security is a crucial consideration, as containers share the same kernel and potential vulnerabilities can pose risks to the host and other containers. Implementing security measures like namespace partitioning, control groups, seccomp, AppArmor, SELinux, and vulnerability scanning can help mitigate these risks. In the next post in this series, we will get into container deployment techniques.