Deploying SELinux modules from Cfengine

Back in January, yo, I wrote about trying to figure out how to use Cfengine3 to do SELinux tasks; one of those was pushing out SELinux modules. These are encapsulated bits of policy, usually generated by piping SELinux logs to the audit2allow command. audit2allow usually makes two files: a source file that's human-readable, and a sorta-compiled version that's actually loaded by semodule.

So how do you deploy this sort of thing on multiple machines? One option would be to copy around the compiled module...but while that's technically possible, the SELinux developers don't guarantee it'll work (link lost, sorry). The better way is to copy around the source file, compile it, and then load it.

SANSNOC used this approach in puppet. I contacted them to ask if it was okay for me to copy their approach/translate their code to Cf3, and they said go for it. Here's my implementation:

bundle agent add_selinux_module(module) {
  # This whole approach copied/ported from the SANS Institute's puppet modules:
  # https://github.com/sansnoc/puppet
   files:
     centos::
       "/etc/selinux/local/."
         comment        => "Create local SELinux directory for modules, etc.",
         create         => "true",
         perms          => mog("700", "root", "root");

       "/etc/selinux/local/$(module).te"
         comment        => "Copy over module source.",
         copy_from      => secure_cp("$(g.masterfiles)/centos/5/etc/selinux/local/$(module).te", "$(g.masterserver)"),
         perms          => mog("440", "root", "root"),
         classes        => if_repaired("rebuild_$(module)");

       "/etc/selinux/local/setup.cf3_template"
         comment        => "Copy over module source.",
         copy_from      => secure_cp("$(g.masterfiles)/centos/5/etc/selinux/local/setup.cf3_template", "$(g.masterserver)"),
         perms          => mog("750", "root", "root"),
         classes        => if_repaired("rebuild_$(module)");

       "/etc/selinux/local/$(module)-setup.sh"
         comment        => "Create setup script. FIXME: This was easily done in one step in Puppet, and may be stupid for Cf3.",
         create         => "true",
         edit_line      => expand_template("/etc/selinux/local/setup.cf3_template"),
         perms          => mog("750", "root", "root"),
         edit_defaults  => empty,
         classes        => if_repaired("rebuild_$(module)");


  commands:
    centos::
      "/etc/selinux/local/$(module)-setup.sh"
        comment         => "Actually rebuild module.",
        ifvarclass      => canonify("rebuild_$(module)");
}

Here's how I invoke it as part of setting up a mail server:

bundle agent mail_server {
  vars:
    centos::
      "selinux_mailserver_modules" slist => { "postfixpipe",
                                              "dovecotdeliver" };

  methods:
    centos.selinux_on::
      "Add mail server SELinux modules" usebundle => add_selinux_module("$(selinux_mailserver_modules)");
}

(Yes, that really is all I do as part of setting up a mail server. Why do you ask? :-) )

So in the add_selinux_module bundle, a directory is created for local modules. The module source code, named after the module itself, is copied over, and a setup script created from a Cf3 template. The setup template looks like this:

#!/bin/sh
# This file is configured by cfengine.  Any local changes will be overwritten!
#
# Note that with template files, the variable needs to be referenced
# like so:
#
#   $(bundle_name.variable_name)

# Where to store selinux related files
SOURCE=/etc/selinux/local
BUILD=/etc/selinux/local

/usr/bin/checkmodule -M -m -o ${BUILD}/$(add_selinux_module.module).mod ${SOURCE}/$(add_selinux_module.module).te
/usr/bin/semodule_package -o ${BUILD}/$(add_selinux_module.module).pp -m ${BUILD}/$(add_selinux_module.module).mod
/usr/sbin/semodule -i ${BUILD}/$(add_selinux_module.module).pp

/bin/rm ${BUILD}/$(add_selinux_module.module).mod ${BUILD}/$(add_selinux_module.module).pp

Note the two kinds of disambiguating brackets here: {curly} to indicate shell variables, and (round) to indicate Cf3 variables.

As noted in the bundle comment, the template might be overkill; I think it would be easy enough to have the rebuild script just take the name of the module as an argument. But it was a good excuse to get familiar with Cf3 templates.

I've been using this bundle a lot in the last few days as I prep a new mail server, which will be running under SELinux, and it works well. Actually creating the module source file is something I'll put in another post. Also, at some point I should probably put this up on Github FWIW. (SANS had their stuff in the public domain, so I'll probably do BSD or some such... in the meantime,please use this if it's helpful to you.)

UPDATE: It's available on Github and my own server; released under the MIT license. Share and enjoy!