SELinux is one of the least well understood components of modern Linux distributions. Search any forum or mailing list and you are likely to find recommendations to switch it off because it “breaks things”. When we decided to migrate the ResNet and eduroam servers from CentOS 5 to 6, we took the decision to move from “SELinux off by default” to “SELinux on by default, and only off where necessary”. Turns out it’s not that hard to configure 🙂
Introduction
Explaining exactly how SELinux works is beyond the scope of this blog post – but suffice it to say that it works by labelling parts of the filesystem, users and processes with a context to say what they are for. It will then block actions it thinks are unsafe – for example, even if your httpd has filesystem permissions to write to /etc/ssh/
, by default SELinux would block this action because it isn’t usual. To learn more, have a look at the many web pages about SELinux.
Configuring SELinux
Configuring SELinux to work nicely on your system is best described as “training” it, and is a lot like training a spam filter. You have to look at the SELinux audit log to see what actions were blocked, review them, and then add them to a whitelist by loading a new policy. You can load as many supplementary policies as you need.
Your SELinux installation should always be left in enforcing mode by default. Edit /etc/selinux/config
to make sure it is enforcing, but be aware that this needs a reboot to take effect.
# /etc/selinux/config SELINUX=enforcing SELINUXTYPE=targeted
When you want to temporarily enable permissive mode, issue the command sudo setenforce 0
. This takes effect immediately. Don’t forget to run sudo setenforce 1
to re-enable enforcing mode after you’ve finished debugging.
When you start out configuring SELinux, it’s important to run it in permissive mode, rather than enforcing mode. Let’s say you want to debug an application that wants to perform operations A, B and C, which would all be blocked by SELinux. In permissive mode, the application would be allowed to run, and SELinux logs what it would have blocked had it been in enforcing mode. Operations A, B and C are all logged and can then be added to the policy. In enforcing mode, the application tries operation A, is blocked and often doesn’t even bother trying operations B and C – so they are never logged, and cannot be debugged.
Capturing SELinux audit logs and generating a policy
All SELinux operations are stashed in the audit log, which is in /var/log/audit/audit.log
on CentOS by default. The audit log is not hugely easy to read by eye, but you can install the package policycoreutils-python
which provides some handy analysis tools.
Assuming you’ve already dropped SELinux into permissive mode, now try executing the operations you wish to debug: might be testing a Nagios plugin, running a new application, or something else. It should succeed as SELinux is permissive, but it will log all the things it would otherwise have blocked.
Run this command, grepping for the process you’re interested in to generate a policy file to grant all those accesses. Be aware of namespacing issues. SELinux comes with a bunch of bundled policies which are called things like nagios
and httpd
. If you are loading supplementary policies for these things, it’s best to add a prefix like resnet-nagios
or sysops-nagios
. The default file extension for a text-mode policy is .te.
sudo cat /var/log/audit/audit.log | grep nagios | audit2allow -m resnet-nagios > resnet-nagios.te
Your .te file is more-or-less human readable and you should inspect it to make sure your new policy isn’t going to allow anything bad. Here’s the .te file I generated by running the above command on my Nagios server:
module resnet-nagios 1.0; require { type nagios_system_plugin_t; type nagios_t; type tmp_t; type initrc_tmp_t; type nagios_services_plugin_t; class file { read ioctl write getattr open append }; } #============= nagios_services_plugin_t ============== allow nagios_services_plugin_t tmp_t:file append; #============= nagios_system_plugin_t ============== allow nagios_system_plugin_t tmp_t:file append; #============= nagios_t ============== allow nagios_t initrc_tmp_t:file { read write getattr open ioctl }; #!!!! The source type 'nagios_t' can write to a 'file' of the following types: # nagios_var_run_t, nagios_log_t, nagios_tmp_t, root_t allow nagios_t tmp_t:file { write ioctl read open getattr append };
Loading a custom SELinux policy by hand
Now that we’ve come up with a text-based SELinux policy, it needs to be converted into a binary policy that can be loaded. The command is very similar but note the capital M rather than lower case, which makes it write out a binary policy which has a .pp extension (not to be confused with Puppet manifests ;))
sudo cat /var/log/audit/audit.log | grep nagios | audit2allow -M resnet-nagios
Once you’ve got your binary SELinux module, loading it by hand is easy:
sudo semodule -i resnet-nagios.pp
The CentOS wiki page on SELinux is handy for manipulating policies manually.
Loading a custom SELinux policy with Puppet
There are Puppet modules available which handle the compiling and loading of modules automatically – you just need to provide the .te file and it will handle the rest. For the ResNet and eduroam servers, we are using James Fryman’s puppet-selinux module. It’s not necessarily the best but it was the most appropriate for us at the time we took the decision over a year ago and has worked solidly – other modules are also available. Here’s how we’re using it:
include selinux if $::osfamily == 'RedHat' { selinux::module { 'resnet-nagios': ensure => 'present', source => 'puppet:///modules/nagios/resnet-nagios.te', } }
Summary
That’s more or less it! It’s easy to set SELinux in permissive mode, capture output and create your own policies. There really is no excuse not to be using it 😉
I hope this article has been useful. If you’re a member of UoB and you want to talk about SELinux or Puppet, grab me on the #uob-unix
IRC channel. I’m dj_judas21
.