Notes on installing Puppet6 on Ubuntu 18.04

This is a compilation of notes that leads to an installation of a Puppet server on an Ubuntu server 18.04.

A node with a Puppet agent was created on an Ubuntu server 18.04.

Examples in this guide don’t care about OSes, everything is done to work on Ubuntu.

These notes were heavily inspired by various articles and documentation pages, notably:

Virtual machines

Links:

Use snapshots to save your work (i.e: when a new puppet configuration is successful). It’s quicker than a reinstall.

Puppet server

Virtual hardware:

OS:

Puppet node

Virtual hardware:

OS:

Preparation

Links:

Once the VMs are ready and booted, configure Netplan to have static IP addresses on the private host network.

I used 192.168.56.5 for Puppet server and 192.168.56.6 for the puppet node

# sudo vim /etc/netplan/50-cloud-init.yaml

network:
  ethernets:
    enp0s3:
      dhcp4: true
    enp0s8: # Interface connected to the private host network
      dhcp4: no
      addresses: [192.168.56.6/24]
  version: 2

Apply configuration:

netplan try
# If it's fine, validate to save the changes

Add hosts to your ~/.ssh/config:

# Puppet-server
host puppet
  hostname 192.168.56.5
  user <the_ubuntu_user>
  forwardagent yes

# Puppet-node, obviously
host puppet-node
  hostname 192.168.56.6
  user <the_ubuntu_user>
  forwardagent yes

Use forwardagent yes to be able to connect to your outside stuff (i.e.: git repos)

You should be able to access your two virtual machines using SSH from the host:

ssh puppet
ssh puppet-node

/etc/hosts

The first time I used Puppet server, I had certificates issues, so I had to specify the Puppet server host in the two VMs host file:

192.168.56.5 puppet puppet.lan

Puppet-release

Links:

For both server and node, you will need to activate the “puppet release” package sources.

On ubuntu 18.04 server, the “multiverse” repository is already active, in other cases, activate it:

sudo apt-add-repository multiverse && sudo apt-get update

Install Puppet Release

wget https://apt.puppet.com/puppet6-release-bionic.deb
sudo dpkg -i puppet6-release-bionic.deb # Adds source in sources.list.d

sudo apt update

Note: If the download is stuck when using IPv6, force IPv4: use --inet4-only

Install puppet server

Links:

Install the server:

sudo apt install puppetserver

You need a root and intermediate signing CA for Puppet Server, in order to identify it to the nodes:

sudo /opt/puppetlabs/server/bin/puppetserver ca setup

Start the service

service puppetserver start

Configure

Links:

Puppet server needs 2Gb of memory; for testing purpose you can tweak it down (check this section of the documentation).

For other options, check the documentation, this is not the point of these notes.

Puppet agent

You should have installed the puppet-release and activated the “multiverse” repository.

Installing the agent

Links:

sudo apt install puppet-agent

# Start the service
sudo /opt/puppetlabs/bin/puppet resource service puppet ensure=running enable=true

Connect the first agent to Puppet server

Try to connect to puppet server:

sudo /opt/puppetlabs/bin/puppet agent --test

# [...]
# Couldn't fetch certificate from CA server; you might still need to sign this agent's certificate (puppet-agent). Exiting now because the waitforcert setting is set to 0.

The agent contacted Puppet server, but the server don’t know it. It should sign its certificate first to add it to its list of managed nodes.

On the master, list certificates:

sudo /opt/puppetlabs/bin/puppetserver ca list
# Requested Certificates:
#    puppet-node.lan   (SHA256)  FC:20:C6:A9:12:F4:40:4D:A6:A3:74:E8:5B:19:84:0A:54:33:16:C9:7A:C0:18:52:75:C9:94:E0:8A:61:29:95

Then, once the SHA is verified, sign the certificate:

sudo /opt/puppetlabs/bin/puppetserver ca sign --certname puppet-node.lan

If you work in a closed environment in which you’re sure about the machines you’re going to certify, you may want to sign all the pending ones in a batch:

sudo /opt/puppetlabs/bin/puppetserver ca sign --all

Re launch the agent on the node and you’re done: Puppet agent fetches the catalog from Puppet server and applies it (does nothing right now).

Now, we have a very basic Puppet server and a node, which is linked to it.

Notes on certificates

Remove the agent’s certificates if you messed up (I generated mine forgotting to sudo…):

# On the node:
sudo /opt/puppetlabs/bin/puppet ssl --localca clean

# On the server:
sudo /opt/puppetlabs/bin/puppetserver ca list --all sudo /opt/puppetlabs/bin/puppetserver ca clean --certname puppet-node

First node configuration

Here comes the fun: we’re going to write a manifest file to configure our nodes !

Puppet environments are different configurations for your conformation system: you may want to test things before applying it on the nodes.

