Book Image

Learning Puppet Security

Book Image

Learning Puppet Security

Overview of this book

Table of Contents (17 chapters)
Learning Puppet Security
About the Author
About the Reviewers

Example – using Puppet to secure openssh

Now that we've got the system set up for our use, we can finally approach the main example for this chapter. In this example, we're going to use what has traditionally been one of the first things used to show off Puppet and install SSH. However, in this case, we're going to use a hardened configuration utilizing some options recommended by the security community.

The example of securing SSH is one that we will return to several times in this book as we expand upon our configuration management toolkit and branch out into things such as firewall management.

Starting the Vagrant virtual machine

Since this is our first time using Vagrant, we'll cover how to start a virtual machine. In the directory with the Vagrantfile, run the following command:

vagrant up

Once this is done, you'll see the output from Vagrant indicating the actions it's taking, as well as output from the commands it runs—this includes the Shell provisioner and the Puppet provisioner. When it's done, you'll end up with something that is similar to the following:

You'll notice some warnings on the screen here. These are options that are changing with the newer version of Puppet. Our manifest could add an allow_virtual setting to get rid of the second warning. The first warning, however, is a result of how Vagrant is calling Puppet.

Connecting to our virtual machine

Once your machine has booted, simply issue the following command to connect:

vagrant ssh

This will connect you to the machine using ssh. Once this is complete, we can start working on our module.

Creating the module

We'll be using a Puppet module to secure SSH. As such, we should go ahead and create the directory to hold our module. You can issue the following commands to create the module skeleton on the guest virtual machine:

sudo mkdir –p /etc/puppet/modules/openssh/manifests
sudo mkdir –p /etc/puppet/modules/openssh/files

These directories will hold the manifests for Puppet to compile as well as our configuration file. For our first simplistic example, we will use a static SSH configuration file. In later chapters, we will build upon it and make it dynamic with the various options that are available.


It's also possible to make the /etc/puppet/modules/openssh directory a symlink to a directory in /vagrant. If you create the directory in /vagrant, you can use any editor on your host system to edit the files and have it immediately available in the guest. This saves you the trouble of having to configure a good editing environment on the guest machine.

Building the module

Now that we have the framework, we'll build our first module. Much like the preceding code, we'll go through it section by section covering what each resource does. The manifest we're building will be very similar to the one we used to provision the Puppet Master for the use of.

First, we'll edit the /etc/puppet/modules/openssh/manifests/init.pp file to create the module's main manifest. This manifest is the main unit of the Puppet code, which is invoked when we include the module. As we go through each of the sections, we'll go through what they do. A complete manifest file can be found on this book's website, but you should really build it along with us. This will help you with understanding and memorization:

class openssh {

The preceding line defines the class. The class in the init.pp file is always named after the module. It's a new construct we've not seen before that is unique to creating modules:

  package { 'openssh-server':
    ensure => 'latest',

The preceding section is similar to the puppetmaster section. The only difference is that we're using latest instead of present. Being a security-related package, it may make sense to make sure that you keep openssh up to date.

Alternatively, if your environment requires it, you could specify a fixed version to install. This might be useful if you require pretested versions or have validated versions. You must weigh the benefits, ensuring that you run the most recent version of the software, including the risk of almost immediately installing it when it is available, and that you're using the latest tag:

  file { '/etc/ssh/sshd_config':
    ensure => 'present',
    owner  => 'root',
    group  => 'root',
    mode   => '0600',
    source => 'puppet:///modules/openssh/sshd_config',


As your Puppet code becomes more complex, care must be taken on how you name your files inside your module. It can sometimes be useful to create the full path to the file under the modules directory, so there is no confusion as to the destination of the time. We omit these here only because our modules are simple, and it makes the examples easier to follow.

This is similar to the Puppet Master configuration file, but we introduced a new construct here. We're sourcing the file from puppet master by using the special puppet:// uniform resource identifier (URL). When Puppet runs, it will fetch the file from the master for use on the agent. The source file should be present in the /etc/puppet/modules/openssh/files directory on the master:

  service { 'sshd':
    ensure => 'running',

Here, as before, we ensure that ssh is running when we run Puppet:

  -> File['/etc/ssh/sshd_config']
  ~> Service['sshd']

This is also a new construct called resource chaining. It is an alternative way to specify that we do things in the order listed: first, the package, followed by the file, and then the service. Note the tilde on the service dependency. This shows that we're notifying the service. It means that if the configuration file changes, the service will be restarted.


In a declarative system, there needs to be a way to ensure that things are run in the correct order. One of the more difficult things for new Puppet users is to grasp the concept that their manifests don't necessarily run in a top-down order. This concept is so hard that in recent versions of Puppet, the default has been changed to a process in the manifest order by default. More information on resource ordering and this change can be found at

The openssh configuration file

To build the configuration file we're going to use, we'll start with the openssh configuration file shipped with CentOS and make a few changes. First, we'll copy the existing configuration file with the following command:

sudo cp /etc/ssh/sshd_config /etc/puppet/modules/openssh/files/

Next, we'll edit the file with your favorite editor. Make sure you run it in sudo as you won't have permission to edit the file. We'll uncomment and change the following lines in the file:

PermitRootLogin no
MaxAuthTries 3

We'll start with these changes to demonstrate how the process works. Then, save the file.

Next, we need to make sure the Puppet agent can read it. We'll set the permissions in such a manner that the Puppet user can read it. Execute the following:

sudo chgrp puppet /etc/puppet/modules/openssh/files/sshd_config
sudo chmod 640 /etc/puppet/modules/openssh/files/sshd_config

The site.pp file

Now, we need to bring it all together to tell Puppet to use our module. By default, Puppet runs a file called site.pp on the master to determine what actions to take when a node checks in. We need to add the new module to the file for Puppet to run it.

The file lives in /etc/puppet/manifests on our Vagrant guest. Go ahead and open it in your favorite editor and add the following section:

node default {
  include openssh

This adds a default node declaration and includes our openssh module on that node. It will ensure that our new module gets used.

Running our new code

Now that we've got it all built, let's go ahead and see the fruits of our labor. Execute the following command:

sudo puppet agent --test

You should see the output as follows:


If you're running these examples outside Vagrant, you will have a bit more work to do. We're using Vagrant to set our hostname to Puppet, and the master by default has its own certificate signed. If you are running without Vagrant, you will need to add a host file entry or DNS pointing to your master, and you may need to sign the certificate. We'll cover certificate singing in Chapter 5, Securing Puppet.

Victory! You can see that Puppet changed the file to disallow root logins and change the maximum authentication attempts to 3.

As with any new technology, the learning curve can seem somewhat overwhelming at first. We've now gone through a rather lengthy example to effectively make a two-line edit to a configuration file on a single machine. This was a short and simple example to explore some base concepts of Puppet. Using this concept, we could apply this same edit to hundreds or even thousands of machines in our infrastructure with very little additional effort. We'll also be exploring more in-depth examples as we gain a skillset. With some practice, you will find that applying changes across one of many machines becomes second nature with Puppet.