Featured Post

Linux daemon using Python daemon with PID file and logging

The python-daemon package ( PyPI listing , Pagure repo ) is very useful. However, I feel it has suffered a bit from sparse documentation, an...

2013-03-15

Writing a new SELinux policy module for a standard init daemon

This is going to be a summary of my experience writing new policy modules for Ganglia gmetad and gmond on RHEL5. Ganglia is a "scalable distributed monitoring system for high-performance computing systems." I downloaded the package source distribution, and built RPMs myself.

In case you are looking to apply this to something else, here are a couple of the underlying assumptions:
  • the service is a standard init-launched daemon
  • each service only has one executable, the daemon program
In the case of gmetad and gmond, the daemon programs are, respectively, /usr/sbin/gmetad and /usr/sbin/gmond.

I have written about creating new SELinux policies before, but I think this is better in that it wraps things up into a module that may be removed or updated more easily than a monolithic policy. Note, however, that rules governing network ports are not bundled into the module. (See below.)

This is going to be an iterative process. Before even starting, one needs to know which files/directories the daemons will write to, and if they run non-root. If the package one is working with is well-documented, this may be obtained from the documentation. If not, some trial and error will be needed. Also, for most programs, these file/directory locations are configurable.

We use the GUI Selinux Policy Generation tool, system-config-selinux. There is a good article on using this tool by Dan Walsh dating back to 2007.

We will start with gmetad. In the case of gmetad, the default location for the RRD files is /var/lib/ganglia/rrds. So, the policy should allow write access to /var/lib/ganglia.

In the Selinux Policy Generation tool, these are the entries used:
  • Name: gmetad
  • Executable: /usr/sbin/gmetad
  • Standard Init Daemon
  • Incoming network ports, both TCP and UDP: 8651,8652
  • Common Application Traits
    • Application uses syslog to log messages
    • Application uses /tmp to Create/Manipulate temporary files
    • Application uses nsswitch or translates UID's (daemons that run as non root)
  • Add Directory: /var/lib/ganglia
This generates 4 files in whatever directory you specify at the end of the druid: gmetad.fc, gmetad,if, gmetad.sh, gmetad.te. If you examine gmetad.sh, you will see:
#!/bin/sh
make -f /usr/share/selinux/devel/Makefile
/usr/sbin/semodule -i gmetad.pp

/sbin/restorecon -F -R -v /usr/sbin/gmetad
/sbin/restorecon -F -R -v /var/lib/ganglia
/usr/sbin/semanage port -a -t gmetad_port_t -p tcp 8651
/usr/sbin/semanage port -a -t gmetad_port_t -p tcp 8652
/usr/sbin/semanage port -a -t gmetad_port_t -p udp 8651
/usr/sbin/semanage port -a -t gmetad_port_t -p udp 8652
Note that the ports are not bundled into the "compiled" module file, gmetad.pp. The port rules are added "manually". The module merely defines the type gmetad_port_t.

The gmetad.te file is what we will be editing in the iterative steps below.  The first line determines a version number, that allows you to update a policy using "semodule -u gmetad.te".

policy_module(gmetad,1.0.0)

Make sure the gmetad service is not running. Now, turn off the auditd service, and move away the audit log file to simplify finding incremental changes in policy that are needed:
# service gmetad stop
# service auditd stop
# cd /var/log/audit< # mv audit.log audit.log.20130313-1500
Then, start up the audit daemon, followed by gmetad. Wait for a few minutes (or much longer) for gmetad to do its thing, and for auditd to accumulate all or most of the AVC denials that would affect gmetad. Once a sufficient amount of time has passed:
# grep gmetad /var/log/audit/audit.log | audit2allow -R > audit.out
The output should look like:
require {
        type gmetad_t;
        class capability { setuid setgid };
}

#============= gmetad_t ==============
allow gmetad_t self:capability { setuid setgid };
kernel_read_kernel_sysctls(gmetad_t)

Next, edit gmetad.te, and increment the version number. Append to the end of gmetad.te the contents of audit.out. Then, generate the policy file, and load the updated policy:
# make -f /usr/share/selinux/devel/Makefile
# semodule -u gmetad.pp
Next, shut down gmetad, shut down auditd, move the audit log away, start auditd, and start gmetad. Wait a bit, and look for new denials in the audit log by doing
# grep gmetad /var/log/audit/audit.log | audit2allow -R > audit2.out
To append any new rules, you have to manually pick out the new unique lines from audit2.out and put them in the appropriate sections (the 'require' section, or the block of allows) of gmetad.te. For gmetad.te, I found there wasn't much change between iterations. For gmond, however, there were quite a few, mostly the addition of file getattr permissions. This involved changing many lines like:

allow gmond_t lvm_t:file read;  -->  allow gmond_t lvm_t:file { getattr read };
This iteration may have to include alternating gmond and gmetad since gmetad has to connect to the gmond port, which means something like:
allow gmetad_t gmond_port_t:tcp_socket name_connect;

Here at the Wake Forest University HPC facility, we have a combination of cfengine and Puppet to manage machine configurations: cfengine for the RHEL5 nodes, and Puppet for the RHEL6 nodes. The policy .pp file is distributed via cfengine, and a shellcommand is run by cfengine to load/update the module, and additional commands do the file system relabelling and the port rules. Basically, reproducing the .sh file that the Policy Generation Tool creates.

UPDATE 2013-03-22: If you have a cyclic dependency in your policy modules -- in this case, gmond refers to gmetad, and gmetad refers to gmond -- you will find that you can't load the modules individually. All you have to do is load them all in one command line:
semodule -i gmond.pp gmetad.pp