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
andpdk
(on the master) andpuppet-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 namedinit.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 calledsite.pp
with the following content:
node 'rem1.internal.genfic.local' {
include packt_selinux
}
Thesite.pp
file provides the top-level hierarchy for Puppet to associate its environmentwith the appropriate definitions. In this example, the node with the hostnamerem1.internal.genfic.local
is configured through a reference topackt_selinux
, the module we created previously.
Inside thepackt_selinux
module, we’ve created thepackt_selinux
class, 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.cil
SELinux module in thefiles/
folder inside thepackt_selinux
module location. This block will have Puppet upload the file to the directory, with the dependency set that the directory must exist. Therequire
statement 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:
seluser
defines the SELinux user for the resource.selrole
defines the SELinux role for the resource.seltype
defines the SELinux type for the resource.selrange
defines the SELinux sensitivity range for the resource.selinux_ignore_defaults
tells 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.cil
file, 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-selinux
module, 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 module
command:
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 byselmoduledir
:
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