Centralising Certificate Management - Part 3

What's in This Post

This is the final post about centralising your certificate management. In part 2 I explained how to use the ACME Controller role in order to generate challenges and fetch the resulting certificates for Let's Encrypt.

Now that we have those certificates on our ACME controller, we need to deploy them to our edge systems and restart any services that require it.

Certificate Deploy Playbook

Here is a simple playbook that will search the directories where the ACME Controller role stored its new certificates and copy those to each host as required.

The previous post showed that my acme_hosts group contains host1.example.com and host2.example.com.
A group variable file for acme_hosts:

 1# group_vars/acme_hosts.yml
 2# src vars define where certificates are found on the acme controller
 3src_root_dir: "/etc/acme_controller/"
 4src_cert_sub_dir: "certs"
 5src_priv_sub_dir: "priv"
 6# dst vars define where certificates will be copied on the host.
 7# You applications will have their config point to these locations.
 8dst_cert_sub_dir: "certs"
 9dst_priv_sub_dir: "priv"
10default_dst_root_dir: "/etc/certificates/"

A host variable file for host1:

1# host_vars/host1.example.com.yml
2  domains:
3    - { name: example.com, services: ['postfix', 'nginx'] }
4    - { name: example.biz, services: ['postfix', 'nginx'] }

My playbook is run on the acme controller host. Alternatively you could use delegate_to to specify your acme controller:

 1# acme_deploy.yml
 2  - name: Deploy ACME certificates to target hosts
 3    hosts: acme_hosts #
 4    become: yes
 5    vars:
 6      services: {}
 7    tasks:
 8      - name: move files to destination and register restart if changed
 9        include_tasks: acme_cert_copy.yml
10        loop: "{{domains}}"
11        loop_control:
12          loop_var: domain
13          label: domain.name
14    handlers:
15      - name: General service reload
16        service:
17          name: "{{item.key}}"
18          state: "{{item.value}}"
19        loop: "{{services|d({})|dict2items}}"
20        listen: "restart services"

The included file:

 1# acme_cert_copy.yml
 2- name: Create Certificate Directory
 3  file:
 4    path: "{{default_dst_root_dir}}{{domain.name}}/{{item}}"
 5    state: directory
 6    mode: 0755
 7  vars:
 8    new_dirs:
 9      - "{{dst_cert_sub_dir}}"
10      - "{{dst_priv_sub_dir}}"
11  loop: "{{new_dirs}}"
12- name: Check that source files exist.
13  local_action:
14    module: stat
15    path: "{{item}}"
16  register: cert_files
17  loop:
18    - "{{src_root_dir}}{{domain.name}}/{{src_cert_sub_dir}}/{{domain.name}}.fullchain"
19    - "{{src_root_dir}}{{domain.name}}/{{src_priv_sub_dir}}/private.key"
20- name: Copy files and register services
21  block:
22    - name: Copy domain certificate
23      copy:
24        src: "{{src_root_dir}}{{domain.name}}/{{src_cert_sub_dir}}/{{domain.name}}.fullchain"
25        dest: "{{default_dst_root_dir}}{{domain.name}}/{{dst_cert_sub_dir}}/{{domain.name}}.fullchain"
26        backup: yes
27        force: yes
28        mode: 0644
29      register: domain_cert
30      notify: "restart services"
31    - name: Copy private key
32      copy:
33        src: "{{src_root_dir}}{{domain.name}}/{{src_priv_sub_dir}}/private.key"
34        dest: "{{default_dst_root_dir}}{{domain.name}}/{{dst_priv_sub_dir}}/private.key"
35        backup: yes
36        force: yes
37        mode: 0644
38    - name: Register services to reload
39      set_fact:
40        services : "{{ services|default({}) |combine({item: 'reloaded'}) }}"
41      loop: "{{domain.services}}"
42      when: domain_cert is changed
43  when:
44    - cert_files.results[0].stat.exists
45    - cert_files.results[1].stat.exists

Why is only 1 certificate file copied?

Although the ACME controller role generated the fullchain certificate, a CA certficate and the server certificate, only the fullchain is copied over. In my case all services I use, requre the fullchain file which contains both the certificate for my issuer (Let's Encrypt's intermediate Certificate) and my server certificate. You can always modify the playbook above to copy the additional certificates to each host.

This series