At Adobe I/O, we’re working on building a serverless platform that allows you deploy custom code to respond to events and execute functions. Adobe I/O Runtime lets developers run code on top of the Adobe Cloud Platform, giving access to the content and data you have stored with Adobe. Adobe I/O Runtime is built on top of Apache OpenWhisk, an open source serverless platform that leverages containers as the means of deploying code (something I’ve written about in my book, Practical Docker with Python). With custom code, however, comes the need for tighter security.
Containers have almost become the ubiquitous method of packaging and deploying applications. While containers are perceived to be completely isolated and secure methods of running your application, the reality is that containers are not completely foolproof and are susceptible to many attack vectors, including:
- Noisy neighbors
- Within containers
- External world
- Within the application
Despite these threats, there are different mechanisms to increase container security. Some of these mechanism are applied at container image build time, while others applied during the container runtime.
Securing images at build
Images are the basic building blocks of containers. Creating an image is as simple as selecting a base image and adding a few Dockerfile instructions. While there are Docker base images of popular Linux distributions such as Ubuntu, selecting those as the base image not only significantly increases the size of the Docker Image, but we also end up inheriting all the packages available in the operating system. Unfortunately, this means that we also inherit the vulnerabilities associated with these packages.
With the number of bots and exploits that are out in the wild, it is critical that containers are scanned for vulnerabilities and are blocked from being deployed if they cross a certain threshold. There are numerous container security scanners (Aqua Container Security, Clair, and Anchore to name a few), which integrate with the Continuous Integration pipeline, scan the image when a new change is pushed, and prevent the image from being pushed to the container registry. This ensures that the vulnerable images are never available to be deployed.
Docker Hub lets anyone publish their Docker images; however, there is no way to verify that the images actually do what they claim to do. Recently, Docker removed a number of images from Docker Hub because the images had embedded cryptocurrency miners, named after popular software packages such as MySQL. With these threats in place, it’s imperative that images are built in-house by starting from a thin base image, with the built images being published to a private registry.
Securing images at runtime
The Linux kernel offers several mechanisms for improving runtime security of containers such as cgroups, namespaces, kernel capabilities, and seccomp. Let’s take a look at these:
Cgroups, or control groups, is a Linux kernel feature that limits, groups, and isolates resource usage (including CPU, memory, disk and network I/O) of a collection of processes. Control groups let the Docker engine share available hardware resources, and if configured, can enforce limits and constraints. For example, limiting the available CPU to a container ensures that a misbehaving container doesn’t bring down the entire Docker host. Applying limits are as easy as passing a few extra flags to the Docker engine:
docker run — memory=2g #Limit the container to use upto 2GiB of memory
docker run — cpus=”0.5” #Only half of the available CPU resources of the host machine are available to the container
Namespaces is an abstraction that makes it appear that processes have their own isolated instance. Namespaces ensures that the processes (and other resources) running with a container cannot see the processes running in other containers or in the host system. The Docker Engine makes the following Namespaces available:
- PID: To achieve process isolation (PID: Process ID).
- NET: Networking isolation
- IPC: InterProcess Communication isolation
- MNT: Filesystem mount points isolation
- UTS: Hostname isolation
With version 1.10, Docker also introduced User namespaces. This means that Docker will remap a user within a container to a different one outside the host. The advantage of this is that we can remap a privileged user within the container to a non-privileged one outside the container. This way, in the case of a container breakout scenario, the damage done to the host is reduced.
Seccomp or Secure Computing Mode is a Linux kernel feature that restricts the actions available within a computer. This is done by filtering and restricting the system calls (syscalls) that the container can do. Seccomp is available by default, assuming the ost machine’s kernel is configured with CONFIG_SECCOMP enabled.
Enabling seccomp doesn’t need any other change apart from the Linux kernel support mentioned above, and any container started will run with Docker’s default seccomp filter, which blocks 44 syscalls of the 300+ syscalls available. Docker also lets you provide a custom seccomp filter, allowing you to further improve security.
Kernel Capabilities lets us drop unwanted (or alternatively, add) capabilities from the container. Capabilities are a superset of syscalls; usually, a few related syscalls are grouped into a capability.
For example, assuming there’s no reasonable need for a container to require the capability to change the owner of the file, we can drop the chown capability by passing:
docker run — cap-drop=chown
LSM (Linux Security Modules)
Linux Security Modules is an architecture that enables Mandatory Access Control modules. Mandatory Access Control is a type of access control in which the ability to perform an operation is constrained and any operation is tested against a set of rules to determine if the operation can succeed. Some examples of LSM include AppArmor and Security Enhanced (SE) Linux.
Docker ships with a template that works with AppArmor. There are SELinux policy templates available. By fine tuning and applying these policies, we can apply an extra safety net over the running container.
The container security landscape is still nascent and there are several suitable means of reducing the risks brought on by running untrusted code. Containers are far from providing foolproof security; by implementing the above mentioned controls, you’ll be able to considerably reduce the risk you face, strengthening the overall security of the platform.