Using Ansible to update Synology with Acme certificates from pfSense Certificate Manager

Using Ansible to update Synology with Acme certificates from pfSense Certificate Manager

I love that my pfSense router can manage Acme certificates for my local domain. I use DigitalOcean for hosting this blog, so I was able to configure pfSense manage my Acme certificate updates using a DNS Challenge controlled through DigitalOcean’s API (with a key).

I got tired of having to manually download and upload the certificate files to my Synology NAS every few months. I’m already leveraging Ansible for other maintenance drudgery, so yesterday I decided to explore automating it.

Prerequisite: you need to enable the “Write Certificates” option in pfSense’s Acme Certificates module. It is a checkbox that can be found if you follow Services -> Acme Certificates -> General Settings:

Screen-Shot-2021-01-31-at-11.36.18-AM

I’ll just cut to the chase here, I have two Github Gists with the Ansible tasks. There are two:

1) Copy certificates from pfSense to your Ansible workspace:

# file: "get_pfsense_certificates.yaml"

# alternatively, set as a variable in your inventory
- name: set domain_name
  set_fact: 
    domain_name: mydomain.org
  
- name: get certs
  fetch:
    src: "/conf/acme/{{ item }}"
    dest: ../.certs/ 
    flat: yes
  with_items:
  - "{{ domain_name }}.fullchain" 
  - "{{ domain_name }}.ca"
  - "{{ domain_name }}.key"
  - "{{ domain_name }}.crt"

2) Copy the certificates to Synology and restart the affected services:

# file: "update_synology_certificates.yaml"
update_synology_certificates.yaml
- name: set directories
  set_fact:
    sys_dir: /usr/syno/etc/certificate
    pkg_dir: /usr/local/etc/certificate
    stg_dir: /tmp/certs

- name: create cert staging directory
  file:
    path: "{{ stg_dir }}"
    state: directory

# certs from previous task
- name: stage certs
  copy:
    src: "../.certs/{{ item.s }}"
    dest: "/tmp/certs/{{ item.d }}"
  with_items:
    - { s: "{{ domain_name }}.crt",
        d: "cert.pem" }
    - { s: "{{ domain_name }}.fullchain", 
        d: "fullchain.pem" }
    - { s: "{{ domain_name }}.key",
        d: "privkey.pem" }

- name: read the certificate info
  shell:
    cmd: jq -r 'to_entries' {{ sys_dir }}/_archive/INFO
  register: cat_info

- name: set cert_info
  set_fact:
    cert_info: "{{ cat_info.stdout | from_json }}"

- name: copy staged certs to archives
  copy:
    src: "{{ stg_dir }}/"
    dest: "{{ sys_dir }}/_archive/{{ item.key }}/"
    remote_src: true
  with_items: "{{ cert_info | flatten }}"
  register: certs

- name: copy certs for packaged services
  copy:
    src: "{{ sys_dir }}/_archive/{{ item.0.key }}/"
    dest: "{{ pkg_dir }}/{{ item.1.subscriber }}/{{ item.1.service }}/"
    remote_src: true
  loop: "{{ cert_info | subelements('value.services') }}"
  when: item.1.isPkg

- name: copy certs for system services
  copy:
    src: "{{ sys_dir }}/_archive/{{ item.0.key }}/"
    dest: "{{ sys_dir }}/{{ item.1.subscriber }}/{{ item.1.service }}/"
    remote_src: true
  loop: "{{ cert_info | subelements('value.services') }}"
  when: not item.1.isPkg
  
- name: restart affected services
  shell:
    cmd: "/usr/syno/sbin/synoservicectl --reload {{ item }}"
  with_items:
    # different per server - no doubt you will need to add or 
    # remove services from this list
    - nginx                   # DSM
    - pkgctl-MailServer
    - pkgctl-SynologyDrive
    - ftpd
    - pkgctl-ReplicationService
  when: certs.changed

- name: remove cert staging directory
  file:
    path: "{{ stg_dir }}"
    state: absent

Even if you are not using either pfSense or a Synology, I’m sure these Ansible Tasks could prove useful in your particular situation. Need help? Feel free to leave and comment!

Shoutout to [BIT]arantno and the discection of the certificate layout on the Synology filesystem. You saved me a lot of time!