Let's Encrypt with FreeNAS 11.1 and later

Integrating Let’s Encrypt TLS Certificates with FreeNAS

FreeNAS has long had the ability to use HTTPS for the web GUI, but that has usually meant dealing with self-signed certificates and the associated headaches, or paying for a commercial certificate. With the launch of Let’s Encrypt in December 2015, trusted TLS certificates became available at no cost. However, their short lifetime, as well as the requirement to use other software tools to issue them, has caused some challenges in integrating them into the FreeNAS web GUI.

With the release of FreeNAS 11.1, the FreeNAS API now has all the hooks needed for a script to automate deployment of a certificate. This resource will describe two methods for obtaining a certificate for your FreeNAS box. It will also describe how to automate deployment of the certificate. The goal is that the certificate will be issued (and renewed) and deployed automatically, and you won’t need to manually deal with it ever again.

Prerequisites

In order to obtain a certificate from Let’s Encrypt, you must own (or at least control) a public domain name–Let’s Encrypt will not issue a certificate for an IP address, nor for a .local domain. You must also be able to prove that you control that domain. Let’s Encrypt supports two methods of proof: either you must demonstrate the ability to change your domain’s DNS records, or you must be able to place a small text file where it can be reached at http://your_domain/.well-known/acme-challenge/pseudorandomstring.

If your DNS host has an API that allows for automated updates, this would be the preferred method, as it doesn’t require opening any outside access to your FreeNAS machine.

Installing acme.sh

Let’s Encrypt only issues certificates through client software that implements the ACME protocol. The “official” client from EFF is certbot, but many others have been developed. This guide will describe the use of acme.sh, a lightweight client that’s written as a shell script, is very flexible, and has very minimal dependencies. acme.sh will register an account with letsencrypt.org, obtain certificates, and call deploy_freenas.py to deploy them to your FreeNAS system.

To install acme.sh, log in to the shell of your FreeNAS box as root, and run curl https://get.acme.sh | sh -s email=you@yourdomain.com (inserting a valid email address). This will download the script, install it in /root/.acme.sh/, and adjust your PATH accordingly. Log out, and log back in.

Installing deploy_freenas.py

deploy_freenas.py is a Python script, based heavily on the work of @gary_1, that uses the FreeNAS API to upload the new certificate into the FreeNAS middleware and set it as the active certificate for the web GUI.

Download the script by changing to a convenient directory (either /root/, or perhaps a dataset where you store scripts), and running git clone https://github.com/danb35/deploy-freenas.

Once you’ve downloaded the script, you’ll need to create a configuration file called deploy_config. The git repo has an example (deploy_config.example) which you can copy and modify, or you can write your own from scratch. In its simplest form, the file would look like this:

[deploy] 
password = YourSuperDuperSecureRootPassword

…and contain your FreeNAS root password. If you’re using TrueNAS 12.0, it’s recommended instead that you generate an API key and specify that (rather than your root password) with api_key = 1-DXcZ19sZoZFdGATIidJ8vMP6dxk3nHWz3XX876oxS7FospAGMQjkOft0h4itJDSP. The other parameters are documented in deploy_config.example.

Important: Since the config file contains your root password, make sure it’s only readable by root.

Issuing the certificate

Now it’s time to actually obtain your certificate. As noted above, to prove your ownership of your hostname, you’ll need to either make specified changes to your domain’s DNS records, or be able to respond to a HTTP challenge. The former is preferred if your DNS host supports it.

Using the DNS challenge

Many DNS hosts have APIs, which allow software to automate changes to your DNS records. acme.sh supports many of these DNS hosts, a list of the supported APIs (and how to use them) can be found at dnsapi · acmesh-official/acme.sh Wiki · GitHub. I use Cloudflare for my DNS. It’s free (at this service level), it has a responsive, easy-to-use dashboard, and its API is well-supported by acme.sh.

From the shell prompt, run the following commands:
export CF_Token="Y_jpG9AnfQmuX5Ss9M_qaNab6SQwme3HWXNDzRWs" - This is an API token for your account from Cloudflare; see the acme.sh docs for more information.
.acme.sh/acme.sh --issue -d fqdn_of_freenas_box --dns dns_cf
.acme.sh/acme.sh --install-cert -d fqdn_of_freenas_box --reloadcmd "/path/to/deploy_freenas.py"

