Let’s Encrypt, DNS challenge, and scripting?: Update 2024

First, I’d like to thank @danb35 who originally asked this question in 2020 via the post link here: Let’s Encrypt, DNS challenge, and scripting?.

That was a very helpful post to which @dicko provided the best answer!

I did have to make a couple tweaks now in 2024, which I’ve shared on this post for the community since I had already done a lot of the legwork for this in an identical fashion for a UniFi controller build.

I do have a question as well so to save those that don’t need the updated instructions some time, I’ll post that first. I’ve been able to run the following commands to set the Let’s Encrypt via acme.sh generated cert as the default certificate under certman “Admin > Certificate Management”.

fwconsole certificate --import
fwconsole certificate --default=1

However, that doesn’t instruct sysadmin “Admin > System Admin > HTTPS Setup > Settings” to use that certificate. I still need to login to the FreePBX WebUI myself and reselect the renewed cert after each acme.sh script renewal this way. I was able to verify that sysadmin is NOT using the updated/renewed cert after renewal and then running: fwconsole reload as well.

I was able to confirm this by:

  1. Running the acme.sh script against the default ZeroSSL CA initially, then loading that cert as the default cert via certman
  2. Then I set sysadmin to use that same cert by “installing” it, which also shows the signing CA on the same page to the right.
  3. Then I reran the acme.sh script using the Let’s Encrypt CA via its --server option and repeated to set the newly generated cert as the default cert via certman
  4. Once I visited sysadmin again, I found that even though the originally ZeroSSL signed cert is no longer in the /etc/asterisk/keys/ directory, FreePBX is still using the former cert signed by ZeroSSL. Only after I manually installed the cert in the WebUI using the drop-down did the Certificate Issuer info on the same page change to Let’s Encrypt.

This would seem to imply that I will need to configure sysadmin to use the newly generated cert upon each renewal via a post-hook just as I’m doing with certman via the fwconsole certificate <flags> commands I posted above.

Are there any terminal commands I can use to instruct sysadmin to use the new cert after each renewal, after I’ve imported them to certman?

Now for the updates to the original post. First, @dicko mentioned on that post to delete the entire contents of /etc/asterisk/keys when adding your new certificates to the same directory in order for the fwconsole certificate <flags> commands to work. I found this unnecessary. Furthermore, these steps can be added to a post-hook script created in the directory: /root/.acme.sh.

  1. Install acme.sh script
mkdir /root/certbot
cd /root/certbot
curl https://get.acme.sh | sh
acme.sh --upgrade --accountemail "[email protected]"
acme.sh --register-account -m "[email protected]" --server zerossl
  1. Create the post hook file
touch /root/.acme.sh/freepbx-renew-hook.sh
chmod 640 /root/.acme.sh/freepbx-renew-hook.sh
  1. Use the following bash script to automatically backup the old certificates, convert the private key to RSA, PEM, and copy to the /etc/asterisk/keys/ directory. Note that this script is intentionally converting the certs into both .p12 and .pem format even though we only need the .pem. I’ve done this in case they are needed in the future but you can tweak as necessary. The password I’ve provided in the openssl commands is only used in this script as openssl immediately encrypts the new .p12 cert and then uses the same password to decrypt it to the .pem output file. You can adjust the password as necessary if you like.
    Be sure you update the command below with your own FQDN.:
#!/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-backup.tar /etc/ssl/private/*.keystore.jks /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/private/fullchain.pem
/usr/bin/openssl pkcs12 -export -in /etc/ssl/private/asterisk19-pub.crt -inkey /etc/ssl/private/asterisk19-priv.key -out /etc/ssl/private/asterisk19.p12 -name freepbx -password pass:freepbx-lets-encrypt
/usr/bin/openssl pkcs8 -topk8 -nocrypt -in /etc/ssl/private/asterisk19-priv.key -out /etc/ssl/private/asterisk19-priv_rsa.key
/usr/bin/openssl pkcs12 -in /etc/ssl/private/asterisk19.p12 -out /etc/ssl/private/asterisk19.pem -nodes -password pass:freepbx-lets-encrypt

echo "** Backup Cert Files"
/usr/bin/tar -cvf cert.tar asterisk19*

echo "** Correct Permissions"
chown root:root /etc/ssl/private/*
chmod 664 /etc/ssl/private/*

echo "** Copying FreePBX Console UI Private Keys"
cd /etc/asterisk/keys
tar -cvf cert-backup_`date +%Y-%m-%d_%H.%M.%S`.tar *
rm -rf /etc/asterisk/keys/freepbx.example.com.*
cp /etc/ssl/private/asterisk19-priv_rsa.key freepbx.example.com.key
cp /etc/ssl/private/asterisk19-pub.crt freepbx.example.com.crt
cp /etc/ssl/private/asterisk19.pem freepbx.example.com.pem
chown asterisk:asterisk /etc/asterisk/keys/freepbx.example.com.*
chmod 640 /etc/asterisk/keys/freepbx.example.com.*

echo "** Load new Cert to FreePBX via certman"
/usr/sbin/fwconsole certificate --import
/usr/sbin/fwconsole certificate --default=1

Note
Since I only had the default self-signed certificate in my FreePBX deployment prior to running this process, the self-signed cert was installed to position 0 and the new Let’s Encrypt cert was installed to position 2. You may need to run: fwconsole certificate --list to verify your new cert’s position in the certman list after you run: fwconsole certificate --import. Adjust the final command: fwconsole certificate --default=X as necessary.

  1. The acme.sh script requires us to define our API email and token as an environment variable prior to running the cert request. Normally, they tell you to create these via the export command in the terminal, but this doesn’t survive a reboot and we want automatic renewals. If you add the following lines to your /etc/environment file, it will survive a reboot. Keep in mind that there’s a security risk to doing this which is why you SHOULD use a restricted API key. There are guides on the internet for how to generate and configure one. Please don’t use your global CloudFlare key for this.
CF_Email="YOUR-CLOUDFLARE-EMAIL"
CF_Token="YOUR-CLOUDFLARE-API-KEY"
  1. Next we generate the certificate. When I first ran this, I used the acme.sh default ZeroSSL CA. You can change this by adding the --server lets-encrypt flag to the command immediately following the -d freepbx.example.com. Again, be sure you update the command below with your own FQDN.
acme.sh --force --issue --dns dns_cf -d freepbx.example.com \
--dnssleep 60 --pre-hook "touch /etc/ssl/private/cert.tar; \
tar -zcvf /root/.acme.sh/FreePBXSSL-Backup_`date +%Y-%m-%d_%H.%M.%S`.tgz \
/etc/ssl/private/*" --fullchainpath /etc/ssl/private/asterisk19-pub.crt \
--keypath /etc/ssl/private/asterisk19-priv.key --reloadcmd "sh /root/.acme.sh/freepbx-renew-hook.sh"

That’s it. You should now have a new Let’s Encrypt cert setup to auto-renew via DNS challenge about every 2 months. Keep in mind, you still need to go into sysadmin and tell it to use the cert but it should be set as default via certman. See my question at the beginning of this post. Cheers!

FreePBX’ acme client implimentation is horribly broken and by all reports getting more broken all the time , Somebody , presumably at Sangoma , needs to rethink the whole thing. acme.sh could easily replace it and bring DNS-01 and its advantages to 99.9% of users here

Yeah. It’s interesting that they don’t already support that. It should be super easy to implement given that so much of the leg work is already out there in multiple opensource formats.