Install and Configure Puppet
Updated by Linode Written by Elle Krout
DeprecatedThis guide has been deprecated and is no longer being maintained.Please refer to the updated version of this guide.
Puppet is a configuration automation platform that simplifies various system administrator tasks. Puppet uses a client/server model where the managed servers, called Puppet agents, talk to and pull down configuration profiles from the Puppet master.
Puppet is written in its own custom language, meant to be accessible to system administrators. A module, located on the Puppet master, describes the desired system. The Puppet software then translates the module into code and alters the agent servers as needed when the puppet agent
command is run on an agent node or automatically at designated intervals.
Puppet can be used to manage multiple servers across various infrastructures, from a group of personal servers up to an enterprise level operation. It is intended to run on Linux and other Unix-like operating systems, but has also been ported to Windows. For the purpose of this guide, however, we will be working with an Ubuntu 16.04 LTS master server and two agent nodes: one Ubuntu 16.04, and one CentOS 7.
NoteBegin this guide as theroot
user. A limited user with administrative privileges will be configured in later steps.
Before You Begin
You should have three available Linodes, one of which has at least four CPU cores for the Puppet master. A Linode 8GB plan is recommended. The two other nodes can be of any plan size, depending on how you intend to use them after Puppet is installed and configured.
Follow the Getting Started guide and ensure your Linodes are configured to use the same timezone.
Note
For ease of use, set the Puppet master server’s hostname to
puppet
, and have a valid fully-qualified domain name (FQDN).To check your hostname, run
hostname
and to check your FQDN, runhostname -f
.
Puppet Master
Install Puppet Master
Install the
puppetlabs-release
repository into Ubuntu 16.04 and update your system. This process downloads a.deb
file that will configure the repositories for you:wget https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb dpkg -i puppetlabs-release-pc1-xenial.deb apt update
Note
If you wish to run another Linux distribution as your master server, the initial
.deb
file can be substituted for another distribution based on the following formats:Red Hat-based systems:
wget https://yum.puppetlabs.com/puppetlabs-release-pc1-OS-VERSION.noarch.rpm
Debian-based systems:
wget https://apt.puppetlabs.com/puppetlabs-release-pc1-VERSION.deb
Any Ubuntu-specific commands will then have to be amended for the proper distribution. More information can be found in Puppet’s Installation Documentation or our guide to package management.
Install the
puppetmaster-passenger
package:apt install puppetmaster-passenger
The default Puppet installation may start Apache and configure it to listen on the same port as Puppet. Stop Apache to avoid this conflict (if using CentOS7, change
apache2
in this example tohttpd
):systemctl stop apache2
Ensure you have the latest version of Puppet by running:
puppet resource package puppetmaster ensure=latest
Configure Puppet Master
Update
/etc/puppet/puppet.conf
and add thedns_alt_names
line to the section[main]
, replacingpuppet.example.com
with your own FQDN:- /etc/puppet/puppet.conf
-
1 2
[main] dns_alt_names=puppet,puppet.example.com
Start the Puppet master:
systemctl start puppetmaster
By default, the Puppet master process listens for client connections on port 8140. If the
puppetmaster
service fails to start, check that the port is not already in use:netstat -anpl | grep 8140
Puppet Agents
Install Puppet Agent
On agent nodes running Ubuntu 16.04 or other Debian-based distributions, use this command to install Puppet:
apt install puppet
On agent nodes running CentOS 7 or other Red Hat systems, follow these steps:
For CentOS 7 only, add the Puppet Labs repository:
rpm -ivh https://yum.puppetlabs.com/el/7/products/x86_64/puppetlabs-release-22.0-2.noarch.rpm
Note
If you’re on a Red Hat system other than CentOS 7, skip this step.Install the Puppet agent:
yum install puppet
Configure Puppet Agent
Modify your Puppet Agent’s host file to resolve the Puppet master IP as
puppet
:- /etc/hosts
-
1
198.51.100.0 puppet
Add the
server
value to the[main]
section of the node’spuppet.conf
file, replacingpuppet.example.com
with the FQDN of your Puppet master:- /etc/puppet/puppet.conf
-
1 2
[main] server=puppet.example.com
Restart the Puppet service:
systemctl restart puppet
Generate and Sign Certificates
On each agent, generate a certificate for the Puppet master to sign:
puppet agent -t
This will output an error, stating that no certificate has been found. This error is because the generated certificate needs to be approved by the Puppet master.
Log in to your Puppet master and list the certificates that need approval:
puppet cert list
It should output a list with your agent node’s hostname.
Approve the certificates, replacing
hostname.example.com
with the hostname of each agent node:puppet cert sign hostname.example.com
Return to the Puppet agent nodes and run the Puppet agent again:
puppet agent -t
Add Modules to Configure Agent Nodes
Both the Puppet master and agent nodes configured above are functional, but not secure. Based on concepts from the Securing Your Server guide, a limited user and a firewall should be configured. This can be done on all nodes through the creation of basic Puppet modules, shown below.
NoteThis is not meant to provide a basis for a fully-hardened server, and is intended only as a starting point. Alter and add firewall rules and other configuration options, depending on your specific needs.
Add a Limited User
From the Puppet master, navigate to the
/etc/puppet/modules
directory and create your new module for adding user accounts, thencd
into that directory:mkdir /etc/puppet/modules/accounts cd /etc/puppet/modules/accounts
Create the following directories, which are needed to have a functioning module:
mkdir {examples,files,manifests,templates}
The
examples
directory allows you to test the module locally.files
contains any static files that may need to be edited or added.manifests
contains the actual Puppet code for the module, andtemplates
contains any non-static files that may be needed.Move to the
manifests
directory and create your first class, calledinit.pp
. All modules require aninit.pp
file to be used as the main definition file of a module.cd manifests
Within the
init.pp
file, define a limited user to use instead ofroot
, replacing all instances ofusername
with your chosen username:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5 6 7 8 9 10 11
class accounts { user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', } }
The
init.pp
file initially defines theaccounts
class. It then calls for theuser
resource, where ausername
is defined. Theensure
value is set to ensure that the user exists (is present). Thehome
value should be set to the user’s home directory path.shell
defines the shell type, in this instance the bash shell.managehome
notes that the home directory should be created. Finally,gid
sets the primary group for the user.Although the primary group is set to share the username, the group itself has not been created. Save and exit
init.pp
. Then, create a new file calledgroups.pp
and add the following contents. This file will be used to create the user’s group. Again, replaceusername
with your chosen username:- /etc/puppet/modules/accounts/manifests/groups.pp
-
1 2 3 4 5 6 7
class accounts::groups { group { 'username': ensure => present, } }
Include this file by adding
include groups
to theinit.pp
file, within theaccounts
class:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5
class accounts { include groups ... }
This user should have privileges so that administrative tasks can be performed. Because we have agent nodes on both Debian- and Red Hat-based systems, the new user needs to be in the
sudo
group on Debian systems, and thewheel
group on Red Hat systems. This value can be set dynamically through the use of a selector and facter, a program included in Puppet that keeps track of information, or facts, about every server. Add a selector statement to the top of theinit.pp
file within the accounts class brackets, defining the two options:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5 6 7 8 9 10 11 12
class accounts { $rootgroup = $osfamily ? { 'Debian' => 'sudo', 'RedHat' => 'wheel', default => warning('This distribution is not supported by the Accounts module'), } user { 'username': ... }
This command sequence tells Puppet that within the accounts module the variable
$rootgroup
should evaluate, using facter, the operating system family ($osfamily
), and if the value returned isDebian
, to set the$rootgroup
value tosudo
. If the value returned isRedHat
, this same value should be set towheel
; otherwise, thedefault
value will output a warning that the distribution selected is not supported by this module.Note
Theuser
definition will include the$rootgroup
, and the Puppet Configuration Language executes code from top to bottom. You must define the$rootgroup
before theuser
so that it can be accessed.Add the
groups
value to the user resource, calling to the$rootgroup
variable defined in the previous step:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5 6 7 8
user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", }
The value
"$rootgroup"
is enclosed in double quotes (“) instead of single quotes (‘) because it is a variable. Any value enclosed within single quotes will be added as typed in your module; anything enclosed in double quotes can accept variables.The final value that needs to be added is the
password
. Since we do not want to use plain text, it should be fed to Puppet as a SHA1 digest, which is supported by default. Set a password from the terminal:openssl passwd -1
You will be prompted to enter your password and confirm. A hashed password will be output. This should then be copied and added to the
user
resource:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13
class accounts { user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", password => '$1$07JUIM1HJKDSWm8.NJOqsP.blweQ..3L0', } }
Caution
The hashed password must be included in single quotes (‘).After saving your changes, use the puppet parser to ensure that the code is correct:
puppet parser validate init.pp
Any errors that need to be addressed will be logged to standard output. If nothing is returned, your code is valid.
Before the module can be tested further, navigate to the
examples
directory and create anotherinit.pp
file, this time to call to theaccounts
module:cd ../examples
- /etc/puppet/modules/accounts/examples/init.pp
-
1
include accounts
After adding this line, save and exit the file.
While still in the
examples
directory, test the module without making changes:puppet apply --noop init.pp
Note
The--noop
parameter prevents Puppet from actually running the module.It should return:
Notice: Compiled catalog for puppet.example.com in environment production in 0.26 seconds Notice: /Stage[main]/Accounts::Groups/Group[username]/ensure: current_value absent, should be present (noop) Notice: Class[Accounts::Groups]: Would have triggered 'refresh' from 1 events Notice: /Stage[main]/Accounts/User[username]/ensure: current_value absent, should be present (noop) Notice: Class[Accounts]: Would have triggered 'refresh' from 1 events Notice: Stage[main]: Would have triggered 'refresh' from 2 events Notice: Finished catalog run in 0.02 seconds
Again from the
examples
directory, runpuppet apply
to make these changes to the Puppet master server:puppet apply init.pp
Log out as
root
and log in to the Puppet master as your new user. The rest of this guide will be run by this user.
Edit SSH Settings
Although a new user has successfully been added to the Puppet master, the account is still not secure. Root access should be disabled for the server before continuing.
Navigate to
files
within theaccount
module directory:cd /etc/puppet/modules/accounts/files
Copy the
sshd_config
file to this directory:sudo cp /etc/ssh/sshd_config .
Open the file with
sudo
, and set thePermitRootLogin
value tono
:- /etc/puppet/modules/accounts/files/sshd_config
-
1
PermitRootLogin no
Navigate back to the
manifests
directory and, usingsudo
, create a file calledssh.pp
. Use thefile
resource to replace the default configuration file with the one managed by Puppet:cd ../manifests
- /etc/puppet/modules/accounts/manifests/ssh.pp
-
1 2 3 4 5 6 7 8
class accounts::ssh { file { '/etc/ssh/sshd_config': ensure => present, source => 'puppet:///modules/accounts/sshd_config', } }
Note
Thefile
directory is omitted from thesource
line because thefiles
folder is the default location of files. For more information on the format used to access resources in a module, refer to the official Puppet module documentation.Create a second resource to restart the SSH service and set it to run whenever
sshd_config
is changed. This will also require a selector statement because the SSH service is calledssh
on Debian systems andsshd
on Red Hat:- /etc/puppet/modules/accounts/manifests/ssh.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class accounts::ssh { $sshname = $osfamily ? { 'Debian' => 'ssh', 'RedHat' => 'sshd', default => warning('This distribution is not supported by the Accounts module'), } file { '/etc/ssh/sshd_config': ensure => present, source => 'puppet:///modules/accounts/sshd_config', notify => Service["$sshname"], } service { "$sshname": hasrestart => true, } }
Include the
ssh
class withininit.pp
:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5
class accounts { include groups include ssh ...
Your complete
init.pp
will look similar to this:- /etc/puppet/modules/accounts/manifests/init.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class accounts { include groups include ssh $rootgroup = $osfamily ? { 'Debian' => 'sudo', 'RedHat' => 'wheel', default => warning('This distro not supported by Accounts module'), } user { 'example': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", password => '$1$07JUIM1HJKDSWm8.NJOqsP.blweQ..3L0' } }
Run the Puppet parser, then navigate to the
examples
directory to test and runpuppet apply
:sudo puppet parser validate ssh.pp cd ../examples sudo puppet apply --noop init.pp sudo puppet apply init.pp
Note
You may see the following line in your output when validating:
Error: Removing mount "files": /etc/puppet/files does not exist or is not a directory
This refers to a Puppet configuration file, not the module resource you’re trying to copy. If this is the only error in your output, the operation should still succeed.
To ensure that the
ssh
class is working properly, log out and then try to log in asroot
. You should not be able to do so.
Add and Configure IPtables
In this section, we’ll configure firewall rules using iptables
. However, these rules will not persist across reboots by default. To avoid this, install the appropriate package on each node (both master and agents) before proceeding:
Ubuntu/Debian:
sudo apt install iptables-persistent
CentOS 7:
CentOS 7 uses firewalld by default as a controller for iptables. Be sure firewalld is stopped and disabled before starting to work directly with iptables:
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo yum install iptables-services
On your Puppet master node, install Puppet Lab’s firewall module from the Puppet Forge:
sudo puppet module install puppetlabs-firewall
The module will be installed in your
/etc/puppet/modules
directory.Navigate to the
manifests
directory under the newfirewall
module:cd /etc/puppet/modules/firewall/manifests/
Create a file titled
pre.pp
, which will contain all basic networking rules that should be run first:- /etc/puppet/modules/firewall/manifests/pre.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
class firewall::pre { Firewall { require => undef, } # Accept all loopback traffic firewall { '000 lo traffic': proto => 'all', iniface => 'lo', action => 'accept', }-> #Drop non-loopback traffic firewall { '001 reject non-lo': proto => 'all', iniface => '! lo', destination => '127.0.0.0/8', action => 'reject', }-> #Accept established inbound connections firewall { '002 accept established': proto => 'all', state => ['RELATED', 'ESTABLISHED'], action => 'accept', }-> #Allow all outbound traffic firewall { '003 allow outbound': chain => 'OUTPUT', action => 'accept', }-> #Allow ICMP/ping firewall { '004 allow icmp': proto => 'icmp', action => 'accept', } #Allow SSH connections firewall { '005 Allow SSH': dport => '22', proto => 'tcp', action => 'accept', }-> #Allow HTTP/HTTPS connections firewall { '006 HTTP/HTTPS connections': dport => ['80', '443'], proto => 'tcp', action => 'accept', } }
Each rule is explained via commented text. More information can also be found on the Puppet Forge Firewall page.
In the same directory create
post.pp
, which will run any firewall rules that need to be input last:- /etc/puppet/modules/firewall/manifests/post.pp
-
1 2 3 4 5 6 7 8 9
class firewall::post { firewall { '999 drop all': proto => 'all', action => 'drop', before => undef, } }
These rules will direct the system to drop all inbound traffic that is not already permitted in the firewall.
Run the Puppet parser on both files to ensure the code does not return any errors:
sudo puppet parser validate pre.pp sudo puppet parser validate post.pp
Move up a directory, create a new
examples
directory, and navigate to it:cd .. sudo mkdir examples cd examples
Within
examples
, create aninit.pp
file to test the firewall on the Puppet master:- /etc/puppet/modules/firewall/examples/init.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } firewall { '200 Allow Puppet Master': dport => '8140', proto => 'tcp', action => 'accept', }
This code block ensures that
pre.pp
andpost.pp
run properly, and adds a firewall rule to the Puppet master to allow nodes to access it.Run the
init.pp
file through the Puppet parser and then test to see if it will run:sudo puppet parser validate init.pp sudo puppet apply --noop init.pp
If successful, run the
puppet apply
without the--noop
option:sudo puppet apply init.pp
Once Puppet is done running, check the iptables rules:
sudo iptables -L
It should return:
Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- anywhere anywhere /* 000 lo traffic */ REJECT all -- anywhere 127.0.0.0/8 /* 001 reject non-lo */ reject-with icmp-port-unreachable ACCEPT all -- anywhere anywhere /* 002 accept established */ state RELATED,ESTABLISHED ACCEPT icmp -- anywhere anywhere /* 004 allow icmp */ ACCEPT tcp -- anywhere anywhere multiport ports ssh /* 005 Allow SSH */ ACCEPT tcp -- anywhere anywhere multiport ports http,https /* 006 HTTP/HTTPS connections */ ACCEPT tcp -- anywhere anywhere multiport ports 8140 /* 200 Allow Puppet Master */ DROP all -- anywhere anywhere /* 999 drop all */ Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination ACCEPT tcp -- anywhere anywhere /* 003 allow outbound */
Add Modules to the Agent Nodes
Now that the accounts
and firewall
modules have been created, tested, and run on the Puppet master, it is time to add them to the Puppet agent nodes created earlier.
From the Puppet master, navigate to
/etc/puppet/manifests
.cd /etc/puppet/manifests
List all available agent nodes:
sudo puppet cert list -all
Create the file
site.pp
to define which nodes will take what modules. Replaceubuntuagent.example.com
andcentosagent.example.com
with the FQDNs of your agent nodes:- /etc/puppet/manifests/site.pp
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 'ubuntuagent.example.com' { include accounts resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } } node 'centosagent.example.com' { include accounts resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } }
This includes the
accounts
module and uses the same firewall settings as above to ensure that the firewall rules are applied properly.On each Puppet agent node, enable the
puppet agent
command:puppet agent --enable
Run the Puppet agent:
puppet agent -t
To ensure the Puppet agent worked, log in as the limited user that was created and check the iptables:
sudo iptables -L
Congratulations! You’ve successfully installed Puppet on a master and two agent nodes. Now that you’ve confirmed everything is working, you can create additional modules to automate configuration management on your agent nodes. For more information, see Puppet module fundamentals. You can also install and use those modules others have created on the Puppet Forge.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
Join our Community
Find answers, ask questions, and help others.
This guide is published under a CC BY-ND 4.0 license.