Docker & SELinux: 30.000 foot view

In a precedent 30.000-foot view series, we saw how AppArmor is used with Docker so container can be confined by default in an AppArmor profile.

SELinux is another Linux security option. As AppArmor its implementation is based on LSM (Linux Security Modules), a kernel architecture that enables to develop MAC (Mandatory Access Control) modules. MAC adds an additional level of security to the DAC (Discretionary Access Control, which is represented by the rwx flags on each file of the system). Please note that DAC must pass before MAC is evaluated.

To make it short, SELinux is based on labels, which are given to processes / files / resources of the system. This label is a security context of the element it’s applied to. It is displayed using the -Z option of ls (-Z option can also be used on several other command such as ps, …)

$ ls -l /usr/bin/ssh
-rwxr-xr-x. 1 root root 669728 Mar 21 22:18 /usr/bin/ssh

$ ls -Z /usr/bin/ssh
-rwxr-xr-x. root root system_u:object_r:ssh_exec_t:s0 /usr/bin/ssh

SELinux has subjects (user running a command, application, process) and objects (file, folder, device, interface, …) and a set of rules that defines to which objects a subject has access to. By default access is denied unless the rules that authorize the access is explicitly defined.

To learn more about SELinux, take a look at this nice guide. If you have time you can even download the coloring book and learn as you color (I heard this was great to relax :) ).

We’ll see on an example how this is used in the context of a Docker container. Let’s first create a Docker host running CentOS 7.3 on a DigitalOcean droplet.

Note: we’ll create the droplet than install Docker instead of using Docker Machine as I had issue with Docker’s SELinux policy using Machine.

We are using a CentOS Linux distribution as SELinux is installed by default. We could have created a Ubuntu machine and installed SELinux but because Ubuntu comes with AppArmor by default, it’s generally not a good idea to have AppArmor and SELinux (2 LSM modules) at the same time.

Enable SELinux on the host

Let’s ssh on that machine and check the current status of SELinux

[root@node ~]# sestatus
SELinux status: disabled

The following command indicates the current status is disabled

Modify /etc/selinux/config file so it looks like the following (SELINUX is set to enforcing instead of the default disabled)

We then reboot the Docker host to take into account those modification (reboot is necessary as the changes we’ve done have consequent security impact on the whole system).

[root@node ~]# reboot

Once the host is up again, let’s check the status of SELinux

Install Docker

Update package list and install Docker

[root@node ~]# curl -fsSL https://get.docker.com/ | sh

Enable Docker selinux policy

[root@node ~]# semodule -v -e docker
Attempting to enable module 'docker':
Ok: return value of 0.
Committing changes:
Ok: transaction number 0.

Start Docker daemon

As CentOS uses systemd, start the Docker daemon

[root@node ~]# systemctl start docker

If not specified, docker daemon will not start with SELinux enabled.

[root@node ~]# docker info | grep ‘Security Options’
WARNING: Usage of loopback devices is strongly discouraged for production use. Use ` — storage-opt dm.thinpooldev` to specify a custom block storage device.
Security Options: seccomp

Let’s change that.

Run docker daemon with SELinux enabled

In order to do so, we modify the options of the docker daemon adding the following flag to the daemon start options.

--selinux-enabled 

Modify the docker.service systemd’s Unit file (/usr/lib/systemd/system/docker.service) so it looks like the following

Restart Docker daemon so it takes into account the configuration change.

[root@node ~]# systemctl stop docker
[root@node ~]# systemctl start docker

Docker daemon is now using selinux.

[root@node ~]# docker info | grep ‘Security Options’
WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
Security Options: seccomp selinux

Note: seccomp is also present in the security options used. We’ll see that one in another article of “30.000 foot view” series.

For now on, we need to understand what “docker running with SELinux” means.

The Docker engine SELinux policy is defined in the following file. Basically, it specifies the domain in which docker daemon is running (docker_exec_t) and defines the context of each Docker related files / resources (/var/run/docker, /var/lib/docker, …).

Note: there is currently an issue related to Docker and SELinux on CentOS 7.3 (https://github.com/docker/docker/issues/30097) as it appears the docker binaries do not have the correct SELinux type:

As defined in the issue, a quick workaround is to change that with the following command:

[root@node ~]# chcon -t docker_exec_t /usr/bin/docker*

This changes the context so that the docker binaries (located in /usr/bin/) have the docker_exec_t type.

Note: thanks to Dan J. Walsh (lead SELinux developper at Red Hat) for pointing out that the docker package that comes with the distribution can also be used. At the time of writing it’s in version 1.10.3 but the docker’s binary have a SELinux context’s type correctly set.

Nginx’s SELinux context

Let’s install Nginx on the host and check the SELinux attributes for the default files located in /usr/share/nginx/html.

$ yum install epel-release
$ yum install nginx
$ ls -Z /usr/share/nginx/html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 404.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 50x.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 nginx-logo.png
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 poweredby.png

As Nginx’s files within /usr/share/nginx/html have the system_u:object_r:httpd_sys_content_t:s0 SElinux security context, Docker container (with security context system_u:system_r:docker_exec_t:s0) should not be able to modify them. Let’s try to show that.

Modify Nginx files from a container with SELinux enabled

We’ll create an Alpine container with default SELinux profile and bind mount the host’s root filesystem into the container’s /host folder.

# Map filesystem of the host inside the container
# SELinux default profil used
$ docker run -ti -v /:/host alpine sh

From within this container, we try to modify the index.html file.

sh-4.3# echo "test" > /host/usr/share/nginx/html/index.html
sh: can't create /host/usr/share/nginx/html/index.html: Permission denied

As we thought, SELinux prevents us from modifying the Nginx’s file. Let’s now run a new container with the security option disabled.

Modify Nginx files from a container with SELinux disabled

As we’ve done above, we’ll create an Alpine container and bind mount the host’s root filesystem into the container’s /host folder, but this time we will disable the security option.

# Map filesystem of the host inside the container
# SELinux labels are disabled
$ docker run -ti -v /:/host --security-opt label:disable alpine sh
/ # echo "test" > /host/usr/share/nginx/html/index.html
/ #

There is no error, we can modify the index.html file as we have bypassed SELinux security policy.

Conclusion

This article is a very high level view of SELinux (from 30.000 foot, right ?) and I hope this can help to demystify and clarify the thing a little bit.

When running Docker containers, do you use SELinux or AppArmor ? I’d really like to have your feedback on this.