In Red Hat Enterprise Linux 8.1 beta, we included a new utility for generating tailored SELinux policies for containers. To learn what Udica offers, why you might want to start testing Udica, and how to get started, read on!
Confined container environment
Containers in the default Red Hat OpenShift environment are separated from each other, and from the system, in part by SELinux technology. SELinux is a process isolation technology which can help mitigate or block various attacks on the system. A recent flaw is the "malicious container escape" (CVE-2019-5736), which could enable an attacker to escape from the container namespace and run arbitrary code on the host system with root privileges. Fortunately, SELinux in Enforcing mode helps make this exploit ineffective.
While it is our observation that no customer environment is the same, we see that most if not all containerised environments have the same need for isolation that can be provided by SELinux.
In order to satisfy diverse customer environments, container SELinux policies need to be present, but also easily tailored. Our way to achieve the easy tailoring of our policies, is to use Udica—a tool for generating tailored SELinux policies for production containers. In the following paragraphs we introduce Udica and its features.
SELinux policy for containers
By default, containers on OpenShift are confined by one general SELinux policy for all containers on the system. The SELinux type for container processes is
container_t. This policy allows containers to read or execute files in
/usr only and to read, write and execute all files on the system labeled
container_file_t. As you can see this is really strict confinement, and mainly protects the host system from container processes.
However, there is also the Super Privileged Container SELinux policy
spc_t, with this policy the container is basically unconfined from SELinux point of view. This policy has to be specified by the system administrator during container startup.
All containers use the same SELinux type (
container_t) which protects the host system, but how do we separate and protect containers from attacking each other?
For this purpose, Multi Category Security (MCS) is enabled for the
container_t SELinux type. Container runtimes (docker, Podman, CRI-O) will dynamically assign two categories when starting a container and concatenate these categories to the SELinux label of the running container. Categories can be unique and randomly generated, or can be specified by the system administrator who is starting containers. These categories protect containers from attacking each other even though they have same SELinux type.
Example of containers labels:
container1 - container_t:s0:c349,c851
container2 - container_t:s0:c270,c963
Problems with SELinux Container Confinement
The current solution brings several problems with confining containers. For all containers there is just one general SELinux policy. This cannot fulfill the ideal balance between security and usability for containers.
On one hand, for certain use cases container type (
container_t) is too strict, such as when some directory is bind mounted to container filesystem namespace. This is required to allow container processes to access this directory.
A real example is a container with the Fluentd service inside—this container needs to read log files in
/var/log on the host filesystem.
On the other hand, for certain use cases the container type is too loose. There are two main situations when SELinux policy should be tighter. The first one is network controlling, when all container processes can bind to any network port. The second one is capability controlling, when all container processes can use all Linux capabilities.
We can approach these problems with two solutions.
In the past there were two solutions how to solve all problems mentioned in the previous section.
The first would be to write completely new SELinux policy for custom containers. This has been the best solution so far and it can help tailor security policy to the needs of your application. However, it’s difficult for system administrators because deep SELinux expertise is required.
The second is to create a new SELinux custom module with additional rules for the generic SELinux type
container_t and load this custom module to the kernel. This is not really a good approach, as additional allow rules will be granted to all containers on the system, because container_t is a generic label assigned to containers by default and deep SELinux expertise is required.
To bring a solution which can address the disadvantages mentioned above, the Udica tool was created. Udica is a tool for generating SELinux security policies for containers. The whole concept is based on the "block inheritance" feature inside SELinux's Common Intermediate Language (CIL). This is an intermediate language supported by SELinux utilities.
Udica creates a policy which combines templates using rules inherited from a specified CIL block and rules discovered by the inspection of the container JSON (lightweight data-interchange format) file, which contains mount points and ports definitions.
How it works
The process of generating SELinux policy for a container using Udica could be split into three main parts:
Parsing the container spec file
Finding suitable allow rules based on the results of the first part
Generating final SELinux policy
During parsing, Udica parses the spec file of a container which can be provided on its standard input or using a command-line parameter. The tool requires the inspection of a file in JSON format. The container the policy is being generated for doesn’t need to be running on the system where the tool is executed. Udica looks for three types of information:
Based on the results, Udica detects which Linux capabilities are required by the container and creates a SELinux rule allowing all these capabilities. The network ports are a similar situation, Udica uses SELinux userspace libraries to get the correct SELinux label of a port that is used by the inspected container.
This step applies only if the container binds to a specific port. As the next step, Udica detects which directories are mounted to the container filesystem namespace from the host.
CIL's block inheritance feature allows Udica to create templates of SELinux allow rules focusing on some concrete action, like: "Allows access home directories, Allows access log files, Allows access communication with Xserver, etc." These templates are called blocks and by building ("merging") blocks together the final SELinux policy is created.
Let’s say Udica generates SELinux policy with the name
my_container for example container which is mounting the
/home directory from the host filesystem to container filesystem namespace. The directory
/home is used for storing users data on the filesystem. Our example container is also exposing port tcp/21, used for the File Transfer Protocol (ftp).
Udica detects that the
/home directory is mounted to container space and the port tcp/21 is exposed off the container inspection file. The tool searches for existing blocks (templates), and if the suitable block doesn’t exist, Udica generates a new one, just for this SELinux policy. In this case, all blocks exist, and are used for final SELinux policy.
The first block which is used is the "base" block, it’s used in every SELinux policy generated by the Udica tool. The base block is is quite similar to SELinux policy included in the container-selinux rpm package. This is used by Podman and CRI-O when starting containers. It allows reading and executing files in "/usr" and reading generic configuration files stored in "/etc."
The second block which is used is the "net" block, because the example container is exposing tcp port 21. The tool will enhance the block by adding the bind permission only for the tcp port 21 labeled by SELinux as
ftp_port_t. The third block used is the "home" block, because the example container mounts the
/home directory and based on the information in the container inspection file, the tool can use a block just with read permissions or read/write permissions. All these blocks are shown in figure 1.
In the third phase of the Udica tool workflow, all blocks are merged and enhanced based on the needs from the inspection container file, and the final SELinux policy for example container is generated. This process is described in Image 2. The final SELinux security policy for the example container can be loaded immediately or moved to another system where it can be loaded via the SELinux userspace semodule tool.
Supported container engines and systems
The Udica tool now supports generating SELinux security policies for containers started from podman and docker container engines. The CRI-O container engine is expected to be supported in the near future. Already generated SELinux policies could then be used for containers using podman, CRI-O, docker engines. SELinux security policies generated from the Users have been able to Udica tool with Fedora since Fedora 28 and on Red Hat Enterprise Linux 8.1 beta.
Udica is a new tool which complements the containers tools (Podman, Skopeo, Buildah, etc.) family supported by Red Hat to help improve the security of customers container environments. It provides a new way to help deal with 0-day vulnerabilities in containers tooling and malicious programs inside containers.
This tool provides a solution finer with grained control, essentially placing it between
spc_t (Super Privileged Container) and
container_t (Standard Containers). When a container is labeled as
spc_t by SELinux, basically the container is unconfined from the SELinux point of view. This is undesirable for security reasons. Our goal is to make SELinux security technology more user friendly and help system administrators in their job to provide highly secure production environments.
The name "Udica" means "Fishing rod" in the author's native Slovak language, and it was selected based on this popular though contested quote:
Give a man a fish, and you feed him for a day. Teach a man to fish, and you feed him for a lifetime.
With this tool we provide you a fishing rod not to fish, but to custom-tailor your policies without the need for SELinux policy expertise. We welcome you to use the tool and please let us know about your experience!
About the author
Lukas Vrabec is a Senior Software engineer & SELinux technology evangelist at Red Hat. He is part of Security Controls team working on SELinux projects focusing especially on security policies. Lukas is author of udica, the tool for generating custom SELinux profiles for containers and currently maintains the selinux-policy packages for Fedora and Red Hat Enterprise Linux distributions.