---
date: 2016-05-10
title: Letsencrypt with Apache and Puppet
category: devops
Summary: Using Puppet to manage Letsencrypt certs and Apache VirtualHosts
---

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


- [danzilio/letsencrypt](https:/forge.puppetlabs.com/danzilio/letsencrypt)
- [stahnma/epel](https:/forge.puppetlabs.com/stahnma/epel)
- [puppetlabs/apache](https:/forge.puppetlabs.com/puppetlabs/apache)
- Rob Nelson's  [hiera_resources](https://github.com/rnelson0/puppet-hiera_resources)

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](https://gogs.chriscowley.me.uk/puppet/chriscowley-lablocal) 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):

```
hiera_include('classes')
class { 'lablocal::nonsslvhosts': }->
class { 'lablocal::letsencryptcerts': }->                                 
class { 'lablocal::sslvhosts': }
```

That references 3 classes in the lablocal module:

nonsslvhosts.pp

```
class lablocal::nonsslvhosts {
  hiera_resources('apache-nonssl-vhosts')
}
```

letsencryptcerts.pp

```
class lablocal::letsencryptcerts {
  hiera_resources('letsencryptcerts')
}
```

sslvhosts.pp

```
class lablocal::sslvhosts {
  hiera_resources('apache-ssl-vhosts')
}
```

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


```

---
classes:
  - apache
  - epel
  - letsencrypt

apache-nonssl-vhosts:
  apache::vhost:
    www.example.com-nonssl:
      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
apache-ssl-vhosts:
  apache::vhost:
    www.example.com:
      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
      proxy_pass:
        -
          path: '/'
          url: 'http://10.1.0.15:8080/'
letsencrypt::email: example@example.com
letsencrypt::configure_epel: false
letsencrypt::manager_cron: true
letsencryptcerts:
  letsencrypt::certonly:
    www.example.com:
      plugin: webroot
      webroot_paths:
        - /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.
