Puppet managed deployment of SELinux modules
Today I needed to work out how to deploy a custom SELinux module across machines using Puppet.
For background, this was a small module to make an adjustment to the targetted policy as shipped with RHEL 6 to allow the Kerberos admin daemon to communicate with openldap via a unix socket (i.e. over ldapi://). I went through the usual drill on a sample machine using audit2allow to generate an SELinux module like this:
grep kadmin /var/log/audit/audit.log | audit2allow -M kadminldapilocal
This output two files: kadminldapilocal.te which is a text file describing the policy, and kadminldapilocal.pp which is a compiled binary module. The latter is the thing you need to then load with semodule -i kadminldapilocal.pp
.
So, how to deploy this module across machines with puppet? Well, the first way would be to use the native puppet selmodule
resource type to deploy the binary SELinux module like this:
file {'/usr/share/selinux/targeted/kadminldapilocal.pp': ensure => present, owner => 'root', group => 'root', mode => 644, source => 'puppet:///kerberos/kadminldapilocal.pp', } selmodule {'kadminldapilocal': ensure => present, syncversion => true, require => File ['/usr/share/selinux/targeted/kadminldapilocal.pp'], }
But as Benjamin Rose points out in his blog deploying managing binary SELinux has disadvantages. In particular, if you have your puppet manifests under cversion control, storing a lump of binary in there isn’t particularly meaningful. Also you become subject to binary compatibility of your SELinux module with the SELinux policy on the machines you’re deploying to. I don’t actually know how much of an issue this is in practice, but I could imagine it might be a problem if you have a heterogeneous operating system environment. Anyway, I liked the approach that Ben outlined in his blog post for deploying the SELinux module via the text (.te) files rather than the binary modules, but wanted to reinvent it such that it was self contained within puppet rather than relying on a client side script. So this is what I came up with:
class semodloader ($moddir = '/usr/local/share/selinux') { package { ['policycoreutils', 'checkpolicy', ]: ensure => latest} file {$moddir: ensure => directory, owner => 'root', group => 'root', mode => 755, require => [ Package['policycoreutils'], Package['checkpolicy'], ], } define semodule ($source, $status = 'present') { case $status { present: { file {"${semodloader::moddir}/${name}.te": owner => 'root', group => 'root', mode => 644, source => $source, require => File ["${semodloader::moddir}"], } file {"${semodloader::moddir}/${name}.mod": owner => 'root', group => 'root', mode => 644, require => File ["${semodloader::moddir}"], } file {"${semodloader::moddir}/${name}.pp": owner => 'root', group => 'root', mode => 644, require => File ["${semodloader::moddir}"], } exec {"${name}-buildmod": command => "checkmodule -M -m -o ${name}.mod ${name}.te", path => ['/sbin', '/usr/sbin', '/bin', '/usr/bin'], cwd => "${semodloader::moddir}", subscribe => File ["${semodloader::moddir}/${name}.te"], refreshonly => true, } exec {"${name}-buildpp": command => "semodule_package -m ${name}.mod -o ${name}.pp", path => ['/sbin', '/usr/sbin', '/bin', '/usr/bin'], cwd => "${semodloader::moddir}", subscribe => File ["${semodloader::moddir}/${name}.mod"], refreshonly => true, } exec {"${name}-install": command => "semodule -i ${name}.pp", path => ['/sbin', '/usr/sbin', '/bin', '/usr/bin'], cwd => "${semodloader::moddir}", subscribe => File ["${semodloader::moddir}/${name}.pp"], refreshonly => true, } # Alternatively: # selmodule {$module: # ensure => $ensure, # syncversion => true, # require => File ["$moddir/$name.pp"], # } } absent: { file {"${semodloader::moddir}/${name}.te": ensure => absent, } file {"${semodloader::moddir}/${name}.mod": ensure => absent, } file {"${semodloader::moddir}/${name}.pp": ensure => absent, } exec {"${name}-remove": command => "semodule -r ${name}.pp > /dev/null 2>&1", path => ['/sbin', '/usr/sbin', '/bin', '/usr/bin'], } } default: { fail("status variable not recognized") } } } }
So, to use this to load my kadminldapilocal module, I simply use:
class {'semodloader': } semodloader::semodule {'kadminldapilocal': source => 'puppet:///kerberos/kadminldapilocal.te', status => 'present', }
And, changing status => 'present'
to status => 'absent'
will trigger unloading and removing the module.
Would welcome comments on this, as I am new to puppet (started using it this week), and so can’t help thinking there must be a simpler way of doing this!
Hi,
I actually really like this code block. Thanks for posting it. I’m incorporating it into a SELinux management module I’m building. I’ve put a link to some of the optimizations that I’ve made.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.rb
hosted with ❤ by GitHub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.rnb
hosted with ❤ by GitHub
Take a look and let me know if you have any questions or feedback.
Thanks.
-James
james@frymanet.com
Hi James,
Your work looks very interesting, thanks for the pointer, I’ll have a look through it carefully when I get chance. I should say though that the code block I posted had a number of problems with it such that I refined it over the following few weeks. I’ll make a new post with the updated code.
I’ve added an updated entry here: