The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Check module availability | aa-enabled | View examples |
| Inspect loaded policy | sudo aa-status | View examples |
| Inspect one task label | pid=$(systemctl show reportd.service -p MainPID \
--value); sudo cat "/proc/$pid/attr/current" | View examples |
| Find recent denials | sudo journalctl -k --grep='apparmor="DENIED"' \
--since='15 minutes ago' | View examples |
| Query Audit records | sudo ausearch -m AVC,USER_AVC -ts recent |
grep 'apparmor="DENIED"' | View examples |
| Include base abstraction | #include <abstractions/base> | View examples |
| Include local overrides | #include if exists <local/usr.local.bin.reportd> | View examples |
| Allow recursive reads | /srv/reporting/** r, | View examples |
| Allow directory traversal | /srv/reporting/ r, | View examples |
| Limit writes to owned files | owner /var/lib/reportd/** rwk, | View examples |
| Execute without transition | /usr/bin/logger ix, | View examples |
| Allow TCP networking | network inet stream, | View examples |
| Validate without loading | apparmor_parser -Q /etc/apparmor.d/usr.local.bin.reportd | View examples |
| Expand includes | apparmor_parser -p /etc/apparmor.d/usr.local.bin.reportd | View examples |
| Replace one loaded profile | sudo apparmor_parser -r \
/etc/apparmor.d/usr.local.bin.reportd | View examples |
| Load complain mode temporarily | sudo apparmor_parser -C -r \
/etc/apparmor.d/usr.local.bin.reportd | View examples |
| Persist complain mode | sudo aa-complain /usr/local/bin/reportd | View examples |
| Restore enforce mode | sudo aa-enforce /usr/local/bin/reportd | View examples |
| Review logged behavior | sudo aa-logprof | View examples |
| Disable one profile | sudo aa-disable /usr/local/bin/reportd | View examples |
AppArmor confines programs with named, path-oriented profiles in addition to normal Unix permissions. A safe workflow starts by proving which profile and executable attachment are active, capturing a representative denial, and translating only the intended behavior into narrow rules. Validate profiles without loading them, keep site changes in local includes when packaged profiles support them, stage learning deliberately, and restore enforce mode before calling a deployment complete.
Step by step
Detailed examples
Prove that AppArmor and the expected attachment are active
A loaded module, a loaded profile, and a confined process are different facts. aa-enabled checks kernel availability; aa-status summarizes loaded policy and task modes. For a specific verified PID, /proc/PID/attr/current exposes its label and whether it is enforcing or complaining. Confirm the executable path as well: path changes, symlinks, wrappers, containers, and service managers can produce a different attachment than the profile filename suggests.
aa-enabled
sudo aa-status
pid=$(systemctl show reportd.service -p MainPID --value)
sudo cat "/proc/$pid/attr/current"
systemctl show reportd.service -p ExecStart Read the denied operation, profile, target, and requested mask together
Reproduce one known failing action and constrain log queries to its time window. An AppArmor event identifies the profile, operation, target name, requested and denied masks, process, and sometimes network fields. Verify that the action is a legitimate application requirement before changing policy. An explicit deny rule may intentionally suppress logging, and missing AppArmor events can mean the failure came from DAC, seccomp, capabilities, a read-only mount, or another control layer.
sudo journalctl -k --grep='apparmor="DENIED"' --since='15 minutes ago' --no-pager
sudo ausearch -m AVC,USER_AVC -ts recent | grep 'apparmor="DENIED"' Anchor the profile to an executable and a declared feature ABI
Profiles normally live under /etc/apparmor.d and use filenames derived from executable paths, but the profile header controls attachment. Include tunables/global and an explicit ABI where supported by the distribution, then import only suitable abstractions. Abstractions are maintained rule sets, not harmless macros: inspect their expansion and security effect. Flags such as attach_disconnected or chroot_relative change path mediation semantics and should not be copied into a profile without a demonstrated need.
abi <abi/4.0>,
#include <tunables/global>
/usr/local/bin/reportd {
#include <abstractions/base>
#include <abstractions/nameservice>
#include if exists <local/usr.local.bin.reportd>
/usr/local/bin/reportd mr,
/etc/reportd/config.yaml r,
} Express path access precisely and account for directory mediation
A trailing slash describes a directory, while * does not cross slash boundaries and ** does. Applications often need directory traversal plus separate file permissions, atomic-replace access in a parent directory, or lock permissions in addition to file writes. Use owner only when the file-system UID relationship is a real invariant. Avoid broad /** write rules: they conceal which state, configuration, secret, or executable the service actually needs.
/srv/reporting/ r,
/srv/reporting/** r,
/var/lib/reportd/ rw,
owner /var/lib/reportd/** rwk,
/var/log/reportd/ rw,
/var/log/reportd/*.log a, Choose execution transitions and non-file permissions deliberately
Execution modes are security boundaries: ix inherits the current profile, px requires a discrete profile transition, and ux executes unconfined and should generally be rejected in production policy. File access alone does not grant networking, capabilities, signals, ptrace, D-Bus, mounts, or other mediated resources. Grant the narrow family, type, capability, or peer relationship demonstrated by the workload; a capability rule still confers a powerful kernel privilege.
/usr/bin/logger ix,
network inet stream,
capability net_bind_service,
signal (receive) set=(term) peer=unconfined, Keep site policy separate from packaged profile ownership
When a vendor profile contains an optional local include, place site-specific rules in the matching /etc/apparmor.d/local file. This reduces package-upgrade conflicts and makes the local security delta auditable. The include must appear inside the intended profile, and its filename is based on the profile's policy filename—not an arbitrary application label. Version the local file, document why every rule exists, and verify that the package profile still includes it after upgrades.
# /etc/apparmor.d/local/usr.local.bin.reportd
/etc/reportd/site.yaml r,
/srv/customer-reports/ r,
/srv/customer-reports/** r, Compile without loading, review expansion, then replace one profile
Run apparmor_parser with -Q to compile while skipping the kernel load, and use -p when include expansion needs review. Only after review should -r replace the loaded profile. The on-disk file is the persistent source that the boot-time service will load; the kernel copy is current runtime state. Reloading all AppArmor policy increases blast radius, so prefer replacing the one changed profile and verify its loaded mode and application behavior immediately.
apparmor_parser -Q /etc/apparmor.d/usr.local.bin.reportd
apparmor_parser -p /etc/apparmor.d/usr.local.bin.reportd
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.reportd
sudo aa-status Bound learning mode and distinguish runtime from persistent changes
Complain mode logs most policy violations instead of enforcing them, so it is a security reduction rather than a harmless verbosity setting. apparmor_parser -C -r forces the running replacement into complain mode without changing the source profile; the next normal reload restores the source-declared mode. aa-complain changes the on-disk mode and reloads it, so it persists until aa-enforce reverses it. Exercise a documented test matrix, review aa-logprof suggestions individually, and finish by verifying enforce mode.
sudo apparmor_parser -C -r /etc/apparmor.d/usr.local.bin.reportd
sudo aa-logprof
sudo aa-enforce /usr/local/bin/reportd
sudo aa-status Roll back policy changes without silently abandoning confinement
Keep the last reviewed profile and local include so a bad change can be reverted and reloaded as one unit. aa-disable both unloads and persistently disables a profile, leaving matching executions unconfined; it is an emergency action, not a troubleshooting shortcut. If it is explicitly authorized, record the exposure window and restore the profile with aa-enforce after fixing the source. Do not unload all profiles or disable AppArmor globally to solve one application's denial.
apparmor_parser -Q ./usr.local.bin.reportd.reviewed
sudo apparmor_parser -r ./usr.local.bin.reportd.reviewed
sudo aa-enforce /usr/local/bin/reportd
sudo aa-status Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- Ubuntu Server DocumentationAppArmorubuntu.com
- Ubuntu Security DocumentationAppArmor Security Profilesdocumentation.ubuntu.com
- Ubuntu Manpage Repositoryapparmor_parser(8)manpages.ubuntu.com
- AppArmor ProjectAppArmor Userspace Projectgitlab.com
- SUSESecurity and Hardening Guide: AppArmordocumentation.suse.com
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



