Introduction
AppArmor is a Linux mandatory access control (MAC) system that confines programs to a limited set of resources — file access, network capabilities, and system calls. Ansible automates AppArmor profile management: deploy custom profiles, switch between enforce/complain modes, audit application behavior, and harden services across your fleet.
Install and Enable AppArmor
---
- name: Configure AppArmor
hosts: all
become: true
tasks:
- name: Install AppArmor
ansible.builtin.package:
name:
- apparmor
- apparmor-utils
- apparmor-profiles
- apparmor-profiles-extra
state: present
when: ansible_os_family == 'Debian'
- name: Enable AppArmor
ansible.builtin.service:
name: apparmor
state: started
enabled: true
- name: Check AppArmor status
ansible.builtin.command: aa-status
register: aa_status
changed_when: false
- name: Display AppArmor status
ansible.builtin.debug:
msg: "{{ aa_status.stdout_lines[:10] }}"
Deploy Custom Profiles
- name: Deploy custom AppArmor profile
ansible.builtin.copy:
dest: /etc/apparmor.d/usr.local.bin.myapp
content: |
# AppArmor profile for myapp — managed by Ansible
#include <tunables/global>
/usr/local/bin/myapp {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
# Binary
/usr/local/bin/myapp mr,
# Config files (read only)
/etc/myapp/ r,
/etc/myapp/** r,
# Data directory (read/write)
/var/lib/myapp/ rw,
/var/lib/myapp/** rw,
# Logs
/var/log/myapp/ rw,
/var/log/myapp/** rw,
# Temp files
/tmp/myapp-* rw,
# Network
network inet stream,
network inet dgram,
# Deny everything else
deny /home/** rw,
deny /root/** rw,
deny /etc/shadow r,
deny /etc/passwd w,
}
mode: '0644'
notify: reload apparmor
- name: Load profile in enforce mode
ansible.builtin.command: aa-enforce /etc/apparmor.d/usr.local.bin.myapp
changed_when: true
Manage Profile Modes
# Set specific profiles to enforce mode
- name: Enforce critical profiles
ansible.builtin.command: "aa-enforce {{ item }}"
loop:
- /usr/sbin/sshd
- /usr/sbin/nginx
- /usr/local/bin/myapp
changed_when: true
# Set profiles to complain mode (log but don't block)
- name: Set profile to complain mode for testing
ansible.builtin.command: "aa-complain {{ item }}"
loop:
- /usr/local/bin/newapp
changed_when: true
# Disable a profile
- name: Disable profile
ansible.builtin.command: "aa-disable {{ item }}"
loop: "{{ apparmor_disabled_profiles | default([]) }}"
changed_when: true
Nginx AppArmor Profile
- name: Deploy Nginx AppArmor profile
ansible.builtin.copy:
dest: /etc/apparmor.d/usr.sbin.nginx
content: |
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/ssl_certs>
capability net_bind_service,
capability setuid,
capability setgid,
capability dac_override,
/usr/sbin/nginx mr,
/etc/nginx/ r,
/etc/nginx/** r,
/etc/ssl/ r,
/etc/ssl/** r,
/var/www/ r,
/var/www/** r,
/var/log/nginx/ rw,
/var/log/nginx/** rw,
/run/nginx.pid rw,
/var/lib/nginx/** rw,
network inet stream,
network inet6 stream,
}
mode: '0644'
notify: reload apparmor
Docker Container Profiles
- name: Deploy Docker container AppArmor profile
ansible.builtin.copy:
dest: /etc/apparmor.d/docker-mycontainer
content: |
#include <tunables/global>
profile docker-mycontainer flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
capability,
file,
deny mount,
deny /sys/firmware/** r,
deny /proc/kcore r,
/app/** r,
/app/data/** rw,
/tmp/** rw,
}
mode: '0644'
notify: reload apparmor
# Use with: docker run --security-opt apparmor=docker-mycontainer
Audit and Troubleshoot
- name: Check for AppArmor denials
ansible.builtin.command: "aa-logprof"
register: logprof
changed_when: false
failed_when: false
- name: Search for denials in logs
ansible.builtin.shell: "dmesg | grep -i apparmor | grep DENIED | tail -20"
register: denials
changed_when: false
- name: Display denials
ansible.builtin.debug:
var: denials.stdout_lines
when: denials.stdout | length > 0
- name: Generate profile from logs
ansible.builtin.command: "aa-logprof -f /var/log/syslog"
register: profile_suggestions
changed_when: false
failed_when: false
Fleet Status Report
- name: Get AppArmor status across fleet
ansible.builtin.command: aa-status --json
register: aa_json
changed_when: false
- name: Report
ansible.builtin.debug:
msg: |
Host: {{ inventory_hostname }}
Enforce: {{ (aa_json.stdout | from_json).profiles | dict2items | selectattr('value', 'eq', 'enforce') | list | length }}
Complain: {{ (aa_json.stdout | from_json).profiles | dict2items | selectattr('value', 'eq', 'complain') | list | length }}
Handlers
handlers:
- name: reload apparmor
ansible.builtin.service:
name: apparmor
state: reloaded
Troubleshooting
Profile Not Loading
- name: Check profile syntax
ansible.builtin.command: apparmor_parser -Q /etc/apparmor.d/usr.local.bin.myapp
register: parse_check
changed_when: false
Application Blocked
Start in complain mode, run the app, then review logs:
- name: Switch to complain mode for debugging
ansible.builtin.command: "aa-complain /etc/apparmor.d/{{ profile_name }}"
changed_when: true
Related Articles
Conclusion
AppArmor confines applications to only the resources they need — Ansible deploys and manages profiles at scale. Use complain mode for profiling new applications, enforce mode for production, and aa-logprof to iteratively refine profiles from real access logs. Deploy profiles for Nginx, Docker containers, and custom applications. Security hardening as code, applied consistently across every server.