2019-03-15 - Progress - Tony Finch
I've done some initial work to get the Ansible playbooks for our DNS systems working with the development VM cluster on my workstation. At this point it is just for web-related experimentation, not actual DNS servers.
Of course, even a dev server needs a TLS certificate, especially because these experiments will be about authentication. Until now I have obtained certs from the UIS / Jisc / QuoVadis, but my dev server is using Let's Encrypt instead.
Chicken / egg
In order to get a certificate from Let's Encrypt using the http-01
challenge, I need a working web server. In order to start the web
server with its normal config, I need a certificate. This poses a bit
of a problem!
Snakeoil
My solution is to install Debian's ssl-cert
package, which creates a
self-signed certificate. When the web server does not yet have a
certificate (if the QuoVadis cert isn't installed, or dehydrated
has
not been initialized), Ansible temporarily symlinks the self-signed
cert for use by Apache, like this:
- name: check TLS certificate exists stat: path: /etc/apache2/keys/tls-web.crt register: tls_cert - when: not tls_cert.stat.exists name: fake TLS certificates file: state: link src: /etc/ssl/{{ item.src }} dest: /etc/apache2/keys/{{ item.dest }} with_items: - src: certs/ssl-cert-snakeoil.pem dest: tls-web.crt - src: certs/ssl-cert-snakeoil.pem dest: tls-chain.crt - src: private/ssl-cert-snakeoil.key dest: tls.pem
ACME dehydrated boulders
The dehydrated
and dehydrated-apache2
packages need a little
configuration. I needed to add a cron job to renew the certificate, a
hook script to reload apache when the cert is renewed, and tell it
which domains should be in the cert. (See below for details of these
bits.)
After installing the config, Ansible initializes dehydrated
if
necessary - the creates
check stops Ansible from running
dehydrated
again after it has created a cert.
- name: initialize dehydrated command: dehydrated -c args: creates: /var/lib/dehydrated/certs/{{inventory_hostname}}/cert.pem
Having obtained a cert, the temporary symlinks get overwritten with links to the Let's Encrypt cert. This is very similar to the snakeoil links, but without the existence check.
- name: certificate links file: state: link src: /var/lib/dehydrated/certs/{{inventory_hostname}}/{{item.src}} dest: /etc/apache2/keys/{{item.dest}} with_items: - src: cert.pem dest: tls-web.crt - src: chain.pem dest: tls-chain.crt - src: privkey.pem dest: tls.pem notify: - restart apache
After that, Apache is working with a proper certificate!
Boring config details
The cron script chatters into syslog, but if something goes wrong it should trigger an email (tho not a very informative one).
#!/bin/bash set -eu -o pipefail ( dehydrated --cron dehydrated --cleanup ) | logger --tag dehydrated --priority cron.info
The hook script only needs to handle one of the cases:
#!/bin/bash set -eu -o pipefail case "$1" in (deploy_cert) apache2ctl configtest && apache2ctl graceful ;; esac
The configuration needs a couple of options added:
- copy: dest: /etc/dehydrated/conf.d/dns.sh content: | EMAIL="hostmaster@cam.ac.uk" HOOK="/etc/dehydrated/hook.sh"
The final part is to tell dehydrated
the certificate's domain name:
- copy: content: "{{inventory_hostname}}\n" dest: /etc/dehydrated/domains.txt
For production, domains.txt
needs to be a bit more complicated. I
have a template like the one below. I have not yet deployed it; that
will probably wait until the cert needs updating.
{{hostname}} {% if i_am_www %} www.dns.cam.ac.uk dns.cam.ac.uk {% endif %}