Centralising Certificate Management - Part 2

What's in This Post

In my previous post, I explained the concept of centralising ACME certificate management as a way of improving the security of your certificates and the systems that rely on them. This post will provide a quick example of how to achieve centralisation of certificate management, using Ansible scripts. The host which will generate ACME challenges and initially store certificates I call the ACME Controller. Other hosts, which will use those certificates, I call ACME Hosts. I have Ansible host groups that match this naming:

1[acme_ctrl]
2controller.example.com ansible_connection=local
3[acme_hosts]
4host1.example.com
5host2.example.com

My ACME controller will run the Ansible playbooks locally, that will keep the design simple.

The ACME Controller

I have a simple Ansible acme_controller role. The README file lists the required variables. Key amongst these variables is the ac_domains list. It contains a definition of each domain you'll want a certificate for.

1ac_domains:
2  - { name: example.net, new_key: 'false', org: 'My Org', country: 'au' }

Updating DNS

Since we'll be using the dns-01 challenge, the ACME controller will need to update each domain for which it will request a certificate. I use a hidden primary that hosts all my domains. This server doesn't answer any DNS queries from the public, I rely on secondary DNS servers to do that on my behalf. Therefore I'll need to grant the ACME Controller permission to send NSUPDATE commands to each zone. That can be done using BIND's update-policy configuration element. Using the ddns-confgen tool will generate the required configuration.

1$ ddns-confgen -a sha256 -k acme-controller >> /etc/named/ac_tsig.key #Generate the key
1# /etc/named/named.conf
2...
3include "/etc/named/ac_tsig.key"
4zone "example.net"{
5  type master;
6  update-policy { grant "acme-controller" zonesub "ANY"; grant "local-ddns" zonesub "any"; };
7};

The values generated by ddns-confgen will need to be added to the dns_update_details variable in the acme_controller role. I use 2 different DNS hosting providers. One of them updates my zone as soon as my primary server sends them a notify. The second provider is much slower and only checks hourly for changes (hey, it's a free service). So I'll need to run the role again in order to collect my certificates once Let's Encrypt can see my DNS changes.

Running the role

The first time you run the role, you'll create an account and generate challenges for all your configured domains.

1- name: Process ACME challenges and collect certificates
2  hosts: acme_ctrl # It doesn't make sense to have overlapping accounts on multiple hosts
3  roles:
4    - acme_controller

The role will check a public resolver to see if the challenge update is visible to the public. It will only fetch the certificate if this check passes. So you can run it as often as needed.

What do I do with these certificates?

In part 3 I'll provide a method for deploying certificates from the ACME Controller to your edge systems.

This series