Managing custom SELinux modules can be a headache – there’s often a lot of guess-and-check work that goes into compiling a module from initial auditd failure logs. This is not to mention that the normal process for compiling a module involves running a bunch of different commands (audit2allow, checkmodule, semodule_package, semodule). It would be absolutely fantastic if I could just manage the text-based .te rule format and have all of the module compilation occur behind the scenes.

Enter puppet as, once again, the perfect solution to this dilemma. We start by creating a shell script to compile all of the SELinux .te files in a specific directory. This script gets put onto everyserver, a class we have which defines our standard build.

As a note, we define ${puppetfilespath} to be /var/lib/puppet/files, and ${cls} is set at the top of each class, defining class-local-storage, or the location where puppet can expect to find the fake-root for the current class.

[root@puppet ~]# cat /var/lib/puppet/files/everyserver/usr/bin/compile_selinux.sh
#!/bin/bash
MODDIR=/etc/selinux/mymodules

CHKMOD=/usr/bin/checkmodule
MODPKG=/usr/bin/semodule_package
SEMOD=/usr/sbin/semodule
LS=/bin/ls

for i in $(${LS} -1 ${MODDIR}/*.te); do
j=${i/te/mod};
${CHKMOD} -M -m -o ${j} ${i};
k=${i/te/pp};
${MODPKG} -o ${k} -m ${j};
${SEMOD} -i ${k};
done

Note that this script can take a while to complete, as the last command (semodule -i) installs the compiled module, and this usually takes a good 30-60 seconds to complete.

Now we need to manage this new script and the directory it needs using puppet:

file {
“/etc/selinux/mymodules”:
ensure => directory,
mode => 600;
“/usr/bin/compile_selinux.sh”:
content => template(“${puppetfilespath}/${cls}/usr/bin/compile_selinux.sh.erb”),
mode => 755,
require => File["/etc/selinux/mymodules"];
}

Then we need to define a exec in puppet for this script to be run:

exec {
“repackage-semods”:
command => “/usr/bin/compile_selinux.sh”,
cwd => “/tmp/”,
refreshonly=>true;
}

To make our lives easier, we define a simple custom type that runs this exec after a change is detected:

define myselmod()
{
file { $name:
owner => root,
group => root,
mode => 600,
path => “/etc/selinux/mymodules/${name}”,
notify => Exec["repackage-semods"],
require => File["/etc/selinux/mymodules"],
content => template(“${puppetfilespath}/${cls}/etc/selinux/mymodules/${name}.erb”)
}
}

Now, in the manifests, you need only do this to manage a custom SELinux module:

myselmod {“mybasicmodule.te”: }

And in that file:

[root@puppet ~]# cat /var/lib/puppet/files/everyserver/etc/selinux/mymodules/mybasicmodule.te.erb
module mybasicmodule 1.0;

require {
type snmpd_t;
type proc_mdstat_t;
class file { read ioctl getattr };
}

#============= snmpd_t ==============
allow snmpd_t proc_mdstat_t:file { read ioctl getattr };

Now suppose you want to add another SELinux rule to this module. All you need to do is edit the .te file and add in the rule you want. Then the client will check in and automatically compile and install the module for you. Really simple! Plus, another advantage is that this method now uses only text-files as opposed to binary data blobs, which is perfect for integration with your versioning system (if you use one). In fact, this is the original reason I decided to use this method over the built-in puppet semodule type. Dealing with .te files only is insanely easy, and takes a lot of the headache out of administering custom SELinux modules across an infrastructure, thus making everyone more likely to actually use SELinux rather than just disable it or put it into perpetual permissive mode.