Unifi & Let’s Encrypt

The final VM in my lab to have its’ SSL configuration sorted was my Unifi controller. This runs on another Ubuntu 16.04 LTS instance, so the usual tools/methods will be used/installed:
- Let’s Encrypt SSL
- acme.sh
- Cloudflare DNS-01 challenge
First up, a nod to James Ridgway for an excellent walk through of how he achieved this task on a UniFi Cloud Key controller. James has written his own Bash script which does the leg work of converting & getting the Let’s Encrypt (LE) into the Unifi keystore. I’d spotted acme.sh has a built-in post deploy hook for Unifi, so I wanted to use that (why re-invent the wheel?). It doesn’t seem get to mentioned properly in the acme.sh doc’s though – the script is here in the acme.sh Github.
James also put the Cloudflare IDs into a config file. I just set these once using export and then acme.sh stores them into it’s own file (account.conf).
The Walk-through
As ever, the first job is to elevate to root, install acme.sh and then set the Cloudflare API details. Through the guide, replace unifi.mylab.domain with the correct FQDN for your UniFi controller and use the correct CloudFlare details (see here for how to find them).
sudo -i curl https://get.acme.sh | sh export CF_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" export CF_Account_ID="xxxxxxxxxxxxx" export CF_Zone_ID="xxxxxxxxxxxxx"
We can then request the LE certificate is issued:
acme.sh --issue --dns dns_cf -d unifi.mylab.domain
It’s then super simple to have acme.sh deploy the certificate files generated in the previous step:
acme.sh --deploy -d unifi.mylab.domain --deploy-hook unifi
acme.sh will also automatically create a cronjob to renew the certificate as needed. If an update removes the job, it’s easy to re-install it:
./acme.sh --install-cronjob
Update – July 2022
I recently moved my Unifi controller from Ubuntu LTS 16.04 to 22.04 and found a few things have changed, the first being acme.sh now defaults to using ZeroSSL rather than LE. I also found I needed to add some extra export lines to authenticate with LE. CF_Token is your Cloudflare API token and when this worked, I had the same value in CF_Key (I’m not entirely sure if both values are needed or not).
sudo -i curl https://get.acme.sh | sh export CF_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" export CF_Account_ID="xxxxxxxxxxxxx" export CF_Zone_ID="xxxxxxxxxxxxx" export CF_EMail="me@mydomain.com"
To request the certificate is now:
acme.sh --issue --dns dns_cf -d unifi.mylab.domain --server letsencrypt
I then ran into an error when trying to use the acme.sh Unifi hook:
[Fri 22 Jul 22:49:28 UTC 2022] Installing certificate for Unifi Controller (Java keystore) [Fri 22 Jul 22:49:28 UTC 2022] _unifi_keystore='/usr/lib/unifi/data/keystore' [Fri 22 Jul 22:49:28 UTC 2022] Generate import pkcs12 [Fri 22 Jul 22:49:28 UTC 2022] Import into keystore: /usr/lib/unifi/data/keystore Importing keystore /tmp/tmp.UHja0HRWf4 to /usr/lib/unifi/data/keystore... keytool error: java.io.IOException: keystore password was incorrect [Fri 22 Jul 22:49:29 UTC 2022] Error importing into Unifi Java keystore. [Fri 22 Jul 22:49:29 UTC 2022] Please re-run with --debug and report a bug.
I could manually log into the keystore, so that seemed a red-herring. Some searching found this thread on the Ubiquiti forum noting that OpenSSL v3 on Ubuntu 22 (Jammy Jellyfish) now needs a -legacy parameter when exporting the SSL to PKCS12 format.
The Unifi hook script calls a routine in the main acme.sh script, so I modified line 1427 (the first pkcs12 call in the procedure) to add the -legacy parameter – this worked and the SSL imported into the Unifi keystore successfully.
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" -caname "$pfxCaname" -legacy