
Table of Contents
SELinux (Security-Enhanced Linux) is one of the most powerful tools to manage user space access control, although it can be particularly complex to implement. A very steep learning curve causes it to be usually overlooked. The web hosting industry hardly ever uses SELinux, especially when products like cPanel requires it to be disabled.
Traditional access control in the simplest terms, that being users
and groups
can be referred to as “Discretionary Access Control” (DAC). In contrast, SELinux offers the ability to add a layer of “Mandatory Access Control” (MAC). Even if a process has relevant user
and permissions
, SELinux can still block unauthorised actions. As an added bonus, all blocked actions are logged. The added observability alone could be reason enough to build around it.
Shown is an example of php-fpm
for two sites selinux.rskeens.com
and private.rskeens.com
. DAC based tightening with open_basedir
is effective and almost every web host will leave it at that because it is simple and straight forward. Perhaps even housing each site in a container will be considered in an effort to achieve even stronger isolation.
Since we are using php-fpm
, we need a new SELinux domain to initiate per pool, making systemd
a great option. Otherwise if those processes were forked, then they would normally all inherit the same SELinux domain.
Once php-fpm
is set up with the pool as its own service, we need two categories of config files:
- Type Enforcement (.te) = define policy rules against labeled subjects / objects.
- File Contexts (.fc) = map filesystem paths to labels.
Let us start with a very basic Type Enforcement file for selinux.rskeens.com
:
12345678910111213141516171819202122232425
policy_module(selinux_rskeens_com, 1.0)
require {
class file { read getattr open map };
class dir { read getattr search open };
class sock_file { create write read append getattr unlink };
}
type selinux_rskeens_com_php_t;
type selinux_rskeens_com_php_exec_t;
type selinux_rskeens_com_docroot_t;
files_type(selinux_rskeens_com_docroot_t)
type selinux_rskeens_com_socket_t;
files_pid_file(selinux_rskeens_com_socket_t)
allow selinux_rskeens_com_php_t selinux_rskeens_com_docroot_t:dir { read getattr search open };
allow selinux_rskeens_com_php_t selinux_rskeens_com_docroot_t:file { read getattr open map };
manage_files_pattern(selinux_rskeens_com_php_t, selinux_rskeens_com_socket_t, selinux_rskeens_com_socket_t)
manage_sock_files_pattern(selinux_rskeens_com_php_t, selinux_rskeens_com_socket_t, selinux_rskeens_com_socket_t)
files_pid_filetrans(selinux_rskeens_com_php_t, selinux_rskeens_com_socket_t, { file sock_file })
init_daemon_domain(selinux_rskeens_com_php_t, selinux_rskeens_com_php_exec_t)
The syntax can be intimidating at first but we now have a quite beautiful layout for the service via init_daemon_domain
comprising of php type selinux_rskeens_com_php_t
with exec selinux_rskeens_com_php_exec_t
plus document root selinux_rskeens_com_docroot_t
and even socket file selinux_rskeens_com_socket_t
. Real world setups would have more complex configurations to adjust mutability per path such as uploads, sessions and logs.
Now that we have selinux_rskeens_com
policy, we can move on to the File Contexts file:
123
/usr/sbin/php-fpm-selinux-rskeens-com -- gen_context(system_u:object_r:selinux_rskeens_com_php_exec_t,s0)
/srv/www/selinux.rskeens.com(/.*)? gen_context(system_u:object_r:selinux_rskeens_com_docroot_t,s0)
/run/php-selinux-rskeens-com\.sock gen_context(system_u:object_r:selinux_rskeens_com_socket_t,s0)
Everything is now ready to compile a Policy Package (.pp) file:
123
make -f /usr/share/selinux/devel/Makefile selinux_rskeens_com.pp
Compiling targeted selinux_rskeens_com module
Creating targeted selinux_rskeens_com.pp policy package
Even if a Policy Package file successfully compiles, its usability is still not certain until semodule
is used. This is admittedly frustrating and can make debugging difficult. Fortunately everything works well for us:
1
semodule -i selinux_rskeens_com.pp
A final implementation step requires contexts to be restored, this is done using restorecon
:
1234567
restorecon -Rv /usr/sbin/php-fpm-selinux-rskeens-com \
/srv/www/selinux.rskeens.com
Relabeled /srv/www/selinux.rskeens.com from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:selinux_rskeens_com_docroot_t:s0
Relabeled /srv/www/selinux.rskeens.com/index.html from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:selinux_rskeens_com_docroot_t:s0
Relabeled /srv/www/selinux.rskeens.com/info.php from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:selinux_rskeens_com_docroot_t:s0
Relabeled /srv/www/selinux.rskeens.com/test.php from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:selinux_rskeens_com_docroot_t:s0
Service php-fpm-selinux-rskeens-com
can then be restarted. Check the setup:
1234567
ps -eZ | grep php
system_u:system_r:selinux_rskeens_com_php_t:s0 40639 ? 00:00:00 php-fpm-selinux
system_u:system_r:selinux_rskeens_com_php_t:s0 40640 ? 00:00:00 php-fpm-selinux
system_u:system_r:selinux_rskeens_com_php_t:s0 40641 ? 00:00:00 php-fpm-selinux
system_u:system_r:selinux_rskeens_com_php_t:s0 40642 ? 00:00:00 php-fpm-selinux
system_u:system_r:selinux_rskeens_com_php_t:s0 40643 ? 00:00:00 php-fpm-selinux
system_u:system_r:selinux_rskeens_com_php_t:s0 40644 ? 00:00:00 php-fpm-selinux
Looks good. Now a test can be performed by attempting to read the contents of private file secret
of document root private.rskeens.com
from selinux.rskeens.com
:
123
type=AVC msg=audit(1759666772.576:14792): avc: denied { open } for pid=40846 comm="php-fpm-selinux" path="/srv/www/private.rskeens.com/secret" dev="sda1" ino=133665 scontext=system_u:system_r:selinux_rskeens_com_php_t:s0 tcontext=unconfined_u:object_r:httpd_sys_content_t:s0 tclass=file permissive=1
type=AVC msg=audit(1759666772.576:14792): avc: denied { read } for pid=40846 comm="php-fpm-selinux" name="secret" dev="sda1" ino=133665 scontext=system_u:system_r:selinux_rskeens_com_php_t:s0 tcontext=unconfined_u:object_r:httpd_sys_content_t:s0 tclass=file permissive=
At this point extensive testing should first be done with permissive
mode remaining active. There will be a lot of tweaks needed but eventually the setup will be working perfectly.
Once ready, switch to enforce
mode by editing /etc/selinux/config
to set SELINUX=enforcing
. Reboot the system and use the command getenforce
to be absolutely certain enforcement is live across system restarts.