Skip to content
June 15, 2011 / jonathanunderwood

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!

4 Comments

Leave a Comment
  1. James Fryman / Aug 17 2011 5:03 pm

    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.


    ## Concepts incorporated from:
    ## https://stuckinadoloop.wordpress.com/2011/06/15/puppet-managed-deployment-of-selinux-modules/
    define selinux::module(
    $ensure = 'present',
    $mod_dir = '/usr/share/selinux',
    $source
    ) {
    # Set Resource Defaults
    File {
    owner => 'root',
    group => 'root',
    mode => '0644',
    }
    # Only allow refresh in the event that the initial .te file is updated.
    Exec {
    path => '/sbin:/usr/sbin:/bin:/usr/bin',
    resfreshonly => 'true',
    cwd => "${mod_dir}",
    }
    ## Begin Configuration
    file { $mod_dir:
    ensure => directory,
    }
    file { "${mod_dir}/${name}.te":
    ensure => $ensure,
    source => $source,
    tag => 'selinux-module',
    }
    file { "${mod_dir}/${name}.mod":
    tag => ['selinux-module-build', 'selinux-module'],
    }
    file { "${mod_dir}/${name}.pp":
    tag => ['selinux-module-build', 'selinux-module'],
    }
    # Specific executables based on present or absent.
    case $ensure {
    present: {
    exec { "${name}-buildmod":
    command => "checkmodule -M -m -o ${name}.mod ${name}.te",
    notify => Exec["${name}-buildpp"],
    }
    exec { "${name}-buildpp":
    command => "semodule_package -m ${name}.mod -o ${name}.pp",
    notify => Exec["${name}-install"],
    }
    exec { "${name}-install":
    command => 'semodule -i ${name}.pp',
    }
    # Set dependency ordering
    File["${mod_dir}/${name}.te"]
    ~> Exec["${name}-buildmod"]
    ~> Exec["${name}-buildpp"]
    ~> Exec["${name}-install"]
    -> File<| tag == 'selinux-module-build' |>
    }
    absent: {
    exec { "${name}-remove":
    command => "semodule -r ${name}.pp > /dev/null 2>&1",
    }
    # Set dependency ordering
    Exec["${name}-remove"]
    -> File<| tag == 'selinux-module' |>
    }
    default: {
    fail("Invalid status for SELinux Module: ${ensure}")
    }
    }
    }

    view raw

    module.rb

    hosted with ❤ by GitHub


    ## Concepts incorporated from:
    ## https://stuckinadoloop.wordpress.com/2011/06/15/puppet-managed-deployment-of-selinux-modules/
    define selinux::module(
    $ensure = 'present',
    $mod_dir = '/usr/share/selinux',
    $source
    ) {
    # Set Resource Defaults
    File {
    owner => 'root',
    group => 'root',
    mode => '0644',
    }
    # Only allow refresh in the event that the initial .te file is updated.
    Exec {
    path => '/sbin:/usr/sbin:/bin:/usr/bin',
    resfreshonly => 'true',
    cwd => "${mod_dir}",
    }
    ## Begin Configuration
    file { $mod_dir:
    ensure => directory,
    }
    file { "${mod_dir}/${name}.te":
    ensure => $ensure,
    source => $source,
    tag => 'selinux-module',
    }
    file { "${mod_dir}/${name}.mod":
    tag => ['selinux-module-build', 'selinux-module'],
    }
    file { "${mod_dir}/${name}.pp":
    tag => ['selinux-module-build', 'selinux-module'],
    }
    # Specific executables based on present or absent.
    case $ensure {
    present: {
    exec { "${name}-buildmod":
    command => "checkmodule -M -m -o ${name}.mod ${name}.te",
    notify => Exec["${name}-buildpp"],
    }
    exec { "${name}-buildpp":
    command => "semodule_package -m ${name}.mod -o ${name}.pp",
    notify => Exec["${name}-install"],
    }
    exec { "${name}-install":
    command => 'semodule -i ${name}.pp',
    }
    # Set dependency ordering
    File["${mod_dir}/${name}.te"]
    ~> Exec["${name}-buildmod"]
    ~> Exec["${name}-buildpp"]
    ~> Exec["${name}-install"]
    -> File<| tag == 'selinux-module-build' |>
    }
    absent: {
    exec { "${name}-remove":
    command => "semodule -r ${name}.pp > /dev/null 2>&1",
    }
    # Set dependency ordering
    Exec["${name}-remove"]
    -> File<| tag == 'selinux-module' |>
    }
    default: {
    fail("Invalid status for SELinux Module: ${ensure}")
    }
    }
    }

    view raw

    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

  2. jonathanunderwood / Aug 17 2011 11:05 pm

    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.

  3. jonathanunderwood / Aug 17 2011 11:23 pm

    I’ve added an updated entry here:

    Deploying SELinux modules with Puppet (reprise)

Trackbacks

  1. Deploying SELinux modules with Puppet (reprise) « Coding blog

Leave a comment