SELinux Basics: Enforcing Without Pain
Every forum answer about SELinux says setenforce 0. I think that's bad advice. SELinux is what stops a compromised web app from reading /etc/shadow, writing into /var/lib/mysql, or calling out to a command server. It only feels hard because the errors are terse and land in a log nobody reads.
These are the few commands I actually use on CentOS 7 to stay enforcing: reading denials, fixing labels, opening ports, flipping booleans, and a small custom module as a last resort. After two or three rounds it stops being scary.
First, install the tools. The base install lacks most of them:
sudo yum -y install policycoreutils-python setroubleshoot-server setools-console
policycoreutils-python provides semanage and audit2allow; setroubleshoot-server provides sealert, which translates denials into English.
Check the mode
getenforce
sestatus
The permanent setting is in /etc/selinux/config (SELINUX=enforcing). If someone has already set it to disabled, switching back requires a relabel on the next boot, which takes a few minutes:
sudo touch /.autorelabel
sudo reboot
To test whether SELinux is the cause, sudo setenforce 0, reproduce, then sudo setenforce 1 right away. That's a diagnostic, not a fix. I set a reminder in my head because forgetting the second command is easy.
Read the denials
Every denial is an AVC message in /var/log/audit/audit.log. Don't read it raw; use ausearch and sealert:
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log
A typical denial looks like:
type=AVC msg=audit(1546830000.123:456): avc: denied { read } for pid=1234 comm="nginx" name="index.html" dev="dm-0" ino=67890 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file
Read it as: a process in domain httpd_t (nginx) was denied read on a file labelled user_home_t. That one line says the fix is a label, not a boolean. A web server shouldn't read home-directory-labelled files, so the file is in the wrong place or has the wrong context.
File contexts
Files moved with mv from a home directory keep their old label. Files in a non-standard document root get that path's default label, usually default_t or var_t. Check and fix:
ls -Z /srv/www/site
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
semanage fcontext -a records the rule permanently so a later restorecon or relabel keeps it. chcon changes the label now but is lost on relabel, so I only use it for quick tests. For directories the web server must write to, use httpd_sys_rw_content_t.
To see what label a path should have: matchpathcon /srv/www/site/index.html.
Non-standard ports
Services can only bind to ports the policy assigns to their type. Moving nginx to 8443 or sshd to 2222 gives bind() ... Permission denied:
sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8443
If the port is already assigned to another type, use -m (modify) instead of -a, and think about whether you really want that.
Booleans
Booleans are switches for things the policy authors anticipated. List the httpd ones:
sudo semanage boolean -l | grep httpd
sudo getsebool -a | grep httpd_can
The ones I set most often:
sudo setsebool -P httpd_can_network_connect 1 # reverse proxy to a backend
sudo setsebool -P httpd_can_network_connect_db 1 # web app to remote MySQL/PostgreSQL
sudo setsebool -P httpd_can_sendmail 1 # app sends email
sudo setsebool -P httpd_use_nfs 1 # document root on NFS
-P makes it persistent. I pick the narrow boolean (_db) over the broad one (network_connect) whenever it covers the case.
A custom module, as a last resort
When no label or boolean fits, audit2allow builds a module from the logged denials:
sudo ausearch -m avc -ts recent -c myapp | audit2allow -M myapp_local
cat myapp_local.te
sudo semodule -i myapp_local.pp
Read the .te file before installing. If it has a line like allow httpd_t shadow_t:file read;, the answer is no: you've found a real problem in the application, not a policy gap. Only install rules you can justify.
Did the denials stop?
getenforce # Enforcing
sudo ausearch -m avc -ts recent | wc -l # should be 0 after your fix
sudo semodule -l | grep myapp_local
ls -Z /srv/www/site | head
Reproduce the original operation (load the page, start the service) and run ausearch again to confirm nothing new shows up.
Odd cases
ausearchshows nothing but the app still fails: some denials aredontaudit. Enable them withsudo semodule -DB, reproduce, thensudo semodule -Bto put things back.restoreconchanges nothing because the label already matches the default: the default is wrong for your use. Add asemanage fcontextrule first, then rerunrestorecon.semanage: command not found: installpolicycoreutils-python.ValueError: Port tcp/8443 already defined: usesemanage port -minstead of-a.- After
touch /.autorelabelthe box relabels and reboots again. Expected; five to ten minutes on a big disk.
It costs a few minutes per new service. I'll take that trade for damage stopping at the web server's own files when a dependency turns out to be bad.