Resource icon

Let's Encrypt with FreeNAS 11.1 and later 0.3

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:
Code:
[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 https://github.com/Neilpang/acme.sh/wiki/dnsapi. 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_Key="(your cloudflare master API key)"
export CF_Email="you@example.com" # the email address you used to register for cloudflare
.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_Key "(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.
Author
danb35
Downloads
3,598
Views
58,669
First release
Last update
Rating
4.50 star(s) 10 ratings

More resources from danb35

Latest updates

  1. Use new API method to restart web server

    It turns out that the API method that was supposed to restart the web GUI in FreeNAS 11.1 didn't...

Latest reviews

Excellent information. Great help!

But it can use a little improvement. I'd like to see four things:

(1) more than just the Cloud Flare example. I used DuckDNS and had to figure things out (see below). Maybe juxtapose the relevant documentation w/ the implementation and give some guidance of how things may vary.

(2) I put everything in a jail and think this is good practice. Some discussion about using jails would be helpful. Even better, if you agree about the value of using a jail, would be revising the instructions to include use of a jail.

(3) Because current versions of FreeNAS (11.2+) use iocage, some discussion of appropriate iocage commands.

(4) Because current versions of FreeNAS do not include bash in a jail by default, examples or instructions using csh instead.

Here's what I did for DuckDNS:

Issuing the Certificate w/ DNS challenge:
Start a shell within the jail
setenv DuckDNS_Token "<token goes here>"
.acme.sh/acme.sh --insecure --issue --dns dns_duckdns -d <subdomain>.duckdns.org

Cron job to automate renewal:
FreeNAS > Tasks > Cron Jobs
Command = iocage exec acme /root/.acme.sh/acme.sh --cron
Run as user = root
Description & Schedule are left whatever you want.
For those of you who have an old-school BIND server:

```
ssh root@nas.nono.io
scp cunnie@ns-he.nono.io:/usr/local/etc/namedb/letsencrypt.key .
chmod 400 letsencrypt.key
curl https://get.acme.sh | sh
exit
ssh root@nas.nono.io
git clone https://github.com/danb35/deploy-freenas
printf "[deploy]\npassword = YourPassword\n" > deploy-freenas/deploy_config
chmod 400 deploy-freenas/deploy_config
bash
# Don't try elliptic curve! Error formatting alert: 'HTTP server does not support certificates with keys shorter than 1024 bits. HTTPS cannot be enabled until a 1024 bit keylength or greater certificate is added # elliptic curve cryptography for the win! But need
export NSUPDATE_SERVER="ns-he.nono.io"
export NSUPDATE_KEY="/root/letsencrypt.key"
.acme.sh/acme.sh --issue \
-d nas.nono.io \
--dns dns_nsupdate \
--reloadcmd /root/deploy-freenas/deploy_freenas.py
.acme.sh/acme.sh --cron --home /root/.acme.sh
```
im getting a Create new order error. Le_OrderFinalize not found. {
"type": "urn:ietf:params:acme:error:malformed",
"details": "error creating new order :: invaild character in DNS name",
"Status": 400

the command i ran is acme.sh --issue -d fqdn_of_freenas_box --standalone --httpport 8675 --reloadcmd "/root/deploy-freenas/deploy_freenas.py"

any help please ?
danb35
danb35
The message would appear to be telling you that the FQDN you've entered is invalid. Is this an actual public domain you own?
Has been working well for ages but I just had an issue where the certificate wasn't updated automatically and thought I would share the fix in case anyone else had the same problem. I ran deploy_freenas.py manually and got a 409 error and so I went to System - General in the FreeNAS GUI and tried update the certificate manually there and it turned out that there was a problem with my config (IPv6 address was invalid). Once I fixed that the script ran successfully and life was good again.
Got everything working, except when it came to my password. Turns out, you can't use certain special characters in your root password, or this script blows up. The ones I found were "%", "(", and ")".

Everything else was perfect.

I'm using Amazon Route 53 for my DNS, and the only complaint I have is that I can't use `ddclient` with it (out of the box, anyway...apparently there are workarounds) to keep my domain's IP up to date.
works as intended
I'm getting a 500 error (importing certificate). [root@freenas ~]# /root/deploy_freenas.py
Error importing certificate!
<Response [500]>
[root@freenas ~]#

Ideas?
Had a chance to finally test this yesterday and it works like a charm. Thanks for the effort!
Great resource, covered all it needed to and easy to follow.

I very much appreciated the advice on using CloudFlare DNS, I didn't know they had a free DNS hosting service, so pleased to have moved my domain NS over to that.

One suggestion I would have is, if you're like me and were previously using 'freenas.local' rather than freenas.domain.tld - get that working beforehand. I half did it during the implementation and it caused a bunch of weird errors with deploy_freenas.py when it couldn't actually access freenas using the name I'd provided. (lack of setting up those A records / host file appropriately beforehand).

I also got some errors about root having no crontab during install of acme.sh but I assume that's expected.
Excellent how-to, does what says on the tin.
Top