Magazine

Automating System Management with Puppet and SELinux

Posted on the 21 June 2021 by Satish Kumar @satish_kumar86

Puppet is the third automation framework that we will check out. It is the oldest one in our list, with its first release in 2005, and is commonly seen as the baseline against which other automation frameworks are compared. It has commercial backing through the Puppet company, also often referred to as Puppet Labs.

How Puppet works

Like SaltStack,Puppetusesan agent/server-based model with public-keyauthentication of the agents to ensure no rogue agents are active within the environment.

ThePuppet masterhasaccess to thePuppet manifests, which is the declarationof the state that Puppet wants to achieve. These manifests use a specific language inspired by Ruby and can refer to classes provided by modules to ensure reusability across the environment.

Puppet modules, hence, arethe workhorse within Puppet, and Puppet has asignificant community called Puppet Forge that allows you to download and install modules created by the community to more easily manage your environment.

Puppet agents will regularly connect to the master, informing the master of the current details of the remote machine. Thesecurrent details are calledfactsand can be used by Puppet to dynamically handle changes in the environment. The master then compiles thetarget state in what it calls acatalogand sends that catalog over to the agent. The agent then applies this catalog and reports the results back.

Installing and configuring Puppet

The Puppet company offers integrated packages for several Linux distributions. The following instructions focus on RPM-compatible distributions, but other platforms have very similar instructions:

  • The Puppet company provides repository definitions through RPM files. After the repositories are established, you can install  puppetserver  and  pdk (on the master) and puppet-agent (on the remote systems) so that the software is readily available to use:
# yum install https://yum.puppet.com/puppet6-release-el-8.noarch.rpm
master ~# yum install puppetserver pdk
remote ~# yum install puppet-agent
  • Configure the master to have its certificate properly named. Edit the puppet.conf file inside /etc/puppetlabs/puppet and, within the [master] section, update or add the following settings:
master ~# vim /etc/puppetlabs/puppet/puppet.conf
certname = ppubssa3ed.internal.genfic.local
server = ppubssa3ed.internal.genfic.local
environment = production
  • Start the Puppet server so that the clients can start connecting to it:
master ~# systemctl start puppetserver
  • On the remote systems, edit the same configuration file, and update or add the following settings in the [main] section:
remote ~# vim /etc/puppetlabs/puppet/puppet.conf
[main]
certname = rem1.internal.genfic.local
server = ppubssa3ed.internal.genfic.local
environment = production
runinterval = 1h
  • Next, start the Puppet agent:
remote ~# systemctl start puppet
  • On the master node, we can now query the pending certificate requests. It should display the requests from the agents we recently started:
master ~# /opt/puppetlabs/bin/puppetserver ca list
Requested Certificates:
  rem1.internal.genfic.local   (SHA256) ...
  • We can accept this request (sign the certificate) as follows:
master ~# /opt/puppetlabs/bin/puppetserver ca sign --certname rem1.internal.genfic.local
Successfully signed certificate request for rem1.internal.genfic.local
  • To validate whether the connection works, log in on the remote machine and trigger the agent to apply the (currently empty) catalog:
remote ~# /opt/puppetlabs/bin/puppet agent --test

Unlike SaltStack, where we can push a change to the agents, Puppet relies on the agents to frequently poll the server. In the configuration we made earlier, we configured the agent to check every hour. With the puppet agent --test command, we can signal the agent to run the state check immediately.

Creating and testing the SELinux class with Puppet

Let’s create our packt_selinux class, through which we will configure our remote machine’s SELinux settings:

  • Call the Puppet Development Kit (PDK) on the master node inside the /etc/puppetlabs/code/modules directory:
master ~# cd /etc/puppetlabs/code/modules
master ~# pdk new module packt_selinux --skip-interview

The result is an empty module with lots of default files and directories. We will be mostly working with the module’s manifest file.

  • Inside the packt_selinux/manifests directory, create a new file named init.pp with the following content:
class packt_selinux {
  file { "/usr/share/selinux/custom":
    ensure => directory,
    mode => "0755",
  }
}
  • Next, inside the  /etc/puppetlabs/code/environments/production/manifests  location, create a file called site.pp with the following content:
node 'rem1.internal.genfic.local' {
  include packt_selinux
}

Thesite.ppfile provides the top-level hierarchy for Puppet to associate its environmentwith the appropriate definitions. In this example, the node with the hostnamerem1.internal.genfic.localis configured through a reference topackt_selinux, the module we created previously.

