Using AppArmor to secure Docker containers

AppArmor is a Mandatory Access Control (MAC) system that is a kernel enhancement to confine programs to a limited set of resources. AppArmor's security model is to bind access control attributes to programs rather than to users.

AppArmor confinement is provided via profiles loaded into the kernel, typically on boot. AppArmor profiles can be in one of two modes: enforcement or complain.

Profiles loaded in enforcement mode will result in enforcement of the policy defined in the profile, as well as reporting policy violation attempts (either via syslog or auditd).

Profiles in complain mode will not enforce policy but instead report policy violation attempts.

AppArmor differs from some other MAC systems on Linux: it is path-based, it allows mixing of enforcement and complain-mode profiles, it uses include files to ease development, and it has a far lower barrier to entry than other popular MAC systems. The following figure shows the AppArmour application profiles linked to apps:

Using AppArmor to secure Docker containers

AppArmor is an established technology first seen in Immunix and later integrated into Ubuntu, Novell/SUSE, and Mandriva. Core AppArmor functionality is in the mainline Linux kernel from 2.6.36 onwards; work is ongoing by AppArmor, Ubuntu, and other developers to merge additional AppArmor functionality into the mainline kernel.

You can find more information about AppArmor at https://wiki.ubuntu.com/AppArmor.

AppArmor and Docker

Applications running inside Docker can leverage AppArmor for defining policies. These profiles can either be created manually or loaded using a tool called bane.

Note

On Ubuntu 14.x, make sure systemd is installed for the following commands to work.

The following steps show how to use this tool:

  1. Download the bane project for GitHub:
    $ git clone https://github.com/jfrazelle/bane
    

    Make sure this is done in the directory in your GOPATH. For example, we used /home/ubuntu/go and the bane source was downloaded in /home/Ubuntu/go/src/github.com/jfrazelle/bane.

  2. Install toml parser needed by bane to be compiled:
    $ go get github.com/BurntSushi/toml
    
  3. Go to the /home/Ubuntu/go/src/github.com/jfrazelle/bane directory and run the following command:
    $ go install
    
  4. You will find the bane binary in /home/Ubuntu/go/bin.
  5. Use a .toml file to create a profile:
    Name = "nginx-sample"
    [Filesystem]
    # read only paths for the container
    ReadOnlyPaths = [
      "/bin/**",
      "/boot/**",
      "/dev/**",
      "/etc/**",
    
    ]
    AllowExec = [
      "/usr/sbin/nginx"
    ]
    # denied executable files
    DenyExec = [
      "/bin/dash",
      "/bin/sh",
      "/usr/bin/top"
    ]
    
  6. Execute bane to load the profile. sample.toml is a file in the directory /home/Ubuntu/go/src/github.com/jfrazelle/bane:
    $ sudo bane sample.toml
    # Profile installed successfully you can now run the profile with # `docker run --security-opt="apparmor:docker-nginx-sample"`
    

    This profile will make a whole lot of paths read only and allows only nginx execution in the container we are going to create. It disables TOP, PING, and so on.

  7. Once the profile is loaded you can create a nginx container:
    $ docker run --security-opt="apparmor:docker-nginx-sample" -p 80:80 --rm -it nginx bash
    

    Note, if AppArmor is not able to find the file, copy the file into the /etc/apparmor.d directory and reload the AppArmour profiles:

    $ sudo invoke-rc.d apparmor reload
    

    Create the nginx container with the AppArmor profile:

    ubuntu@ubuntu:~/go/src/github.com$ docker run --security-opt="apparmor:docker-nginx-sample" -p 80:80 --rm -it nginx bash
    root@84d617972e04:/# ping 8.8.8.8
    ping: Lacking privilege for raw socket.
    

The following figure shows how an nginx app running inside a container uses AppArmour application profiles:

AppArmor and Docker

Docker security benchmark

The following tutorial shows some of the important guidelines that should be followed in order to run Docker containers in secured and production environments. It is referred from the CIS Docker Security Benchmark https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf.

Audit Docker daemon regularly

Apart from auditing your regular Linux filesystem and system calls, audit Docker daemon as well. Docker daemon runs with root privileges. It is thus necessary to audit its activities and usage:

$ apt-get install auditd
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
  libauparse0
Suggested packages:
  audispd-plugins
The following NEW packages will be installed:
  auditd libauparse0
0 upgraded, 2 newly installed, 0 to remove and 50 not upgraded.
Processing triggers for libc-bin (2.21-0ubuntu4) ...
Processing triggers for ureadahead (0.100.0-19) ...
Processing triggers for systemd (225-1ubuntu9) ...

Remove the audit log file, if it exists:

$ cd /etc/audit/
$ ls
audit.log
$ nano audit.log
$ rm -rf audit.log

Add the audit rules for the Docker service and audit the Docker service:

$ nano audit.rules
-w /usr/bin/docker -k docker
$ service auditd restart
$ ausearch -k docker
<no matches>
$ docker ps
CONTAINER ID    IMAGE      COMMAND    CREATED    STATUS   PORTS     NAMES
$ ausearch -k docker
----
time->Fri Nov 27 02:29:50 2015
type=PROCTITLE msg=audit(1448620190.716:79): proctitle=646F636B6572007073
type=PATH msg=audit(1448620190.716:79): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=398512 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1448620190.716:79): item=0 name="/usr/bin/docker" inode=941134 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=CWD msg=audit(1448620190.716:79):  cwd="/etc/audit"
type=EXECVE msg=audit(1448620190.716:79): argc=2 a0="docker" a1="ps"
type=SYSCALL msg=audit(1448620190.716:79): arch=c000003e syscall=59 success=yes exit=0 a0=ca1208 a1=c958c8 a2=c8

