Background

I use the not-for-profit certificate authority (CA) Lets Encrypt to provide TLS certificates for a number of websites and services that I host on a number of virtual private servers and on my DMZ server.

I mentioned to a colleague that I was looking for a way to manage Lets Encrypt certificates via Ansible and he suggested I take a look at Dehydrated. In this write-up I share the Ansible configuration that I wrote to setup Dehydrated when (re-)provisioning my hosts.

If you have an interest in automating the provisioning of Lets Encrypt certificates, this write-up should be useful.

Ansible Role

Using Dehydrated to obtain Lets Encrypt certificates on a number of hosts, I created a dehydrated Ansible role. This is published over on GitLab.

In one situation, I have a pair of servers configured by Ansible, one of which runs a web server to receive dynamic DNS updates. In the vars/main.yml file is the setupDehydrated variable which is set to true.

If you find yourself in a similar situation you can pull this variable into host_vars or into your Ansible inventory and use it accordingly.

You may want to issue a certificate to the first server in a set of load balanced servers, and then copy or synchronize it to subsequent servers, in which case the setupDehydrated variable might also be useful.

Tasks

The role is subdivided into two tasks:


- include: packages.yml
  when: setupDehydrated | bool

- include: installAndConfigureDehydrated.yml
  when: setupDehydrated | bool

packages.yml

The first task installs the prerequisite packages. These are namely a flavour of nginx for Lets Encrypt HTTP-01 ACME challenge and Git for checking out the Dehydrated GitHub repository.

The flavour of nginx to install is determined by the variable nginxPackage defined in vars/main.yml. On Debian the value of this would typically be nginx-full or nginx-light. In scenarios where you would prefer to run a lighter weight web server, for example when serving up simple web pages, you might prefer to use nginx-light.

In my Ansible playbooks, I set this variable in group_vars. In the case where you are adopting this role to setup Lets Encrypt on a single server, then you can simply leave it where it is.

installAndConfigureDehydrated.yml

This is the business end of the role. It is probably best to read through the task first hand. In summary, here is what this task does:

  • Creates the dehydrated user and group
  • Creates an /etc/dehydrated configuration directory
  • Copies the domains.txt file to the configuration directory. See the domains.txt section.
  • Copies the dehydrated configuration template into place. See the config section.
  • Creates accounts, certificates and ACME challenge directories
  • Creates an nginx ‘ssl’ directory
  • Configures nginx to serve the ACME challenge directory from the .well-known/acme-challenge location from the default site on port 80. See the caveats section.
  • Sets up [dehydrated-hook.sh](#dehydrated-hook.sh]
  • Accepts the CAs terms-of-service
  • Generates a certificate
  • Installs the a cron.weekly script for certificate renewal
  • Installs a sudoers file allowing some commands from dehydrated-hook.sh to run as root, for example restarting nginx

domains.txt

files/examplehost/etc/dehydrated/domains.txt

This file contains a list of domains for which the created certificate should be used. The first domain is setup as the CN (Common Name) as well as a SAN (Subject Alternative Name) and the subsquent domains are setup as additional SANs. Here is an example from one of my VPS Servers:

caramel.biscuit.ninja biscuit.ninja dcadden.co.uk cdn.biscuit.ninja

And the resulting certificate:

Screen capture depicting the details of a certificate issued from Lets Encrypt using Ansible and Dehydrated

The domains.txt file for this VPS would be put into the files/caramel/etc/dehydrated/ folder because the host is called caramel.biscuit.ninja and the file is sought by the installAndConfigureDehydrated.yml task using the {{ inventory_hostname_short }} Ansible variable.

This means that the role can be applied to multiple hosts by simply including a domains.txt file for each applicable host and making the necessary changes to your playbook.

For example, for two servers, foo.example.com and bar.example.com, you would include the following two files within your copy of the role:

  • files/foo/etc/dehydrated/domains.txt
  • files/bar/etc/dehydrated/domains.txt

And in your playbook you might have something like:

- name: apply vpsWeb configuration to vpsWeb servers
  hosts: vpsWeb
  roles:
  - dehydrated
  - vpsWeb

where both foo.example.com and bar.example.com are in the vpsWeb group in the inventory file:

[vpsWeb]
foo.example.com
bar.example.com

config

The /templates/etc/dehydrated/config.j2 file contains all of the Dehydrated (Lets Encrypt/ACME) configuration options. There is an example configuration file in the Dehydrated GitHub repository. Reading that will help explain some of the configuration options.

When testing your implementation of the Ansible Dehydrated, switch the CA to the acme-staging-v02 API in order to avoid being rate limited in response to multple Ansible pushes.

The config file requires a contact email address. This is templated in from the acme_contact_email variable which is included in the vars/main.yml file.

dehydrated-hook.sh

Dehydrated supports a hook, which is a script that can be run as a result of running the Dehydrated script and, for example, deploying a new certificate. /files/usr/local/bin/dehydrated-hook.sh will reload nginx when a new certificate is issued (deploy_cert).

There are empty handlers in that file for a number of other tasks, for example sync_cert(), which can be used to flush to disk when a new certificate is created but before they are symlinked, which might be useful in some situations.

Caveats

This role:

  1. Assumes that you are not going to serve content from the nginx default server and will create specific servers for hosting your sites and services with a server_name directive.
  2. Makes no provision for the export or re-use of Lets Encrypt account keys.

Conclusion

If you find Dehydrated to be useful in deploying Lets Encrypt TLS certificates to your environment, then I encourage you to buy the developer a ‘coffee’.

Ansible is a great tool for automating the provisioning of servers and environments and most importantly, for getting that configuration into a version control system. I have shared a number of examples over on GitLab which you might find useful.

References

  1. Lets Encrypt documentation
  2. RFC 8555
  3. Dehydrated home page
  4. Ansible documentation
  5. nginx documentation