Update Unifi Dream Machine Pro certificates with automation
- October
- 27
8:50 pm Linux
Since I like to use Letsencrypt for my certificates I wanted to have a method to deploy them and keep them up to date on my Unifi Dream Machine Pro. I did some reading and found that the certs are actually in two different places, one for the web GUI and one for the WIFI guest portal.
I used some shell scripting along with make, ansible and a few cron jobs to get it done.
The files that need to be maintained on the UDM Pro are these:
/mnt/data/unifi-os/unifi/data/keystore
- this is a java keystore file/mnt/data/unifi-os/unifi-core/config/unifi-core.crt
- full chain certificate in PEM format/mnt/data/unifi-os/unifi-core/config/unifi-core.key
- key for the PEM cert
I have all my certificate renewals done on a single VM which is much easier to manage than having each server do one. So first step I needed was to copy the certificates to a location that my ansible user could read them when there was a renewal. (they are only accessible by root otherwise). Fortunately, certbot has a hook that can be used to do this. I modified my certbot renewal file in /etc/letsencrypt/renewal/mydomain.com and added a line for the hook:
[renewalparams]
authenticator = dns-rfc2136
account = 656f748e58bf73c472623f243ab7eda1
server = https://acme-v02.api.letsencrypt.org/directory
dns_rfc2136_credentials = /etc/letsencrypt/certbot-rfc2136-credentials
dns_cloudflare_propagation_seconds = 30
dns_rfc2136_propagation_seconds = 90
renew_hook = /usr/local/bin/copy_certs_to_ansible.sh
Of course I have my cron set up to check and run renewals once a day.
3
0 2
*
* * /usr/bin/certbot renew >> /var/log/letsencrypt/renew.log
If the domain actually renews, certbot will execute the renew_hook which is in copy_certs_to_ansible.sh. Here is that script.
#!/bin/sh
#
# Copy the pem versions to ansible certs dir
#
/bin/cp -p /etc/letsencrypt/live/mydomain.com/* /home/ansible/certs/mydomain.com/
#
# Rebuild the java keystore if needed
#
cd /home/ansible/certs; make
#
# Set proper owner and group on all the files
#
/bin/chown ansible /home/ansible/certs/mydomain.com/*
/bin/chgrp ad_admins /home/ansible/certs/mydomain.com/*
You notice the "make" command in the middle. There is a Makefile in /home/ansible/certs/mydomain.com/ that manages the dependencies in order to create the java keystore file. We don't want to simply rebuild it and copy it every day because ansible will then restart the unifi-os service every time. Instead, we only want to regenerate the keystore when the certificate has actually changed. Here is the Makefile from /home/ansible/certs:
#!/usr/bin/make
#TZ="US/New_York"
all: mydomain-com-keystore
mydomain-com-keystore: mydomain.com/keystore
mydomain.com/keystore: mydomain.com/keystore.p12
@/usr/bin/keytool -importkeystore -destkeystore mydomain.com/keystore -srckeystore mydomain.com/keystore.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -deststorepass aircontrolenterprise -alias unifi -noprompt
mydomain.com/keystore.p12: mydomain.com/fullchain.pem mydomain.com/privkey.pem
@/bin/openssl pkcs12 -export -in mydomain.com/fullchain.pem -inkey mydomain.com/privkey.pem -out mydomain.com/keystore.p12 -passout pass:aircontrolenterprise -name 'unifi'
And finally is the ansible playbook that keeps the certificates up to date. I have the UDM Pro defined in my ansible inventory like this (udmpro.yml):
---
all:
hosts:
children:
udmpro:
hosts:
gw.mydomain.com:
ansible_connection: ssh
ansible_user: "root"
ansible_ssh_pass: "yourrootpassword"
ansible_ssh_private_key_file: "~/.ssh/id_rsa.pub"
Here is the playbook that logs into the UDM Pro and checks to make sure certificates are up to date, restarts unifi-os if needed (udmpro_update.yml).
---
- hosts: udmpro
gather_facts: no
become: no
tasks:
- name: Copy *.mydomain.com certificate file
ansible.builtin.copy:
src: /home/ansible/certs/mydomain.com/fullchain.pem
dest: /mnt/data/unifi-os/unifi-core/config/unifi-core.crt
owner: root
group: root
mode: '0644'
backup: true
register: cert
- name: Copy *.mydomain.com key file
ansible.builtin.copy:
src: /home/ansible/certs/mydomain.com/privkey.pem
dest: /mnt/data/unifi-os/unifi-core/config/unifi-core.key
owner: root
group: root
mode: '0644'
backup: true
register: key
- name: Copy java keystore
ansible.builtin.copy:
src: /home/ansible/certs/mydomain.com/keystore
dest: /mnt/data/unifi-os/unifi/data/keystore
owner: 902
group: 902
mode: '0640'
backup: true
register: keystore
- name: Restart unifi-os if needed
command: unifi-os restart
when: cert.changed or key.changed or keystore.changed
That playbook is called via cron once per day and makes sure the most recent letsencrypt certificate is installed on the UDM Pro.
« Integrating Ansible with Hashicorp Vault | MacOS - change account to admin from command line » |
1 comment
It seems that as of today the above playbook fails because sftp is no longer working on the udmpro. To fix that I modified my ansible inventory to use scp as the transfer mechanism instead. Here’s what my inventory file looks like now:
---
all:
hosts:
children:
udmpro:
hosts:
gw.montco.net:
ansible_connection: ssh
ansible_user: "root"
ansible_ssh_pass: "putyourpasswordhere"
ansible_ssh_private_key_file: "/home/ansible/.ssh/id_rsa.pub"
ansible_ssh_transfer_method: scp