Everyone says "start with complain mode" for AppArmor profiling, but most tutorials gloss over the critical part: interpreting the logs correctly to build an *effective* profile, not just a permissive one. The goal isn't to just silence the noise; it's to understand the legitimate access patterns so you can lock it down properly.
Here's the actual process, skipping the fluff:
* Install `apparmor-utils`, ensure the module is loaded. Put your agent's binary (or interpreter, e.g., `/usr/bin/python3.11`) into enforce mode with a dummy deny profile first: `aa-genprof your_agent_bin`. Deny everything at the prompt to create a baseline.
* Immediately switch to complain mode: `sudo aa-complain your_agent_bin`. Now run your agent through its full operational cycle—not just startup. This means executing its core tasks, handling network requests, reading/writing temporary files, accessing models, etc.
* The logs (`/var/log/syslog` or `journalctl -f`) will now show what it *tries* to do. The key is to ask for each entry:
* Is this path/network access required for correct function, or is it opportunistic?
* Is the specific syscall necessary? (e.g., `open` vs `openat`)
* Can the access be constrained to a narrower subtree? (e.g., `/tmp/agent_*` vs `/tmp/*`)
The real work begins after collection. You must audit each logged event. Common noise you should probably deny:
* Probing `/proc/sys/kernel/` for irrelevant OS details.
* Attempting to read `/etc/passwd` or shadow files unless legitimately needed for user lookup.
* Scanning all network interfaces. Bind to specific ones if needed.
* Writing to arbitrary locations in `/home`. Restrict to a specific workspace directory.
The output is a profile where every rule has a justification tied to your agent's threat model. Only then do you switch back to enforce mode. This method prevents the common pitfall of creating a policy that's just a verbose allow-list of everything the agent happened to touch during a superficial test run.
If it's not in the threat model, it's not secure.
That point about separating required access from opportunistic noise is huge. I'm setting up profiles for some Python-based data processing agents, and the logs are full of attempts to read things like `/etc/ld.so.cache` or probe random `~/.local/share` directories that aren't strictly necessary.
So when you're asking those questions for each log entry, how do you actually test if a specific file read is "required"? Do you just add a deny rule for that path, switch to enforce, and see if your agent breaks? Seems like a lot of trial and error.