Create a user for the container

Currently, mapping the container's root user to a non-root user on the host is not supported by Docker. The support for user namespace would be provided in future releases. This creates a serious user isolation issue. It is thus highly recommended to ensure that there is a non-root user created for the container and the container is run using that user.

As we can see in the following snippet, by default, the centos Docker image has a user field as blank, which means, by default, the container will get a root user during runtime, which should be avoided:

$ docker inspect centos
[
  {
    "Id": "e9fa5d3a0d0e19519e66af2dd8ad6903a7288de0e995b6eafbcb38aebf2b606d",
    "RepoTags": [
      "centos:latest"
    ],
    "RepoDigests": [],
    "Parent": "c9853740aa059d078b868c4a91a069a0975fb2652e94cc1e237ef9b961afa572",
    "Comment": "",
    "Created": "2015-10-13T23:29:04.138328589Z",
    "Container": "eaa200e2e187340f0707085b9b4eab5658b13fd190af68c71a60f6283578172f",
    "ContainerConfig": {
      "Hostname": "7aa5783a47d5",
      "Domainname": "",
      "User": "",
      contd

While building the Docker image, we can provide the test user, the less-privileged user, in the Dockerfile, as shown in the following snippet:

$ cd
$ mkdir test-container
$ cd test-container/
$ cat Dockerfile
FROM centos:latest
RUN useradd test
USER test
root@ubuntu:~/test-container# docker build -t vkohli .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM centos:latest
 ---> e9fa5d3a0d0e
Step 2 : RUN useradd test
 ---> Running in 0c726d186658
 ---> 12041ebdfd3f
Removing intermediate container 0c726d186658
Step 3 : USER test
 ---> Running in 86c5e0599c72
 ---> af4ba8a0fec5
Removing intermediate container 86c5e0599c72
Successfully built af4ba8a0fec5
$ docker images | grep vkohli
vkohli    latest     af4ba8a0fec5      9 seconds ago     172.6 MB

When we start the Docker container, we can see that it gets a test user, and the docker inspect command also shows the default user as test:

$ docker run -it vkohli /bin/bash
[test@2ff11ee54c5f /]$ whoami
test
[test@2ff11ee54c5f /]$ exit
$ docker inspect vkohli
[
  {
    "Id": "af4ba8a0fec558d68b4873e2a1a6d8a5ca05797e0bfbab0772bcedced15683ea",
    "RepoTags": [
        "vkohli:latest"
    ],
    "RepoDigests": [],
    "Parent": "12041ebdfd3f38df3397a8961f82c225bddc56588e348761d3e252eec868d129",
    "Comment": "",
    "Created": "2015-11-27T14:10:49.206969614Z",
    "Container": "86c5e0599c72285983f3c5511fdec940f70cde171f1bfb53fab08854fe6d7b12",
    "ContainerConfig": {
      "Hostname": "7aa5783a47d5",
      "Domainname": "",
      "User": "test",
      Contd..

Do not mount sensitive host system directories on containers

If sensitive directories are mounted in read-write mode, it would be possible to make changes to files within those sensitive directories. The changes might bring down security implications or unwarranted changes that could put the Docker host in a compromised state.

If the /run/systemd sensitive directory is mounted in the container then we can actually shutdown the host from the container itself:

$ docker run -ti -v /run/systemd:/run/systemd centos /bin/bash
[root@1aca7fe47882 /]# systemctl status docker
docker.service - Docker Application Container Engine
  Loaded: loaded (/lib/systemd/system/docker.service; enabled)
  Active: active (running) since Sun 2015-11-29 12:22:50 UTC; 21min ago
  Docs: https://docs.docker.com
 Main PID: 758
  CGroup: /system.slice/docker.service
[root@1aca7fe47882 /]# shutdown

It can be audited by using the following command, which returns the list of current mapped directories and whether they are mounted in read-write mode for each container instance:

$ docker ps -q | xargs docker inspect --format '{{ .Id }}: Volumes={{ .Volumes }} VolumesRW={{ .VolumesRW }}'

Do not use privileged containers

Docker supports the addition and removal of capabilities, allowing the use of a non-default profile. This may make Docker more secure through capability removal, or less secure through the addition of capabilities. It is thus recommended to remove all capabilities except those explicitly required for your container process.

As seen in the following, when we run the container without the privileged mode, we are unable to change the kernel parameters, but when we run the container in privileged mode using the --privileged flag, it is possible to change the kernel parameters easily, which can cause security vulnerability:

$ docker run -it centos /bin/bash
[root@7e1b1fa4fb89 /]#  sysctl -w net.ipv4.ip_forward=0
sysctl: setting key "net.ipv4.ip_forward": Read-only file system
$ docker run --privileged -it centos /bin/bash
[root@930aaa93b4e4 /]#  sysctl -a | wc -l
sysctl: reading key "net.ipv6.conf.all.stable_secret"
sysctl: reading key "net.ipv6.conf.default.stable_secret"
sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
sysctl: reading key "net.ipv6.conf.lo.stable_secret"
638
[root@930aaa93b4e4 /]# sysctl -w net.ipv4.ip_forward=0
net.ipv4.ip_forward = 0

So, while auditing, it should be made sure that all the containers should not have the privileged mode set to true:

$ docker ps -q | xargs docker inspect --format '{{ .Id }}: Privileged={{ .HostConfig.Privileged }}'
930aaa93b4e44c0f647b53b3e934ce162fbd9ef1fd4ec82b826f55357f6fdf3a: Privileged=true
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset