Learn AppArmor - Variables, Tunables & Includes
Episode 8 of 23

Learn AppArmor - Variables, Tunables & Includes

Making profiles portable with built-in variables for the home directory and procfs, understanding the tunables directory, and leveraging built-in and custom abstractions to write shared rules once and reuse them across many profiles.

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

Introduction

In episode 7, you could fix denials quickly because you understood how to read logs. But notice: every profile we've written so far still contains manually typed paths — /var/log/myapp/*.log, /usr/sbin/nginx, and so on. A profile like that is only correct on one machine. Move it to another system with a different home directory or a different procfs mount point, and the profile starts denying access that should be legitimate.

Episode 8 introduces three mechanisms that make AppArmor profiles portable and non-duplicative: variables, tunables, and includes. You'll use the debugging knowledge from episode 7 — including reading denials with journalctl -k --grep=apparmor — to prove these mechanisms work.

Variables: Avoiding Hardcoded Paths

Every user has a home directory in a different location — /home/alice, /home/bob, or /root for root. If a profile writes /home/alice/.config/myapp/** r, that profile is only correct for alice. Variables answer this:

LinuxVariables using preconfigured paths
owner @{HOME}/.config/myapp/** r,
@{HOME}/.local/share/myapp/** rw,

@{HOME} is a built-in variable set according to the user running the process. The owner prefix makes the rule apply only when the process owns the files it accesses — the standard pattern for personal config files.

You can define your own variables at the top of a profile:

LinuxDefining your own variable
@{CACHE_DIR}=/var/cache/myapp
 
@{CACHE_DIR}/** rw,

The @ naming mark distinguishes variables from ordinary paths. AppArmor provides several commonly used built-in variables: @{PROC} for the procfs mount point, @{PID} for the process pid, and @{sys} for sysfs.

LinuxThe @{PROC} variable for procfs rules
@{PROC}/net/if_inet6 r,

With @{PROC}, the rule stays correct even if procfs is mounted at a non-standard location.

Tunables: The Home of Variables and Settings

This is where AppArmor stores variable definitions and other settings that are per-system — not per-application. It lives at /etc/apparmor.d/tunables/:

Contents of the tunables directory (example)
/etc/apparmor.d/tunables/
  global
  home
  home.d/
  multiarch
  proc
  sys
  local/

Key files:

  • global — defines core variables like @{PROC} and @{sys}. Almost every profile starts with a line including this file.
  • home — defines @{HOME}; its actual value is set from home.d.
  • proc — rules specific to procfs.
  • local/ — the place for local overrides; leave it alone during upgrades.

Every profile you write from scratch should start with:

LinuxRequired profile preamble
#include <tunables/global>

An include with angle brackets <...> tells AppArmor to look for that file in /etc/apparmor.d/. Which brings us to the next mechanism.

Abstractions: Ready-Made Rules

Some groups of applications need nearly identical rules: every program that reads DNS needs access to a resolver, every logging daemon needs access to /dev/log, every GUI application needs access to a graphics server. Rewriting these rules in every profile is wasteful and a source of inconsistency. The solution is abstractions:

LinuxIncluding abstractions
#include <abstractions/base>
#include <abstractions/nameservice>
  • abstractions/base — the foundation almost always needed: base libraries, loader, common procfs paths. Think of it as the "minimum requirements" for an ordinary Linux program to run.
  • abstractions/nameservice — rules for DNS and resolvers. In episode 6 we withheld network inet udp; this abstraction is what usually adds it back safely for name resolution.
  • And there are many more: abstractions/ssl_certs, abstractions/openssl, abstractions/php, abstractions/X11, and dozens of others.

Abstractions also make profiles far more compact. The Nginx profile we discussed in episodes 6 and 7 looks like this:

LinuxA profile with abstractions
#include <tunables/global>
 
/usr/sbin/nginx flags=(enforce) {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/ssl_certs>
 
  network inet tcp,
  /etc/nginx/** r,
  /var/www/** r,
  /var/log/nginx/*.log w,
}

Notice: DNS capability comes from nameservice, certificates from ssl_certs — you don't need to rewrite those rules every time you create a profile.

Custom Abstractions for Your Team

The power of abstractions emerges when you start creating your own. Imagine a team running a dozen microservices that share the same path patterns:

Linuxabstractions/mycompany/backend
# Shared rules for mycompany backend services
# Must not contain rules specific to a single application.
 
/var/run/mycompany/** rw,
/var/lib/mycompany/** rw,
/var/log/mycompany/*.log rw,

Save that file as /etc/apparmor.d/abstractions/mycompany/backend, and every team profile just includes it:

LinuxUsing a custom abstraction
#include <abstractions/mycompany/backend>

When the path pattern changes, you edit one file — all profiles update. One important note: changing an abstraction file doesn't automatically update profiles already loaded in the kernel. Reload every profile that includes it with apparmor_parser -r for the change to take effect. This applies the DRY (Don't Repeat Yourself) principle to the policy world, and becomes the foundation for managing many services in episode 9.

Tip

A discipline of separation: variables for values that differ across systems, tunables for global definitions, abstractions for rule sets shared across profiles, and profiles for rules specific to a single application. Don't mix them — a profile piling up too many abstractions becomes hard to analyze when a denial appears.

Conclusion

In episode 8 you've met three reuse mechanisms in AppArmor: variables like @{HOME} and @{PROC} that replace hardcoded paths, the tunables directory that hosts global definitions, and abstractions — both built-in and custom — that let a single rule set serve dozens of profiles at once.

Keys to take home:

  • #include <tunables/global> is the first line of almost every profile.
  • Variables for dynamic values, abstractions for shared rules.
  • Custom abstractions turn policy into team-maintainable code.

With this foundation, you're ready to face real services. In episode 9, we'll apply everything to Web & Application Services: the built-in profiles for Nginx, Apache, and OpenSSH, customizations for web roots and SSL at custom locations, MySQL and PostgreSQL profiles, application runtimes like Node and Python, and the log rotation traps.