Learn AppArmor - Auditing & Monitoring
Episode 15 of 23

Learn AppArmor - Auditing & Monitoring

Building oversight over confinement: reading and aggregating AppArmor denial logs, using aa-notify for periodic summaries, journald- and auditd-based alerting, and establishing a compliance baseline with per-service profile coverage documentation and periodic security reviews.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 14 you realized AppArmor can be fragile — bugs in the kernel and parser are real threats. The question now: how do you know when confinement starts to break? Denials never read, profiles silently stopping to load, or an attack pattern trying to break into an application — all of that is only visible if there are eyes.

This episode builds those eyes. We'll read denial logs from various sources, aggregate them into comprehensible signals, set up alerting, and close with the compliance side: per-service profile coverage documentation and a security review baseline.

Why Monitoring Is Part of Confinement

Confinement without monitoring is an alarm without sound. A working deny rule means some process tried to do something forbidden — and that attempt can be a sign of an ongoing exploit. If you don't read denials, you lose AppArmor's most valuable security signal.

Three levels of awareness to build:

  • Reactive — reading logs when an incident happens. Too late to prevent, but still worth mastering.
  • Scheduled — periodic denial summaries, e.g. daily. Catches trends and anomalies.
  • Proactive — automated alerting when something suspicious appears. This is the end goal.

Sources of Denial Logs

AppArmor denials can appear in several places, depending on the system configuration:

  • Kernel ring buffer / journaldjournalctl -k shows AppArmor audit messages from the kernel; the fastest way on systems without auditd.
  • Syslog/var/log/syslog or /var/log/kern.log on systems using rsyslog.
  • auditd/var/log/audit/audit.log if the audit daemon is active; the most complete format for compliance needs.

All sources store the same events, just with different formats and locations. Start with journald — it's available on almost every modern system:

See AppArmor denials from the kernel
sudo journalctl -k --since "1 hour ago" --grep=apparmor

Notice the event pattern: apparmor="DENIED" followed by the profile name, operation, path, and process name. This is the pattern we'll parse for aggregation.

aa-notify: Denial Summaries

aa-notify is a tool from apparmor-utils that presents denial summaries without reading raw logs. It reads the logs, counts denials per profile, and shows a list — perfect for periodic reports.

Denial summary for the last 24 hours
sudo aa-notify -s 86400

The -s flag takes denials within a given second range. With -v you get more detail, and with -f you can point it at a specific log file if you want to process a non-default source. On systems with auditd, aa-notify automatically uses /var/log/audit/audit.log; on other systems it reads syslog or journald.

Tip

Schedule aa-notify as a daily job, not a tool only run during incidents. A consistent daily summary lets you recognize "normal" before you can detect "weird".

Denial Aggregation

Raw logs contain thousands of repeating lines. The real value appears after aggregation: which profile gets denied most, which path is hunted most often, and whether a new profile suddenly shows up. A single shell line is enough to sketch the big picture:

Top denials per profile in the last 24 hours
sudo journalctl -k --since "24 hours ago" \
  | grep 'apparmor="DENIED"' \
  | sed -E 's/.*profile="([^"]+)".*/\1/' \
  | sort | uniq -c | sort -rn | head -20

Read the result with critical questions: why does this profile produce so many denials? Is there deny noise that should be handled with an explicit deny rule (episode 12)? Or is there a sensitive path being probed — a sign of attack? For deeper aggregation, build a pipeline that groups by profile, operation, and path combinations, then store the results as a metric.

Alerting Based on journald and auditd

A daily summary catches problems after they've happened. For issues needing fast reaction — for example a new profile suddenly blocked en masse, or denials appearing for sensitive paths — set up automated alerting. The simplest approach: a systemd unit that runs aa-notify daily, combined with a service to send the results:

LinuxDaily AppArmor digest systemd unit
# /etc/systemd/system/aa-deny-digest.service
[Unit]
Description=AppArmor daily denial digest
 
[Service]
Type=oneshot
ExecStart=/usr/bin/aa-notify -s 86400 -f /var/log/audit/audit.log
LinuxTimer to schedule the digest
# /etc/systemd/system/aa-deny-digest.timer
[Unit]
Description=Run AppArmor digest daily
 
[Timer]
OnCalendar=daily
 
[Install]
WantedBy=timers.target

For real-time alerting, enable the audit daemon (auditd) and combine it with a tool that continuously reads audit.log — for example filtering AUDIT_APPARMOR_DENIED events with ausearch or forwarding them to your log pipeline and SIEM. A good alert threshold is usually a combination of two signals: a denial appearing on a sensitive path, or a sudden volume spike on one profile.

Search AppArmor denials in the audit log
sudo ausearch -m 1400 -ts today

Warning

Don't alert on every denial line — you'll drown in notifications and stop paying attention. Alert on anomalies: denials on sensitive paths, a previously quiet profile suddenly busy, or spiking denial volume. Low, consistent denial volume is just part of a normal server's life.

Compliance: Documenting Coverage per Service

The part often ignored until the audit arrives: documentation. Regulatory frameworks and internal policies almost always ask "which services are confined, with which profile, and in which mode?". Without records, the answer is a reconstruction job under pressure.

Build a simple coverage table updated on every change:

ServiceProfileModeStatusNotes
nginxusr.sbin.nginxenforceactivereloaded 2026-07, no regression
postgresusr.lib.postgresql.*.bin.postgresenforceactivelimited data read-write
sshdusr.sbin.sshdcomplainreviewawaiting full scenario tests
backup-agentusr.sbin.backup-agentenforceactivedata read access + write dir

This documentation isn't just compliance — it's a map telling you where confinement is weak (complain mode), where testing isn't finished, and which services still have no profile at all.

The Security Review Baseline

A baseline is a snapshot of the system's state at a point in time, used as a comparison point in the future. For AppArmor, the most useful snapshot is the aa-status output — the list of profiles, modes, and confined processes:

Create a baseline and compare
sudo aa-status > /var/lib/apparmor/aa-status-baseline.txt
sudo aa-status > /tmp/aa-status-now.txt
diff /var/lib/apparmor/aa-status-baseline.txt /tmp/aa-status-now.txt

Make this snapshot part of a periodic review. Changes worth questioning: a new profile? A profile changing mode from enforce to complain? A process that used to be confined now running unconfined? All three are markers that something changed — and change without a decision is the beginning of a security regression.

Tip

Store the baseline in version control together with your profiles, and set up a job that diffs aa-status every night against the baseline. An empty diff for weeks is proof of stable confinement — and a diff that suddenly appears is an invitation to investigate.

Conclusion

In this episode you've built full oversight over confinement: reading denial logs from journald, syslog, and auditd; aggregating them into signals with aa-notify and a one-line pipeline; setting up alerting with systemd timers and auditd; and establishing a compliance baseline with a per-service coverage table and regularly diffed aa-status snapshots.

The core takeaways:

  • Denials are a security signal, not just log junk — read and understand them.
  • aa-notify gives summaries; aggregation gives the big picture; alerting gives fast reaction.
  • Alert on anomalies, not on normal volume.
  • Document coverage per service — it's both your audit answer and your weakness map.
  • A regularly diffed aa-status baseline detects regressions early.

In episode 16 we zoom out from software to the kernel: where AppArmor sits in the Linux Security Module stack, how it interacts with Yama and Landlock, CONFIG_SECURITY_APPARMOR configuration, and the different layers filled by seccomp, capabilities, and LSMs. See you in episode 16!