Securing Ubiquiti UniFi Cloud Key with Let’s Encrypt SSL and automatic dns-01 challenge
Let’s Encrypt is great as it is free, but it also has downsides: (1)certificates need to be renewed every 90 days and (2) your internal servers need to be accessible. I was reluctant to use Let’s Encrypt for my internal equipment as this would mean that during the renewal, the server needs to be addressable/reachable from the outside.
To automate the whole Let’s Encrypt process, we will use acme.sh which is an alternative to certbot and I will rely on my CloudFlare account which I use for DNS already (the acme.sh supports a number of other DNS providers other than Cloudflare as well).
Install acme.sh via the online installer on the Cloud Key:
curl https://get.acme.sh | sh
The online installer will download the latest version and also install a cronjob. You can safely ignore the warning about netcat as we will use another method to do the verification:
Exit the terminal and re-open it again. We will also enable auto-upgrade for acme.sh (the –accountemail will be used for Let’s Encrypt email notifications when certs are renewed):
acme.sh --upgrade --auto-upgrade --accountemail "[email protected]"
Create a post-hook file
To automate the certificate installation, create the file /root/.acme.sh/cloudkey-renew-hook.sh – no adjustments are needed:
#!/bin/bash # Renew-hook for ACME / Let's encrypt echo "** Configuring new Let's Encrypt certs" cd /etc/ssl/private rm -f /etc/ssl/private/cert.tar /etc/ssl/private/unifi.keystore.jks /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/private/fullchain.pem openssl pkcs12 -export -in /etc/ssl/private/cloudkey.crt -inkey /etc/ssl/private/cloudkey.key -out /etc/ssl/private/cloudkey.p12 -name unifi -password pass:aircontrolenterprise keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore /etc/ssl/private/cloudkey.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi rm -f /etc/ssl/private/cloudkey.p12 tar -cvf cert.tar * chown root:ssl-cert /etc/ssl/private/* chmod 640 /etc/ssl/private/* echo "** Testing Nginx and restarting" /usr/sbin/nginx -t /etc/init.d/nginx restart ; /etc/init.d/unifi restart
Using the CloudFlare DNS API
Log into your CloudFlare console and get the Global API key:
Going forward you will use the following exports to use the DNS API:
export CF_Key="YOUR-CLOUDFLARE-API-KEY" export CF_Email="YOUR-CLOUDFLARE-EMAIL"
With acme.sh and DNS challenge, the process of verification is automated. Again, adjust the domain name as part of the -d option:
acme.sh --force --issue --dns dns_cf -d unifi.naschenweng.info --pre-hook "touch /etc/ssl/private/cert.tar; tar -zcvf /root/.acme.sh/CloudKeySSL_`date +%Y-%m-%d_%H.%M.%S`.tgz /etc/ssl/private/*" --fullchainpath /etc/ssl/private/cloudkey.crt --keypath /etc/ssl/private/cloudkey.key --reloadcmd "sh /root/.acme.sh/cloudkey-renew-hook.sh"
The above command will first do a backup of the existing SSL keys and will then contact Let’s Encrypt to issue new certificates, install the cert and restart the Cloud Key:
[Fri Jan 6 12:36:04 CAT 2017] Run pre hook:'touch /etc/ssl/private/cert.tar; tar -zcvf /root/.acme.sh/CloudKeySSL_2017-01-06_12.36.04.tgz /etc/ssl/private/*' tar: Removing leading `/' from member names /etc/ssl/private/cert.tar [Fri Jan 6 12:36:05 CAT 2017] Single domain='unifi.naschenweng.info' [Fri Jan 6 12:36:05 CAT 2017] Getting domain auth token for each domain [Fri Jan 6 12:36:05 CAT 2017] Getting webroot for domain='unifi.naschenweng.info' [Fri Jan 6 12:36:05 CAT 2017] _w='dns_cf' [Fri Jan 6 12:36:05 CAT 2017] Getting new-authz for domain='unifi.naschenweng.info' [Fri Jan 6 12:36:14 CAT 2017] The new-authz request is ok. [Fri Jan 6 12:36:14 CAT 2017] unifi.naschenweng.info is already verified, skip. [Fri Jan 6 12:36:14 CAT 2017] unifi.naschenweng.info is already verified, skip dns-01. [Fri Jan 6 12:36:14 CAT 2017] unifi.naschenweng.info is already verified, skip dns-01. [Fri Jan 6 12:36:14 CAT 2017] Verify finished, start to sign. [Fri Jan 6 12:36:17 CAT 2017] Cert success. -----BEGIN CERTIFICATE----- MIIFEDCCA/igAwIBAgISA1A5Vw5heGc48ksteHBNbzoTMA0GCSqGSIb3DQEBCwUA ... ... lugkHA== -----END CERTIFICATE----- [Fri Jan 6 12:36:17 CAT 2017] Your cert is in /root/.acme.sh/unifi.naschenweng.info/unifi.naschenweng.info.cer [Fri Jan 6 12:36:17 CAT 2017] Your cert key is in /root/.acme.sh/unifi.naschenweng.info/unifi.naschenweng.info.key [Fri Jan 6 12:36:17 CAT 2017] The intermediate CA cert is in /root/.acme.sh/unifi.naschenweng.info/ca.cer [Fri Jan 6 12:36:17 CAT 2017] And the full chain certs is there: /root/.acme.sh/unifi.naschenweng.info/fullchain.cer [Fri Jan 6 12:36:18 CAT 2017] Installing key to:/etc/ssl/private/cloudkey.key [Fri Jan 6 12:36:18 CAT 2017] Installing full chain to:/etc/ssl/private/cloudkey.crt [Fri Jan 6 12:36:18 CAT 2017] Run Le_ReloadCmd: sh /root/.acme.sh/cloudkey-renew-hook.sh ** Configuring new Let's Encrypt certs cloudkey.crt cloudkey.key cloudkey.p12 fullchain.pem unifi.keystore.jks ** Testing Nginx and restarting nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful [ ok ] Restarting nginx (via systemctl): nginx.service. [Fri Jan 6 12:36:26 CAT 2017] Reload success
Finally, adjust your controller’s hostname:
If everything is done correctly, you will have a browser without any more SSL errors and you will not have to worry about renewing certificates:
Configuring the cronjob
Since the Let’s Encrypt certificate needs to be renewed every 3 months, you need to configure the auto-renew via a cronjob through crontab -e and append the following to the end of the crontab:
# m h dom mon dow command 0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh/" >> /var/log/letsencrypt.log
Does it survive upgrades
I have upgraded my UCK a number of times (currently on 0.64) and only with the recent firmware upgrade the crontab was reset. All other configuration remained intact and it was as simple as just restoring the crontab entry above.
If you feel uncomfortable using Let’s Encrypt, I also have a guide for installing a regular SSL certificate with RapidSSL available.
🍺 Pay it forward: If any of my content helped you in any way, then follow me on Twitter or send me some coins:
(Ripple) rPz4YgyxPpk7xqQQ9P7CqNFvK17nhBdfoy (BTC) 1Mhq9SY6DzPhs7PNDx7idXFDWsGtyn7GWM (ETH) 0xb0f2d091dcdd036cd26017bb0fbd6c1488fc8d04 (LTC) LTfP7yJSpGFvuPqjSEKaqcjue6KSA9118y (XVG) D5nBpFBaD6vmVJ5CBUhkz8E4SNWscf6pMu (BNB) 0xb0f2d091dcdd036cd26017bb0fbd6c1488fc8d04