Protecting sensitive paths such as /etc/shadow, .ssh directories, and key stores from processes with no right to read them, structuring profiles for daemons that legitimately handle sensitive data, and enforcing read-only access for data while denying writes to unneeded locations.

In episode 12 you shrunk the attack surface with deny rules for execution and setuid binaries. But there's a second dimension of confinement just as important: data. A daemon might not be able to execute a shell, but if it can read /etc/shadow or private keys, it's still a fatal leak when successfully exploited.
AppArmor gives you fine-grained control over file read-write access. This episode is about protecting data: denying access to sensitive paths, structuring profiles for daemons that legitimately touch sensitive data, and ensuring every piece of data can only be written where it should be.
Imagine a server as an office with a safe. /etc/shadow is the master key, ~/.ssh is the key cabinet, and the key store is the safe of important documents. DAC (ordinary permissions) already limits who may open the safe, but AppArmor adds a different layer: it restricts processes, not just users. A daemon running as root can technically read every file — unless its AppArmor profile explicitly forbids it.
That's the power of deny rules for data: a process that should never touch certain data, never will, no matter how high its privileges. And because deny wins over allow, this prohibition holds even if abstractions or other rules grant access.
Files holding system credentials — shadow, passwd, gshadow, plus their backup files — should only be readable by programs that genuinely need them, usually only authentication processes. Every other daemon should get an explicit deny rule. Start by checking which profiles are already loaded with aa-status, then add denies for daemons that should never touch these files:
deny /etc/shadow rw,
deny /etc/shadow- rw,
deny /etc/gshadow rw,
deny /etc/gshadow- rw,
deny /etc/security/opasswd rw,Notice the --suffixed files like /etc/shadow-: those are backups created by tools such as vipw. Short-term memory easily guesses to block /etc/shadow, but forgets the backup — yet a careful attacker hunts for backup files with looser permissions. Block both.
~/.ssh and the directories where private keys are stored are the second most valuable targets. Private keys in /root/.ssh, @{HOME}/.ssh, /etc/ssl/private, or /etc/kubernetes/pki open the door to other systems. For daemons that never make SSH connections or hold keys, deny the whole directories:
deny @{HOMEDIRS}/*/.ssh/** r,
deny /root/.ssh/** r,
deny /etc/ssl/private/** r,
deny /etc/kubernetes/pki/** r,
deny /var/lib/mysql/** rw,
deny /root/.gnupg/** r,The deny /var/lib/mysql/** rw rule above deserves attention: a web daemon never needs to read a MySQL database directly — that's the application's own job. This deny protects MySQL data from other exploited processes. The same pattern applies to other key stores: /etc/openvpn/, /etc/letsencrypt/live/, and secret directories mounted into containers (back to episode 10).
Not every daemon must be denied sensitive data. Daemons that legitimately handle it — for example backup agents, secret-rotation agents, or services reading certificates — need profiles built on a different principle: grant the minimal fit, and only for what is their job.
profile myapp /usr/sbin/myapp {
#include <abstractions/base>
/usr/sbin/myapp mr,
/etc/myapp/ r,
/etc/myapp/** r,
/var/lib/myapp-data/ r,
/var/lib/myapp-data/** r,
/var/run/myapp/ rw,
/var/log/myapp.log w,
deny /etc/shadow rw,
deny /etc/shadow- rw,
deny @{HOMEDIRS}/*/.ssh/** r,
deny /root/.ssh/** r,
deny /etc/ssl/private/** r,
deny /var/lib/mysql/** rw,
deny /tmp/** w,
}Notice the patterns above:
r), not rw. If the daemon only reads data, give it only r — modification isn't needed, so it isn't allowed./var/run/myapp/) and the log. No write access anywhere else.deny /tmp/** w prevents the daemon from writing temporary files in a location readable by all users — preventing symlink race scenarios and places to hide payloads.Tip
The "read-only for data, write only in designated areas" pattern is the easiest way to cut the damage of an exploit: an attacker who hijacks a process can't modify data, plant files, or overwrite configuration — they can only do what the profile allows.
Above you already saw one important idiom: read access without write. AppArmor distinguishes r (read) and w (write) separately, so "read-only" isn't just intent — it's enforced by the kernel. Use it for all data that should only be read:
r, never rw.r plus k for locking if needed.r access from the backup reader side, rw only for the process writing them.If you find an unexpected write denial, don't immediately add w — first ask why the daemon writes there. Often the answer is a wrong path or an application bug, and the write denial actually saves the data from corruption. To investigate the cause, use aa-logprof as a guide while reviewing each deny entry.
After the profile is installed in enforce mode, don't assume — verify. The fastest way is to trigger a denial by actually invoking the forbidden access from the application context, then check the logs. To make sure the profile is truly loaded in enforce, start from aa-status:
sudo aa-enforce /usr/sbin/myapp
sudo systemctl restart myapp
sudo journalctl -k --grep=apparmor | tail -20Every forbidden access appears as an apparmor="DENIED" event with the profile name, operation, and blocked path. From here you can confirm two things: the deny works, and no other deny is blocking legitimate application function. If legitimate denials appear, return to the complain-to-enforce cycle from episode 12 to fix them with discipline.
In this episode you've protected data from two directions: explicit denies for sensitive paths like /etc/shadow, ~/.ssh, and key stores — so processes without rights never touch them — and profiles for daemons that legitimately handle sensitive data, with the read-only-for-data principle, narrowed write areas, and denial verification after enforce.
The core takeaways:
r for reads, a small area for writes.In episode 14 we flip the view from configuration toward the software itself: how fragile AppArmor itself has been — the CVE history and regressions like the parser 4.1.0 case on Debian — and how a correct patching strategy keeps confinement real rather than a mere belief. See you in episode 14!