Inside thepackt_selinuxmodule, we’ve created thepackt_selinuxclass, which currently is composed of a single directive to create/usr/share/selinux/custom.

  • With these definitions in place, have the remote agent update its state:
remote ~# puppet agent -t

In product environments, it is common to have this command either scheduled regularly or to run the Puppet agent continuously as a daemon.

With the class properly assigned to the node, we can expand our configuration with more SELinux details.

Assigning SELinux contexts to filesystem resources with Puppet

Let’s augment our current class definition with the following snippet:

file { 'selinux_custom_module_test':
  path => "/usr/share/selinux/custom/test.cil",
  ensure => file,
  owner => "root",
  group => "root",
  source => "puppet:///modules/packt_selinux/test.cil",
  require => File["/usr/share/selinux/custom"],
  seltype => "usr_t",
}

For this block to work properly, we need to place thetest.cilSELinux module in thefiles/folder inside thepackt_selinuxmodule location. This block will have Puppet upload the file to the directory, with the dependency set that the directory must exist. Therequirestatement refers to the previously defined block.

We also see that Puppet has out-of-the-box support for SELinux type definitions. The file class has several SELinux-supported parameters that can be used:

  • seluserdefines the SELinux user for the resource.
  • selroledefines the SELinux role for the resource.
  • seltypedefines the SELinux type for the resource.
  • selrangedefines the SELinux sensitivity range for the resource.
  • selinux_ignore_defaultstells Puppet to ignore the default SELinux context (as queried from the SELinux policy).

Our previous example is thus actually superfluous because Puppet will actively query the SELinux policy to discover what the right resource context is and apply this. With selinux_ignore_defaults set to true, Puppet will not query and adjust the context accordingly, which can be useful when testing out new setups that do not have proper context definitions set.

Loading custom SELinux policies with Puppet

Puppetdoes have support for loading and managing SELinux modules. However, its support is currently restricted to the more traditional SELinux policy modules, and not the CIL powered ones.

So, let’s create another block in our module definition that loads thetest.cilfile, but only if no test SELinux module is already loaded:

exec { '/usr/sbin/semodule -i /usr/share/selinux/custom/test.cil':
  require => File['selinux_custom_module_test'],
  unless => '/usr/sbin/semodule -l | grep -q ^test$',
}

This approach allows us to create custom SELinux configuration adjustments if the native Puppet support does not suffice.

Using Puppet’s out-of-the-box SELinux support

Puppethas a few SELinux-related classes supported out of the box but has more support through Puppet Forge, an ecosystem of community-contributed modules. One of the modules that we can recommend is thepuppet-selinuxmodule, which Puppet (the company) maintains on Puppet Forge (and thus has a higher chance of remaining supported in later versions of Puppet).

Installing new modules is quite easy, using thepuppet modulecommand:

master ~# /opt/puppetlabs/bin/puppet module install puppet-selinux

We can then refer to the selinux class (provided through this module) within our manifest:

  • The selinux class can be directly used to set the enforcing (or permissive) state of the system:
class { selinux:
  mode => 'enforcing',
  type => 'targeted',
}
  • The (native) selboolean class can be used to set SELinux booleans:
selboolean { 'httpd_builtin_scripting':
  value => off,
}
  • SELinux file contexts can be defined using the selinux::fcontext class:
selinux::fcontext { '/srv/web(/.*)?':
  seltype => 'httpd_sys_content_t',
}
  • Equivalence definitions for the file context are handled by selinux::fcontext::equivalence, like so:
selinux::fcontext::equivalence { '/srv/www':
  ensure => 'present',
  target => '/srv/web',
}
  • Custom port mappings are handled by selinux::port:
selinux::port { 'set_ssh_custom_port':
  ensure => 'present',
  seltype => 'ssh_port_t',
  protocol => 'tcp',
  port => 10122,
}
  • Individual SELinux domains can be made permissive using selinux::permissive:
selinux::permissive { 'zoneminder_t':
  ensure => 'present',
}
  • If standard SELinux modules are present, the use of selmodule allows loading it up. In this case, it will search for the SELinux module named after the block, inside the directory referred to by selmoduledir:
selmodule { 'vlock':
  ensure => 'present',
  selmoduledir => '/usr/share/selinux/custom',
}

While other SELinux-supporting modules might be available on Puppet Forge, be sure to validate whether these modules are mature and sufficiently stable. If their support is uncertain, you might want to pursue the exec route, as used earlier on, in Loading custom SELinux policies with Puppet


Back to Featured Articles on Logo Paperblog