--- 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.