The default environment is “production”, and we’ll use it for now.

Files are located under /etc/puppetlab/code/environments

Edit /etc/puppetlab/code/environments/production/manifests/site.pp

Message of the day

Links:

A simple thing we can try to have quick results is to change the message of the day (MOTD), which is displayed when we login on a node.

The MOTD used to be a single file in /etc/motd, but Ubuntu came in and now we have a message of the day composed of fragments of messages, scattered across multiple files in /etc/update-motd.d/:

/etc/update-motd.d/
|-- 00-header
|-- 10-help-text
|-- 50-landscape-sysinfo -> /usr/share/landscape/landscape-sysinfo.wrapper
|-- 50-motd-news
|-- 80-esm
|-- 80-livepatch
|-- 90-updates-available
|-- 91-release-upgrade
|-- 95-hwe-eol
|-- 97-overlayroot
|-- 98-fsck-at-reboot
`-- 98-reboot-required

We’ll change the header to tell user that the node is managed by Puppet.

# resource "file"
file { '/etc/update-motd.d/00-header':
  ensure  => file,
  owner   => root,
  group   => root,
  mode    => '0755', # file has mode rwxr-xr-x
  content => "#!/bin/sh\nprintf \"This is ${facts['fqdn']}\n\n\"printf \"This node is managed by Puppet.\"\n",
}

What will it do ? Well, Puppet will ensure that file or folder /etc/update-motd.d/00-header' exists and is a file, owned by root:root, with given mode and content.

Save the file and run the agent on the node:

sudo /opt/puppetlabs/bin/puppet agent --test --noop

Note the --noop option: the changes will not be applied. If no errors shows up, run the command again, without the “noop” option.

Base packages

Of all the things we want to automate, the system packages are a good start too. You can add packages or remove them with something like:

# We don't want cloud-ini
package { 'cloud-init':
  ensure => purged
}
package { 'tree':
  ensure => installed,
}
package { 'vim':
  ensure => installed,
}
package { 'fail2ban':
  ensure => installed,
}
package { 'htop':
  ensure => installed,
}
# ...

As before, run with --noop first.

Modules: SSH configuration

Links:

Puppet modules are classes meant to do something. You can find existing modules on the Puppet forge, and it goes from managing SSH configuration to setup a GitLab instance.

We’ll focus on the SSH configuration, in order to:

The first thing to do is to choose a module in the 470+ SSH modules from the Forge. I was told by a trusted source that the one from Zach Leslie is a good start.

Tell Puppet to use the module. On the server:

sudo /opt/puppetlabs/bin/puppet module install zleslie-ssh --version 1.2.0

Edit site.pp and add the following configuration:

# SSH configuration
class { 'ssh::server::config':
  authenticationmethods  => 'publickey',
  ciphers                => 'chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr',
  kexalgorithms          => 'curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256',
  log_level              => 'VERBOSE',
  macs                   => 'hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com',
  passwordauthentication => 'no',
  permitrootlogin        => 'no',
}

# Allowed groups
ssh::allowgroup { 'adm': }

# Activate the server
class { 'ssh::server':
}

If we don’t want to be able to login, we have to add the allowed SSH keys to the user before applying anything.

Users

You may want to configure users on all your systems, allowing them to login with their SSH keys:

user { 'johndoe':
  ensure         => present,
  groups         => [
    'adm',
    'sudo',
  ],
  shell          => '/bin/bash',
  home           => '/home/johndoe',
  managehome     => true,
  password       => '*',
  purge_ssh_keys => true, # We only want the keys we declare
}

$ssh_key      = 'ssh-rsa SOMELONGSTRING johndoe@example.com'
$ssh_elements = $ssh_key.split(' ')

# The key will be added, allowing us to login, as the SSH policy is "publickey" only
ssh_authorized_key { $ssh_elements[2]:
  ensure => present,
  user   => 'johndoe',
  type   => $ssh_elements[0],
  key    => $ssh_elements[1],
}

In this example, we used variables and functions (split), which is nice.

Now you can apply the configuration on the node. Logout and login to see how it’s working.

Per-node configuration

We applied the configuration on the puppet-node virtual machine, but we may want to apply it to the Puppet server too.

# On puppet-server:
sudo /opt/puppetlabs/bin/puppet agent --test

Now, all our nodes have the same configuration. But what if we wanted to have specific elements on a specific node ?

We can define a per-node configuration in site.pp:

# Global configuration
# ...

# Specific node
node puppet-node {
  package { 'cowsays':
    ensure => installed,
  }
}

# Specific node
node puppet {}

Conclusion

We learned how to set up a Puppet server to manage hosts with shared and custom configuration.

I encourage you to play with a few nodes, document yourself on the Puppet server configuration.

What’s next?