DB-유

SELinux Basics: Enforcing Without Pain

· linux, selinux, centos, security

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

  • ausearch shows nothing but the app still fails: some denials are dontaudit. Enable them with sudo semodule -DB, reproduce, then sudo semodule -B to put things back.
  • restorecon changes nothing because the label already matches the default: the default is wrong for your use. Add a semanage fcontext rule first, then rerun restorecon.
  • semanage: command not found: install policycoreutils-python.
  • ValueError: Port tcp/8443 already defined: use semanage port -m instead of -a.
  • After touch /.autorelabel the 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.