AppArmor

It is well known that in cloud-native environments, we can control application access to resources in the cluster through RBAC mechanisms, but these are not enough for production environments, where the host is still a security risk when applications have access to host resources (e.g. Linux privilege words, network access, file permissions). For this situation, the Linux kernel security module AppArmor supplements standard Linux user and group-based permissions to limit the application to a limited set of resources and also serves as a protection for the Pod from unwanted attacks.

On a system with AppArmor enabled, the container runs with the default permission configuration for the container, but of course, the application can use a custom configuration as well. This article describes how to use AppArmor in a container.

How to Use AppArmor

AppArmor is a Linux kernel security module that allows system administrators to use per-program configuration files to restrict the functionality of programs. The configuration file can allow network access, raw socket access, and permission to read, write, or execute files on matching paths, among other features.

However, not all systems support AppArmor. by default, several distributions support the module, such as Ubuntu and SUSE, and many more offer optional support. You can check if the module is AppArmor-enabled by using the following command.

1
2
$ cat /sys/module/apparmor/parameters/enabled
Y

AppArmor operates in the following two types of profile modes.

  • enforce: In enforce mode, the system starts enforcing the rule and reports the violation attempt in syslog or auditd (only if auditd is installed) and disallows the action.
  • complain: In complain mode, the system does not enforce any rules. It only logs the offending attempt.

Configuration files are text files located in the /etc/apparmor.d/ directory. These files are named after the full path of the executable they are parsing, but replace / with .. For example, if the tcpdump command is located in /usr/sbin/tcpdump, the equivalent AppArmor configuration file would be named usr.sbin.tcpdump.

You can also set up your own profile, such as the sample profile to restrict write access to all files to.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ cat <<EOF >/etc/apparmor.d/containers/sample
#include <tunables/global>

profile sample flags=(attach_disconnected) {
  #include <abstractions/base>
  file,
  # Deny all file writes.
  deny /** w,
}
EOF

Make the above configuration take effect.

1
2
3
4
5
6
7
8
$ apparmor_parser /etc/apparmor.d/containers/sample
$ apparmor_status
apparmor module is loaded.
35 profiles are loaded.
35 profiles are in enforce mode.
   ...
   sample
   ...

AppArmor supports three main rules for Capability, File, and Network.

  • Capability: Capability for Linux processes, where no Capability is granted, only a valid mask for the process capability set. e.g., capability sys_admin, means that system administration tasks are allowed.
  • File:
    • Read, write, execute, etc. permissions for files. For example, /home/** rw, means read/write access to all files under /home.
    • File system mount rules, including whether to have mount and unmount permissions, file system type, mount parameters, and mount path. For example, mount options=ro /dev/foo, which allows read-only mounts to the /dev/foo path.
    • rules such as chmod, chown, setuid, etc.
  • Network:
    • Permissions for network sockets, including create, accept, bind, etc., and the type and address of the network, e.g., network tcp, indicating support for all tcp-type network operations.
    • Rules for DBUS, IPC, Signals, etc.

AppArmor’s configuration files are defined in a very flexible manner, and more specific use can be found in the AppArmor documentation.

Using AppArmor in Containers

After configuring the AppArmor configuration file on the host, let’s look at how to use it in a container.

Docker as the engine

When the container engine is Docker, for comparison, first run a normal nginx container and create a test file.

1
2
3
4
5
$ docker run --rm -it nginx /bin/bash
root@45bf95280766:/# cd
root@45bf95280766:~# touch test
root@45bf95280766:~# ls
test

Next, run a container with the AppArmor configuration file sample that uses the above restrictions on write access to all files, and create a test file.

1
2
3
4
$ docker run --rm -it --security-opt "apparmor=sample" nginx /bin/bash
root@1d1d8f6b1aa0:/# cd ~
root@1d1d8f6b1aa0:~# touch test
touch: cannot touch 'test': Permission denied

We can see that the AppArmor configuration file prevents the create file operation.

Engine is Containerd

When the container engine is Containerd, do the same test.

1
2
3
4
5
6
$ nerdctl run --rm -it docker.io/library/nginx:latest /bin/bash
root@09e6c02616a7:/# cd ~
root@09e6c02616a7:~# touch test
root@09e6c02616a7:~#
root@09e6c02616a7:~# ls
test

Use the sample configuration file in the container.

1
2
3
4
$ nerdctl run --rm -it --security-opt "apparmor=sample" docker.io/library/nginx:latest /bin/bash
root@8be22275bc9d:/# cd
root@8be22275bc9d:~# touch test
touch: cannot touch 'test': Permission denied

Similarly, the AppArmor configuration file prevents file creation operations.

Using AppArmor in Kubernetes

How do you use it in Kubernetes? The key is container.apparmor.security.beta.kubernetes.io/<container_name> and the value has 3 different values.

  • runtime/default: use the default container runtime configuration (e.g. docker-default).
  • localhost/<profile_name>: uses the configuration file in effect on the node, with <profile_name> as the filename.
  • unconfined: does not use any AppArmor configuration files.

Take the configuration file sample created above as an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
  name: test
  annotations:
    container.apparmor.security.beta.kubernetes.io/app: localhost/sample
spec:
  containers:
  - args:
    - -c
    - sleep 1000000
    command:
    - /bin/sh
    image: ubuntu
    name: app

Also test if the pod has permission to create files.

1
2
3
4
$ kubectl exec -it test -- bash
root@test:/# cd
root@test:~# touch test
touch: cannot touch 'test': Permission denied

Summary

In systems with AppArmor enabled, it is essential to use AppArmor to protect nodes and pods, but AppArmor configuration can be tricky. However, there are mature solutions in the community, such as the tool bane for quickly generating AppArmor configuration files, and DaemonSet for configuring the same configuration file for each node, see the example. You can also use node initialization scripts (e.g. Salt, Ansible, etc.) or images. You can also copy the configuration files to each node and load them via SSH, see the example.