Letsencrypt with Apache and Puppet

This week I set myself the task of getting my various SSL site under Puppet control. Like all the cool kids I use letsencrypt for these certs. I also want to get all the information from Hiera. There are a small collection of modules you need:

If you're using R10K (which you should) then add the following to your Puppetfile:

mod 'puppetlabs/apache', '1.4.1'
mod 'danzilio/letsencrypt', '1.0.0'
mod 'stahnma/epel'
mod 'hiera_resources',
  :git => 'https://github.com/rnelson0/puppet-hiera_resources.git'         

We need to do 3 things (in the right order):

  1. Create a non-SSL Virtualhost
    • DNS must already point to the host
    • Will redirect to the SSL Virtualhost
    • Must not redirect the Letsencrypt acme-challenge
    • The first time, this will need to respond via the default virtualhost
  2. Generate a certificate
  3. Create the SSL Virtualhost using this certificate

I cannot tell you how to do the DNS, but the rest we can do with Puppet.

I put all this in a dedicated local module. The only thing local about it is that it is not really useful for anyone other me. It is available though if you want to have a look at it. What this allows me to do is define my SSL and non-SSL vhosts seperately in Hiera, and also define my certs in hiera. I can then create relationships between them to define the order.

So, my site.pp will contain (lablocal is that local module):

class { 'lablocal::nonsslvhosts': }->
class { 'lablocal::letsencryptcerts': }->                                 
class { 'lablocal::sslvhosts': }

That references 3 classes in the lablocal module:


class lablocal::nonsslvhosts {


class lablocal::letsencryptcerts {


class lablocal::sslvhosts {

None of that is very complicated. All of the clever stuff is happenning in the Hiera file for that node:

  - apache
  - epel
  - letsencrypt

      servername: www.example.com
      port: 80
      docroot: /var/www/html
      redirectmatch_status: 301
      redirectmatch_regexp: ^(?!/\.well-known/acme-challenge/).*
      redirectmatch_dest: https://www.example.com$0
      port: 443
      servername: www.example.com
      docroot: /var/www/www.example.com
      ssl: true
      ssl_chain: /etc/letsencrypt/live/www.example.com/chain.pem
      ssl_key: /etc/letsencrypt/live/www.example.com/privkey.pem
      ssl_cert: /etc/letsencrypt/live/www.example.com/cert.pem
          path: '/'
          url: ''
letsencrypt::email: [email protected]
letsencrypt::configure_epel: false
letsencrypt::manager_cron: true
      plugin: webroot
        - /var/www/html

All this is colllected using the hiera_resources module. First we collect the nonssl vhosts. We create a vhost with using the default docroot that performs a permanent redirect to the https:// vhost. However, we add in an exception for /.well-known/acme-challenge/ so that the letsencrypt server can talk back to us to authorise the certificate.

When this is created, Apache will be scheduled for a restart. This does not actually happen, so the first time the certificate is created letsencrypt will actually come in via the default virtualhost, not this one. In the future though, when the certificate is renewed, it will be used and the exception is required.

Next we create the certificate itself using the webroot plugin and put the response in /var/www/html/. It will also create a cron job to automatically renew the certificate.

Finally it can create the main Virtualhost which should probably not contain anything surprising.