Why go deeper than selinux-apparmor
The basic overview of Mandatory Access Control is in selinux-apparmor. This article covers policy mechanics: how to write, debug, and extend rules, and how to work with booleans, file contexts, and port labelling.
Real scenarios:
- "Apache cannot read
/var/www/data/" -> fix the file context - "Custom service on a nonstandard port -> SELinux blocks the bind" -> port label
- "You need a legitimate exception, not a disable" -> custom policy module
- "Mass tuning via switches" -> SELinux booleans
- "Compliance", fine-grained control with auditing
Type enforcement, the foundation
SELinux has a four-part security context:
user_u:role_r:type_t:level
└── user (SELinux user, not the Linux user!)
└── role (system_r, unconfined_r, ...)
└── type / domain (httpd_t, sshd_t, var_log_t, ...)
└── MLS/MCS level (s0, s0:c1.c5, ...)
- On a process the type is called the domain (
httpd_t,sshd_t) - On a file, socket, or port it is the type (
var_log_t,httpd_sys_content_t) - A type enforcement rule:
allow source_domain target_type:class { permissions };
Example:
allow httpd_t httpd_sys_content_t:file { read getattr open };This means a process in the httpd_t domain may read/getattr/open files of type
httpd_sys_content_t. Everything else is denied.
View a context:
ls -Z /var/www/html/ # files
ps -eZ | grep httpd # processes
ss -tnpZ # sockets
id -Z # current user context
Targeted vs MLS vs MCS
| Policy | What |
|---|---|
| targeted | default; confines services only (httpd, sshd, named...). Users in unconfined_t run unrestricted |
| mls | Multi-Level Security; for government, high-grade systems with classification levels |
| mcs | Multi-Category Security; containers in OpenShift get different categories for isolation |
Active mode:
sestatus
# SELinux status: enabled
# Loaded policy name: targeted
# Current mode: enforcing
# Mode from config file: enforcing
- enforcing blocks and logs
- permissive only logs, does not block (for debugging)
- disabled is off, labels are not maintained (to come back you must relabel)
audit2allow, generating rules from denials
The main workflow when "X does not work because of SELinux":
# 1. Enable permissive temporarily (for a specific domain) or fully
semanage permissive -a httpd_t # for one domain
# OR
setenforce 0 # globally, for debugging
# 2. Reproduce the problem
systemctl restart httpd
curl http://localhost/
# 3. Find denials in auditd
ausearch -m AVC -ts recent
# 4. Generate a policy rule
ausearch -m AVC -ts recent | audit2allow -M my-httpd-fix
# Creates my-httpd-fix.te (text) and my-httpd-fix.pp (binary)
# 5. Load it
semodule -i my-httpd-fix.pp
# 6. Return to enforcing
semanage permissive -d httpd_t
setenforce 1
audit2allow is not "the right way". It is a quick patch.
First check whether the context you need already exists, or whether there is a
boolean.
Booleans, targeted switches
The distribution policy includes dozens of booleans for common on/off features:
# Show all
getsebool -a | head
# A specific one
getsebool httpd_can_network_connect
# -> off
# Enable temporarily (until reboot)
setsebool httpd_can_network_connect on
# Enable permanently
setsebool -P httpd_can_network_connect on
# Description
semanage boolean -l | grep httpd
Common ones:
httpd_can_network_connect, apache may reach the network (proxy/upstream)httpd_can_sendmailsamba_export_all_rwnfs_export_all_rwssh_chroot_rw_homedirs
Before audit2allow, always check for a boolean. There is most likely
a ready-made solution already.
File contexts, semanage fcontext
A file's context is an xattr named security.selinux (see extended-attributes).
When a file is created, it inherits the context from the parent directory.
If you put a file in a nonstandard location, its context will be wrong:
ls -Z /opt/myapp/index.html
# unconfined_u:object_r:usr_t:s0 ...
# Apache wants httpd_sys_content_t. Assign it:
semanage fcontext -a -t httpd_sys_content_t '/opt/myapp(/.*)?'
restorecon -Rv /opt/myapp
semanage fcontextadds a rule to the database (persistent)restoreconapplies the rules to existing fileschconchanges the context without writing to the database (temporary, will not survive a relabel)
List the current ones:
semanage fcontext -l | grep httpd
Port labels
SELinux labels sockets too:
# apache wants port 8080
systemctl restart httpd # FAIL: SELinux denied
ausearch -m AVC | tail -5 # you will see a denial on name_bind
# Fix:
semanage port -a -t http_port_t -p tcp 8080
systemctl restart httpd # OK
# List
semanage port -l | grep http
If sshd runs on a nonstandard port, do the same:
semanage port -a -t ssh_port_t -p tcp 2222
Custom policy module
When you need a legitimate exception, package it as a module:
# my-custom.te
module my-custom 1.0;
require {type httpd_t;
type custom_data_t;
class file { read getattr open };}
allow httpd_t custom_data_t:file { read getattr open };Compile and install:
checkmodule -M -m -o my-custom.mod my-custom.te
semodule_package -o my-custom.pp -m my-custom.mod
semodule -i my-custom.pp
View loaded modules:
semodule -l | head
Remove:
semodule -r my-custom
Relabel the whole filesystem
After a catastrophic misconfiguration or after bringing SELinux back from disabled:
# On the next boot
touch /.autorelabel
reboot
# Or interactively
fixfiles -F restore
This can take hours on large filesystems.
sealert, graphical AVC analyzer
dnf install setroubleshoot setroubleshoot-server
systemctl enable --now setroubleshootd
It analyzes an AVC denial and suggests a fix in plain language:
sealert -a /var/log/audit/audit.log
This produces "Try changing boolean X" / "Try restorecon" / "If you really need this, generate a module".
Useful for juniors and people new to SELinux.
When things go wrong
- "SELinux is preventing..." in the log: run
sealert -a /var/log/audit/audit.logorausearch -m AVC -ts recent | audit2allow. Permission deniedwith no obvious cause: checkls -Zandgetenforce. The problem is often not in Linux permissions but in the SELinux context.- Correct mode 644, owner, group, but the read fails: the context
does not fit the domain. Run
restorecon -Rv path. semanagenot found: installpolicycoreutils-python-utils(RHEL) orpolicycoreutils-python(on older systems).- dontaudit rules hide denials: some denials are suppressed by the system.
semodule -DBdisables dontaudit for debugging. Remember to revert:semodule -B. - A policy module stops working after a restart: the module is not loaded
into the active policy. Check
semodule -l | grep my-custom. If it is missing, runsemodule -iagain. - Changes made with chcon are lost: that is expected.
chcondoes not write to the database. Usesemanage fcontextplusrestorecon.
Checklist "SELinux problem"
getenforce, the mode. IfPermissive, the problem is not in SELinux.ausearch -m AVC -ts recent, is there a denial in audit.logls -Z, what context the unreadable file hasps -eZ | grep procname, what domain the process runs insemanage boolean -l | grep <topic>, is there a ready-made togglesemanage fcontext -l | grep <path>, what context it should berestorecon -Rv, fix existing filesaudit2allowas a last resort