Day 5 — Getting started with AppArmor


The main behind a tool like AppArmor is to reduce the application attack surface. It creates one more layer of security between our application and system functionality. Let say our use case is we want our container not to write inside the specific filesystem or directory. This is where we can use tools like AppArmor. AppArmor implements fine-grained control over the processes running inside the container. It’s a Linux Security Module that confines your program to a limited set of resources.


AppArmor is installed by default in most Linux distributions. In the case of Ubuntu, you can verify it using

# apt list --installed |grep -i AppArmor


  • Install it using the below command
sudo apt install apparmor-profiles
  • To check if the AppArmor service is up and running.
# systemctl status apparmor
● apparmor.service - AppArmor initialization
Loaded: loaded (/lib/systemd/system/apparmor.service; enabled; vendor preset: enabled)
Active: active (exited) since Sun 2021-10-31 00:46:35 UTC; 18min ago
Docs: man:apparmor(7)
Main PID: 482 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 2314)
CGroup: /system.slice/apparmor.service
  • The next step is to verify if the AppArmor module is enabled in all the nodes in the Kubernetes cluster. The value of Y means the AppArmor module is already loaded.
# cat /sys/module/apparmor/parameters/enabled 
  • To apply AppArmor to your application, it’s applied as a profile. This profile must be loaded inside the kernel. As we can see, several profiles are already loaded in this host. These profiles are simple text files and define which an application can use resources.
# cat /sys/kernel/security/apparmor/profiles 
/usr/{sbin/traceroute,bin/traceroute.db} (complain)
/usr/sbin/smbldap-useradd (complain)
/usr/sbin/smbldap-useradd///etc/init.d/nscd (complain)
/usr/sbin/smbd (complain)
/usr/sbin/nscd (complain)
/usr/sbin/nmbd (complain)
/usr/sbin/mdnsd (complain)
/usr/sbin/identd (complain)
/usr/sbin/dovecot (complain)
/usr/sbin/dnsmasq (complain)
/usr/sbin/dnsmasq//libvirt_leaseshelper (complain)
/usr/sbin/avahi-daemon (complain)
/usr/lib/dovecot/ssl-params (complain)
/usr/lib/dovecot/pop3-login (complain)
/usr/lib/dovecot/pop3 (complain)
/usr/lib/dovecot/managesieve-login (complain)
/usr/lib/dovecot/managesieve (complain)
/usr/lib/dovecot/log (complain)
/usr/lib/dovecot/lmtp (complain)
/usr/lib/dovecot/imap-login (complain)
/usr/lib/dovecot/imap (complain)
/usr/lib/dovecot/dovecot-lda (complain)
/usr/lib/dovecot/dovecot-lda///usr/sbin/sendmail (complain)
/usr/lib/dovecot/dovecot-auth (complain)
/usr/lib/dovecot/dict (complain)
/usr/lib/dovecot/deliver (complain)
/usr/lib/dovecot/config (complain)
/usr/lib/dovecot/auth (complain)
/usr/lib/dovecot/anvil (complain)
/usr/lib/chromium-browser/chromium-browser (complain)
/usr/lib/chromium-browser/chromium-browser//xdgsettings (complain)
/usr/lib/chromium-browser/chromium-browser//sanitized_helper (enforce)
/usr/lib/chromium-browser/chromium-browser//lsb_release (complain)
/usr/lib/chromium-browser/chromium-browser//chromium_browser_sandbox (complain)
/usr/lib/chromium-browser/chromium-browser//browser_openjdk (enforce)
/usr/lib/chromium-browser/chromium-browser//browser_java (enforce)
syslogd (complain)
syslog-ng (complain)
klogd (complain)
ping (complain)
docker-default (enforce)
/usr/sbin/tcpdump (enforce)
/usr/lib/snapd/snap-confine (enforce)
/usr/lib/snapd/snap-confine//mount-namespace-capture-helper (enforce)
man_groff (enforce)
man_filter (enforce)
/usr/bin/man (enforce)
/usr/bin/lxc-start (enforce)
/usr/lib/connman/scripts/dhclient-script (enforce)
/usr/lib/NetworkManager/nm-dhcp-helper (enforce)
/usr/lib/NetworkManager/nm-dhcp-client.action (enforce)
/sbin/dhclient (enforce)
lxc-container-default-with-nesting (enforce)
lxc-container-default-with-mounting (enforce)
lxc-container-default-cgns (enforce)
lxc-container-default (enforce)
  • The simple Apparmor profile will look like this. What this profile is doing it’s denying all write access to the root filesystem.
#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>


# Deny all file writes.
deny /** w,


  • To check which are the profile is loaded, you can use the aa-status command. As you can see, 56 profiles are already loaded, and 19 are loaded in enforce mode.
# aa-status 
apparmor module is loaded.
56 profiles are loaded.
19 profiles are in enforce mode.
37 profiles are in complain mode.
14 processes have profiles defined.
14 processes are in enforce mode.
docker-default (2300)
docker-default (2365)
docker-default (2371)
docker-default (2410)
docker-default (2497)
docker-default (2562)
docker-default (2653)
docker-default (2685)
docker-default (3412)
docker-default (3419)
docker-default (4481)
docker-default (4500)
docker-default (4687)
docker-default (4754)
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.
  • These profiles can be loaded in three different modes
* enforce: AppArmor monitors and enforce the profile rules 
* complain: AppArmor will not enforce any profile rules but log the events
* unconfined: Nothing is enforced and it doesn't even log the events
  • To use AppArmor with pods. go to and create the file default deny file
cd /etc/apparmor.d
# cat default.deny
#include <tunables/global>
  • To use AppArmor with Pod, you need to use the annotations whose format is key and value
  • Remember with annotations, you need to specify the container name, not the pod name

NOTE: Container runtime must need to support AppArmor, and profile/AppArmor must be present on each node.

apiVersion: v1
kind: Pod
name: hello-apparmor
annotations: localhost/k8s-apparmor-example-deny-write
- name: hello
image: busybox
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]


  • To load the profile
apparmor_parser -v default.deny 
Addition succeeded for "k8s-apparmor-example-deny-write".
  • Also, not the file name and the profile name is different. With annotation specify the profile name(k8s-apparmor-example-deny-write) not the file name(default.deny).
  • Create the pod
kubectl create -f /tmp/pod.yaml 
pod/hello-apparmor created
  • Verify the profile is loaded properly
# kubectl describe pod hello-apparmor |grep -i apparmor
Name: hello-apparmor
Annotations: localhost/k8s-apparmor-example-deny-write
  • Test it. As you can see the file creation inside the / is denied
# kubectl exec -it hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
command terminated with exit code 1


AppArmor adds one more layer of security between our application and system functionality. But getting a written profile is entirely depends upon your application and requires lots of work.

AWS Community Builder, Ex-Redhat, Author, Blogger, YouTuber, RHCA, RHCDS, RHCE, Docker Certified,4XAWS, CCNA, MCP, Certified Jenkins, Terraform Certified, 1XGCP