| |

LFCS 145 ๐Ÿง AppArmor โ€” Profiles and Enforcement

โฑ๏ธ Estimated read: 11 min | |

The previous chapter introduced AppArmor’s conceptual model: path-based profiles attached to executables, with two enforcement modes. This chapter focuses on the practical mechanics of the profile files themselves and the operational workflow of switching between enforce and complain mode, reloading profiles after edits, and using the tooling to iterate on profile development. The LFCS exam tests the profile syntax, the mode-switching commands, and the validation of the profile before it is applied. The critical trap is editing a profile file in /etc/apparmor.d/ and assuming the change is active. The kernel holds the loaded profile in memory. The file on disk is only read when the apparmor_parser is invoked. A change to the file without a reload has no effect on the running system.

Key point: The profile file in /etc/apparmor.d/ is the source. The loaded profile in the kernel is the runtime state. The apparmor_parser -r command compiles the file and replaces the runtime profile. The aa-enforce and aa-complain commands switch the mode of an already-loaded profile. The apparmor_parser -Q command verifies a profile’s syntax without loading it.


Why profile management requires a specific mental model

The source versus runtime problem. The profile file is text. It contains the rules that define what the executable is allowed to do. The kernel does not read the file at runtime; it reads the compiled version that the apparmor_parser loads. When the administrator edits the file, the kernel is unaware. The apparmor_parser -r command recompiles the file and replaces the in-memory profile. Without this command, the change is only on disk.

The validation problem. A profile with a syntax error fails to load, and the apparmor_parser returns an error. In the worst case, the parse error leaves the old profile in place or removes the profile entirely, and the application becomes unconfined. The apparmor_parser -Q (or --skip-kernel-load) flag validates the syntax without applying the profile to the kernel. This is the dry-run step that catches the errors before the application is affected.

The complain mode problem. The complain mode is the development mode. The profile is loaded, but the kernel does not enforce it. The denials are logged to the system log, and the application continues to work. The administrator uses the log to identify the missing rules, adds them to the profile, and reloads. When no more denials appear, the mode is switched to enforce. The aa-complain and aa-enforce commands are the switches.

The hat problem. AppArmor supports “hats” (child profiles) for the same executable, selected by the environment variable AA_EXEC_PROFILE_NAME. A program can run under different profiles depending on the context, and each hat has its own rules. The ^hat_name syntax in the profile file defines a hat. The aa-exec -p <profile> command runs a program under a specific profile or hat. Hats are an advanced feature, but the exam may test the aa-exec command.

The trade-off. The file-based model is simple: edit the file, reload, test. The cost is that the reload is not automatic. The administrator must remember the reload step. The systemctl reload apparmor command reloads all profiles, which is the alternative to reloading one by one. The systemd integration ensures that the profiles in /etc/apparmor.d/ are loaded at boot.


a. The profile syntax and file structure

An AppArmor profile is a file in /etc/apparmor.d/. The filename is the executable path with the slashes replaced by dots. The /usr/sbin/httpd executable becomes the usr.sbin.httpd file. The file starts with the profile declaration, which is the executable path, followed by the rules in braces.

# /etc/apparmor.d/usr.sbin.httpd
#include <tunables/global>