If you use a different DNS host, you’ll need to substitute the appropriate credentials, which are documented at the link above. The examples at that link assume you’re using the bash shell, though they’ll also work with zsh, which has been the default root shell on FreeNAS since 11.2. If your root shell is something different like csh, you may need to replace the export with setenv. In that case, the first command above would look like setenv CF_Token "(your cloudflare master API key)". Note the use of “setenv” rather than “export”, and the lack of the = sign.

If all goes as expected, acme.sh will generate an account key, register the account with letsencrypt.org, request the certificate, create the appropriate DNS records, obtain the certificate, and clean up the DNS records. It will then call deploy_freenas.py to install the cert on FreeNAS.

Using the HTTP challenge

Note: The HTTP challenge uses acme.sh in standalone mode, which requires installation of socat. As a result, if you’re unable to use DNS validation, you’ll need to install acme.sh, socat, and deploy_freenas.py inside a jail.

To complete the HTTP challenge, the Let’s Encrypt servers must be able to connect to http://fqdn_of_freenas_box and obtain a small text file from /.well-known/acme-challenge/pseudorandomfilename. This means a few things:

  1. fqdn_of_freenas_box must resolve to the external (i.e., public) IP address of your network.
  2. You must be able to open port 80 on your firewall, and send it to the jail into which you installed acme.sh.

The first step is a matter of your DNS records; the second is a matter of the capabilities of, and your control over, your firewall.

Once the DNS records are published and the port forward is in place, run the following command:

acme.sh --issue -d fqdn_of_freenas_box --standalone --reloadcmd "/path/to/deploy_freenas.py"

As above, if all goes well, this will issue the certificate and deploy it to your system. You can remove the port forward after the cert is issued if you like, but you’ll need to put it back in place to renew the cert.

Automating renewal

Now that you’ve obtained and deployed your certificate, you’ll want to set up a cron job to renew it automatically. To do this, add a cron job through the web GUI, to run as root. It should run daily, at whatever time you like–it’s recommended that it run at an “odd” time like 3:17 am, rather than at the top of the hour, to avoid excessive stress on the Let’s Encrypt servers. The command will be /root/.acme.sh/acme.sh --cron if you used DNS validation, or iocage exec <jailname> /root/.acme.sh/acme.sh --cron if you installed acme.sh in a jail. With this command, acme.sh will run daily. If your certificate is at least 60 days old, it will attempt to renew it. If it fails, it will try again the next day, and so on until the certificate is successfully renewed.

Note

The above instructions describe the use of my deploy_freenas.py script to upload the newly-obtained cert into TrueNAS and configure services to use it. acme.sh now includes its own deployment script, which may suit your needs better. For more information on using it, see: deployhooks · acmesh-official/acme.sh Wiki · GitHub I believe my script gives more flexibility in choosing which services to apply the new cert to, though.

Also, TrueNAS itself now includes this capability, but only for a very short list of DNS providers–only Route53 for CORE, or Route53 or Cloudflare for SCALE. If you’re using an appropriate DNS provider, this may suit your needs.

3 Likes

Thanks for providing these instructions. I was able to set this up successfully a while ago, however since I use Linode_v4 as the DNS verification mechanism, it requires socat to be installed. That started disappearing from Truenas somewhere around April of last year. I was able to work around it for a while by reinstalling socat, however in the latest releases /usr/bin is now on a read-only file system and the level of hackery now required to work around this is getting to ridiculous levels.

So, I’d love to know if you have an alternative approach here. I tried putting the binary in a different location and added export PATH=$PATH:/path/to/socat/bin to my acme.sh.env, but that still isn’t working.

Would love to hear if you have other ideas!

You should never be installing software on the base OS–if you need software that isn’t included in the base OS, you should use a jail (for CORE) or a sandbox (for SCALE). Install acme.sh, socat, and any other dependencies there, and run the deploy script from there as well.

3 Likes

Thanks for that tip. Sounds like a much better way to do this. ;]

Just so I can make sure I’m on the same page here: I’m running SCALE, so are you talking about using Jailmaker (Sandboxes (Jail-like Containers) | TrueNAS Documentation Hub) to create the sandbox?

