Mastering the SELinux denial debugging workflow: reading deny logs with ausearch, translating them into policy rules with audit2allow, creating and installing local policy modules, plus best practices so you don't get trapped into generating policies blindly.

In episode 6, you learned to use booleans to resolve denials without touching the policy. But not every denial has a boolean. Sometimes your application genuinely needs rights that the built-in policy doesn't allow — for example reading log files in a special format, or accessing a specific device.
For cases like this, SELinux provides a very powerful yet dangerous tool: audit2allow. This tool reads the audit log and translates denials into ready-to-use policy rules. Interestingly, this tool can also make you forget how to "read" denials — which is why this episode focuses on the correct workflow, not just running the generation engine.
Before touching audit2allow, make sure the debugging process follows a disciplined order. Skipping a step makes you analyze the wrong data:
ausearch or grep.Important
Step 5 is what separates an engineer who understands SELinux from one who's just "shooting". If a boolean already allows that behavior, don't generate a module. A policy module is the last resort, not the first — every allow rule you add expands one domain's attack surface.
The audit log is the source of truth. The first command you must master:
sudo ausearch -m AVC -ts recentThe result looks roughly like this:
type=AVC msg=audit(1710000000.123:456): avc: denied { read } for pid=1234
comm="httpd" name="index.html" dev="sda1" ino=5678
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:var_log_t:s0 tclass=file permissive=0This is SELinux's "police report", and there are five elements you must read:
scontext is the subject — the domain of the process trying to access, here httpd_t.tcontext is the object — the label of the file or resource being targeted, here var_log_t.tclass is the object type — file, dir, tcp_socket, and so on.{ read } is the permission being requested.permissive=0 confirms this access was truly blocked, not just recorded.To filter denials belonging to one process, add the -c flag with the command name, for example ausearch -m AVC -c httpd. Get used to filtering — a production server's audit log can contain thousands of denials irrelevant to your problem.
After understanding the denial, it's time to ask for rule suggestions. Run audit2allow -a to read the entire audit log and generate proposed rules:
sudo audit2allow -aIf your log only contains the denial above, the output will look like this:
#============= httpd_t ==============
allow httpd_t var_log_t:file read;
allow httpd_t var_log_t:file open;Notice: audit2allow only translates denials into one-to-one allow rules. It doesn't know your business context. For this machine, reading the var_log_t file is "needed" — but from a security standpoint, allowing httpd to read all system log files is a bad idea. This is where your understanding from step 4 determines the quality of the output.
Once you're satisfied with the proposed rules, generate the module with the -M flag:
sudo audit2allow -a -M myhttpdThis command produces two files: myhttpd.te (the policy source in Type Enforcement format) and myhttpd.pp (the compiled policy package). Here's the contents of myhttpd.te:
module myhttpd 1.0;
require {
type httpd_t;
type var_log_t;
class file { read open };
}
allow httpd_t var_log_t:file { read open };Install the module into the active policy with semodule -i, then verify and test:
sudo semodule -i myhttpd.pp
sudo semodule -l | grep myhttpdIf it turns out it's not needed, remove it with semodule -r myhttpd. Removing is easier than editing — so don't hesitate to try and clean up.
Local policy modules are a legitimate tool, but misuse can damage system security. Follow these principles:
allow, don't install it.semanage fcontext, not by giving the domain access to an entire object type.var_log_t is used by all system log files. Allowing httpd_t to read var_log_t means also allowing it to read the audit log. It's safer to label the application's dedicated log directory with a new type, then allow access to only that type.-D flag produces dontaudit rules to suppress noise. Too much dontaudit actually hides the signs of real attacks — denials that could have been an indicator of compromise become invisible.Warning
Never run audit2allow -a -M on a production server that's been running for a long time without filtering first. An audit log containing months of denials will produce a giant module that allows all sorts of domain and object combinations — the exact opposite of what SELinux wants. Always filter with ausearch -c or -ua so only denials from one subject are generated.
In this episode 7, you've held the correct denial debugging workflow: reading AVC records with ausearch and understanding its five important elements, asking for rule suggestions with audit2allow -a, creating a module with audit2allow -M, installing with semodule -i, and the principles for not generating policies blindly.
The key takeaways:
So far we've talked about processes and domains — not yet about the humans logging into the system. In episode 8, we'll discuss Users, Roles & MLS/MCS: how SELinux users are mapped from Linux users, the role of role in restricting domain transitions, and two classification mechanisms — Multi-Level Security for hierarchical data and Multi-Category Security for isolation between workloads.