/usr/sbin/httpd {
  #include <abstractions/base>
  #include <abstractions/apache2-common>

  /var/www/html/** r,
  /var/log/apache2/** rw,
  /etc/apache2/** r,
  /run/apache2.pid rw,
  network inet tcp,
  network inet6 tcp,
  capability net_bind_service,
  deny /etc/shadow r,
}

The #include <tunables/global> line loads the global variables (e.g., @{HOME}). The #include <abstractions/base> line loads the common rules (e.g., read access to /etc/ld.so.cache). The #include <abstractions/apache2-common> line loads the rules specific to Apache. The rules in the braces are the profile-specific rules.

The rule format is [audit] [deny] [owner] <path> <permissions>. The r is read, w is write, k is lock, m is memory map, and x is execute. The ** matches any file or directory recursively. The * matches any file in a single directory. The network rule allows a socket family and type. The capability rule allows a Linux capability.

The deny prefix is the explicit denial. The audit prefix logs the access even if it is allowed. The owner prefix restricts the rule to files owned by the process’s user.

# Deny access to shadow
deny /etc/shadow r,

# Allow only owner's files
owner /home/*/private/** rw,

# Audit the access
audit /var/log/secure r,

The rules are evaluated in order. The first matching rule wins. The deny rule overrides the allow rule if both match.


b. The apparmor_parser and the mode commands

The apparmor_parser command compiles the profile and loads it into the kernel. The -r flag replaces the existing profile. The -a flag adds a new profile. The -R flag removes a profile. The -Q flag validates the syntax without loading.

# Validate the profile syntax
sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.httpd

# Load (or reload) the profile
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.httpd

# Add a new profile
sudo apparmor_parser -a /etc/apparmor.d/usr.sbin.httpd

# Remove a profile
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.httpd

The aa-enforce and aa-complain commands switch the mode of a profile. The argument is the profile file or the executable path. The commands call apparmor_parser with the appropriate flags.

# Switch to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.httpd

# Switch to complain mode
sudo aa-complain /etc/apparmor.d/usr.sbin.httpd

The aa-status command confirms the mode change.

sudo aa-status | grep httpd
# /usr/sbin/httpd

The systemctl reload apparmor command reloads all profiles. The systemctl restart apparmor command unloads and reloads all profiles, which is the heavier operation.


c. The profile development workflow

The profile development workflow is the iterative process of running the application in complain mode, reading the denials, adding the rules, reloading the profile, and switching to enforce mode.

# 1. Set the profile to complain mode
sudo aa-complain /etc/apparmor.d/usr.sbin.httpd

# 2. Run the application and exercise its features
sudo systemctl restart httpd
curl http://localhost/

# 3. Read the denials
sudo journalctl -k | grep -i apparmor

# 4. Add the missing rules to the profile file
# (edit /etc/apparmor.d/usr.sbin.httpd)

# 5. Reload the profile
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.httpd

# 6. Repeat steps 2-5 until no denials appear

# 7. Switch to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.httpd

The aa-logprof command is the automated version of steps 3 and 4. It reads the log, shows each denial, and asks whether to allow or deny it. The answer is added to the profile. The aa-logprof then writes the updated profile and reloads it.

sudo aa-logprof

The aa-logprof interactive prompt asks for each denial:

Reading log entries from /var/log/syslog.
Updating AppArmor profiles in /etc/apparmor.d/.
Profile:  /usr/sbin/httpd
Path:     /srv/applogs/error.log
New Mode: rw
Severity: unknown

[1 - include <abstractions/apache2-common>]
[2 - /srv/applogs/** rw,]
[3 - /srv/applogs/ rw,]
[(A)llow] / (D)eny / (N)ew / (G)lob / Glob w/(E)xt / (Q)uit

Enter selection [1 - 3] :

The selection adds the rule to the profile. The A option allows the access, the D denies it, and the N creates a new rule manually. The G creates a glob rule, and the E creates a rule with the extension.


Complete Example Session

# ============================================
# PART 1: VIEW THE PROFILE
# ============================================

cat /etc/apparmor.d/usr.sbin.httpd
# ============================================
# PART 2: VALIDATE THE SYNTAX
# ============================================

sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.httpd
# No output: the syntax is valid.
# ============================================
# PART 3: SET COMPLAIN MODE
# ============================================

sudo aa-complain /etc/apparmor.d/usr.sbin.httpd
sudo aa-status | grep httpd
# complain mode
# ============================================
# PART 4: EXERCISE THE APP
# ============================================

sudo systemctl restart httpd
curl http://localhost/
# ============================================
# PART 5: READ THE DENIALS
# ============================================

sudo journalctl -k | grep -i apparmor
# apparmor="DENIED" profile="/usr/sbin/httpd" name="/srv/applogs/error.log"
# ============================================
# PART 6: ADD THE RULE
# ============================================

# Edit the profile, add:
# /srv/applogs/** rw,

sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.httpd
# ============================================
# PART 7: SWITCH TO ENFORCE
# ============================================

sudo aa-enforce /etc/apparmor.d/usr.sbin.httpd
sudo aa-status | grep httpd
# enforce mode
# ============================================
# PART 8: VERIFY NO NEW DENIALS
# ============================================

sudo systemctl restart httpd
sudo journalctl -k | grep -i apparmor
# No new denials.
# ============================================
# PART 9: USE aa-logprof
# ============================================

sudo aa-logprof
# Interactive suggestions.

The nine parts trace the profile management: view, validate, complain, exercise, read, add, enforce, verify, and aa-logprof.


Quick Reference

Profile Rules

RulePurpose
/path/** rRead recursively
/path/* wWrite in single dir
network inet tcpTCP over IPv4
capability net_bind_serviceBind privileged port
deny /etc/shadow rExplicit denial
owner /home/*/** rwOwner-only access
audit /var/log/secure rLog access

apparmor_parser Flags

FlagPurpose
-rReplace (reload)
-aAdd
-RRemove
-QValidate only
-vVerbose

Mode Commands

CommandEffect
aa-enforce <profile>Enforce mode
aa-complain <profile>Complain mode
aa-statusShow modes

Development Tools

CommandPurpose
aa-logprofSuggest from log
aa-genprof <app>Generate new profile

Best Practices

โœ… Do This:

sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.httpd
<!-- Validate syntax before loading -->              # โœ…
sudo aa-complain /etc/apparmor.d/usr.sbin.httpd
<!-- Use complain mode for development -->           # โœ…
sudo aa-enforce /etc/apparmor.d/usr.sbin.httpd
<!-- Switch to enforce in production -->             # โœ…

โŒ Don’t Do This:

# Edit the profile and assume it is active
<!-- The kernel holds the old version -->            # โŒ
# Reload without validating
<!-- A syntax error disables the profile -->         # โŒ
# Leave a profile in complain mode
<!-- The app is not confined -->                     # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Profile change not appliedNo reloadapparmor_parser -r
Profile fails to loadSyntax errorapparmor_parser -Q
Application still deniedMissing ruleCheck log, add rule
Enforce breaks appComplain mode was not testedTest in complain first
Profile is missingFile name mismatchUse the executable path

Real-World Examples

1. Validate Profile

sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.httpd

2. Reload Profile

sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.httpd

3. Complain Mode

sudo aa-complain /etc/apparmor.d/usr.sbin.httpd

4. Enforce Mode

sudo aa-enforce /etc/apparmor.d/usr.sbin.httpd

5. Read Denials

sudo journalctl -k | grep -i apparmor

6. Add Read Rule

/var/www/html/** r,

7. Add Write Rule

/var/log/apache2/** rw,

8. Add Network Rule

network inet tcp,

9. Add Capability

capability net_bind_service,

10. Use aa-logprof

sudo aa-logprof

Visual

Profile Development Workflow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Set complain mode                           โ”‚
โ”‚  aa-complain                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Exercise the app                            โ”‚
โ”‚  โ†’ Denials logged                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Read log                                    โ”‚
โ”‚  journalctl -k                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Add rules + reload                          โ”‚
โ”‚  apparmor_parser -r                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Switch to enforce mode                      โ”‚
โ”‚  aa-enforce                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Source vs Runtime

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  /etc/apparmor.d/usr.sbin.httpd              โ”‚
โ”‚  โ†’ Source file                               โ”‚
โ”‚  โ†’ Edited by administrator                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ”‚ apparmor_parser -r
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Kernel profile                              โ”‚
โ”‚  โ†’ Compiled profile                          โ”‚
โ”‚  โ†’ Enforced at runtime                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Profile directory/etc/apparmor.d/
Profile filenameExecutable path with dots
Validate syntaxapparmor_parser -Q
Load/reloadapparmor_parser -r
Complain modeaa-complain
Enforce modeaa-enforce
Denial logjournalctl -k | grep apparmor
Development toolaa-logprof

Key takeaways:

  • The file is the source; the kernel is the runtime. The profile file in /etc/apparmor.d/ is not read at runtime. The apparmor_parser -r command compiles it and replaces the in-memory profile. An edit without a reload has no effect.
  • The apparmor_parser -Q validates the syntax. The dry-run step catches the syntax errors before the profile is loaded. A profile with a syntax error can fail to load, leaving the application unconfined.
  • The complain mode is the development mode. The profile is loaded but not enforced. The denials are logged. The administrator uses the log to identify the missing rules and adds them to the profile.
  • The aa-logprof automates the rule addition. It reads the log, shows each denial, and suggests the rule. The administrator accepts or denies each suggestion, and the profile is updated automatically.
  • The workflow is iterative. The complain mode, the exercise, the log read, and the rule addition are repeated until no denials appear. Then the profile is switched to enforce mode.

Remember: The profile file is the source. The kernel profile is the runtime. The apparmor_parser -r is the reload. The aa-complain is the development. The aa-enforce is the production. The aa-logprof is the automation. The exam tests the reload step and the mode distinction.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!