I just kinda ran with it and it seems to be working fine. FWIW here is what I did to get things set up:

  1. Followed instructions from Sandboxes (Jail-like Containers) | TrueNAS Documentation Hub to install jailmaker.
  2. Had to add sudo chmod +x /mnt/main/jailmaker/jlmkr.py to make it work.
  3. Then ran jlmkr to follow instructions to create a jail named letsencrypt that runs at startup - use --bind='/etc/certificates' in the systemd_nspawn_user_args setting to ensure the certificates land in the correct place
  4. Ran apt install cron socat git in jail (using jlmkr shell letsencrypt)
  5. Ran curl https://get.acme.sh | sh -s email=you@yourdomain.com in jail
  6. Ran git clone https://github.com/danb35/deploy-freenas in jail
  7. Copied /root/.acme.sh from my old install into jail (same path)
  8. Copied /root/deploy-freenas/deploy_config into jail (same path)
  9. Ran ./acme.sh --force --dns dns_linode_v4 --dnssleep 240 --issue -d <truenas_fqdn> --standalone --reloadcmd "/root/deploy-freenas/deploy_freenas.py" in jail to ensure cert renew works (specific for me, since I use linode_v4 DNS verification)
  10. Ran ./acme.sh --cron --force in jail to ensure cron job will run
  11. Checked crontab -l, entry created by acme.sh was 53 6 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null in my case (YMMV)
  12. Deleted ACME Cert renewal cron job from Truenas UI

Hope this all continues to work now!

1 Like

Are you sure this is correct? If you’re using DNS validation, you wouldn’t ordinarily add the --standalone flag; that would have it do HTTP validation instead. The acme.sh docs don’t include this flag:

You are of course correct - the --standalone was a leftover from your instructions. I believe the --dns flag effectively overrides it, but I’ll edit it out of my post to avoid confusing other folks. ;]

Bugger. It appears I can no longer edit my earlier post. :confused:

Yeah, Discourse puts a time limit on that. But without the --standalone flag, socat probably isn’t needed either.

I’m in the process of reinstalling my Truenas server. Freenas/Core ran happily for about a decade and a half. And now I’ve switched over to SCALE. The procedure that you described is pretty much how I set this up in FreeNAS back in the day.

Looking at the certificate part of the SCALE GUI it seems like its pushing me into a different direction. Use the GUI to get a CSR, use the GUI for DNS validation and use the GUI to publish the certificate. Only issue is that there are only three preconfigured DNS validation methods provided (Cloudflare, AWS and OVH) There is however an option to use Shell to add your own DNS validation.

The old forum had a thread on how to get this option to work. Howto: ACME DNS-authenticator This however does not work for me.

I’m not sure what would be the best strategy now. Continue to get the Shell DNS-authentication to work or just script the whole process using the setup you describe and ignore TrueNAS SCALE built in capabilities?

It really is up to you. I see several possibilities:

  • Use the built-in TrueNAS system, and get your script to work to make the necessary DNS updates for it.
  • Switch DNS providers to Cloudflare, and use the built-in support
  • If acme.sh supports your DNS host, install acme.sh (it’s safe to install; it’s a script that doesn’t modify anything in the system), use it to get the cert, and use its built-in function to deploy the cert to TrueNAS.
  • Use acme.sh or some other client to get the cert, and have it call my script to deploy it to TrueNAS.

Between the last two, I believe my script is more featureful than the deploy script that’s part of acme.sh, but I don’t know whether those features would benefit you.

1 Like

As the lead clown-car tester of his script I can attest it works, even if applied by clowns.

My advice is a bit more nuanced. If you already have or can have a FQDN to get managed by Cloudflare, then I’d recommend you use the built in system with Cloudflare. It’s pretty painless and not dependent on Jails, apps, or other magic.

Route53 and the other limited list of DNS service folk in the pre-approved iXSystems list would also fall into that category.

However, I would still install and configure the SSL system that Dan has put together for the simple reason that there are likely tons of devices in your local network that need a SSL to gave a secure GUI and as such would benefit from Dans tutorials / scripts / etc.

I’m away from home for a while, and have taken a secondary NAS with me. I have a domain for that location (and a subdomain for the LAN, which I wasn’t smart enough to set up for my home network), which has its DNS through Cloudflare, so I could use TrueNAS’ built-in certificate management. But what I’ve done instead is to set up a sandbox, install acme.sh there, get a wildcard cert there, and then script deployment of that cert to wherever on my LAN I can (including to TrueNAS itself, my Proxmox host, and my UPS, but I haven’t made it work for my WiFi access point yet).

I ended up with sticking to the acmesh script.

After more time than I dare to admit and after making every part of every script log EVERYTHING. I realised that I had set a static IP but had never bothered to set a DNS server… Then once the logging showed that the IP used to get a token from my registrar was the static IPv6 and not the IPv4 one I had whitelisted all was easy :slight_smile:

Clown car tested indeed

1 Like