[HOWTO] configure official certificates for FreeNAS using Let's Encrypt

airflow

Contributor
Joined
May 29, 2014
Messages
111
Hello,

I recently wanted to leverage the free official Let's Encrypt CA for using in some of my projects, for example for the GUI of FreeNAS itself. It's quite easy, and with a little hack you can automate the process completely, so you will never have to manually renew the certificate of FreeNAS and it will be valid forever. =) Well actually it's not a hack, as everything can be configured via the GUI of FreeNAS.

  1. Create a jail on FreeNAS for the installation of certbot, which is the client from Let's Encrypt to fetch and renew certificates
    a. update the ports-tree within the jail
    b. install ports-mgmt/portmaster, security/py-certbot & www/nginx
  2. The webserver (nginx) is used by certbot to verify that the FQDNs you are trying to certify are actually yours. This means that these names actually have to resolve to the IP of the jail and be reachable by the internet. If you are not comfortable with this, this is not for you.
  3. As we would like to use the name in the certificate for the GUI of FreeNAS, but from the internet it has to resolve to the jail, we have a little bit of contradiction here. I solve this problem by using split-DNS, which just means that the name for FreeNAS resolves differently when coming from the internet or the internal LAN. I achieve this goal by using dnsmasq in another jail of my FreeNAS, and setting it as my internal DNS-resolver/proxy.
    a. when coming from the Internet it should point to the IP of the jail, and it must be reachable from the internet.
    b. when coming from the internal LAN, it should point to the GUI of FreeNAS. The GUI of FreeNAS should of course not be reachable via internet.
  4. Now for the configuration of certbot: That's actually asthonishingly easy. It is just one command per name. You have to specify the webroot of the nginx-installation, which is /usr/local/www/nginx, and of course the FQDN.
    certbot --webroot -w /usr/local/www/nginx -d freenas.example.com certonly -n
  5. The certificate and all necessary files are now stored in /usr/local/etc/letsencrypt/live. Each time they are renewed (we will come to that later) they are automatically replaced there.
  6. You can do that for as many names you'd like. I do this for a variety of services in my network with different name. The goal is to automize the process for all of them. =)
  7. In the case of FreeNAS, it is quite easy to achieve. You could of course manually configure the generated certificates via the GUI. But you would have to repeat that each three month, as Let's Encrypt certificates are only valid three month. Instead, we do the following:
    a. Look at the directory /etc/certificates on your FreeNAS. Here the system stores and retrieves its configured certificates. The name of this directory is stored in the rc.d-variable SSLDIR. Don't touch this directory. It will be recreated on every reboot anyway.
    b. Instead, create a new directory anywhere you like within your ZFS-pools. Create symbolic links from the Let's Encrypt certificates to this new directory. Use exactly the same names for the targets of the links, so you recreate the file-structure basically. fullchain.pem links to the file ending with .crt, privkey.pem links to the file ending with .key.
    c. Now you just have to tell FreeNAS that it should use your new certificate-directory instead of its own. You can do that comfortably in System/Tunables/Add Tunable/" (type = rc.conf). The name of the variable is SSLDIR, the value is the path to the directory created by you before.
  8. Create a shellsript which executes the certbot-command from before. You might want to add a "service nginx restart" after that to reload the certificate for the GUI. Each time you run the certbot-command from before, it automatically checks if the certificate is to be renewed and will do so if needed. Call the shellscript via the cronjob-function on a regular basis. When the certificate of your FreeNAS is to be renewed, it will happen automatically now.
When implementing the solution, be sure to enable both HTTP+HTTPS (with no redirection), so if anything goes wrong, you do not lock yourself out. Good luck!
 

m0nkey_

MVP
Joined
Oct 27, 2015
Messages
2,739
The only issue I have with this, is the FreeNAS UI should not be exposed to the Internet and should only be accessible over a LAN or VPN.


.. or become your own CA :D
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
The only issue I have with this, is the FreeNAS UI should not be exposed to the Internet
...and nothing about this process requires that the FreeNAS UI be accessible from the Internet. The server in the jail will be, but not the web GUI.
 
Joined
May 28, 2017
Messages
3
Awesome howto - was exactly what I needed. Thank you very much for putting this together for everyone.

A couple minor items:
On step 4, you'll need to omit the "-n" from the command if this is your first time running certbot.

On step 7a, I was just a bit confused by the wording (it's probably just me), but to be clear, the link target names will be the same as the names from /etc/certificates.

Thanks again for putting this together, much appreciated!
 

krad

Cadet
Joined
Jul 6, 2013
Messages
2
You can use DNS auth for this now so you dont have to expose anything to the net. What I have done is setup a cheap placeholder domain on aws route53. I then use this for the acme auth codes. I can then overload that domain locally an push out the a records I need to my home/work lan clients, similar to the original post, but with nothing pointing to me on the internet side.

The big advantage here is you dont have to setup a root CA so you dont need to import any root certs

This is how you do it (you dont have to call ansible just something to do the aws call eg awscli)

Code:
# do this initally
letsencrypt certonly   --agree-tos --manual --preferred-challenges=dns --manual-auth-hook ./auth_hook.sh --manual-public-ip-logging-ok -d  <some_domain>

# then to renew in the future
letsencrypt  --preferred-challenges=dns --manual-auth-hook ./auth_hook.sh renew

]# cat auth_hook.sh
#!/bin/bash
AWS_PROFILE=default
BOTO_CONFIG=./.aws/credentials
ANSIBLE_FORCE_COLOR=true
export BOTO_CONFIG AWS_PROFILE ANSIBLE_FORCE_COLOR

ansible-playbook -c local ansible_auth_hook.yml -i hosts.cfg


# cat ansible_auth_hook.yml
#!/usr/local/bin/ansible-playbook -i "127.0.0.1," -c local
# this is a self contained playbook as it makes it easier to see everything

---
- name: "lets DNS challenge helper"
  hosts: localhost
  gather_facts: no
  vars:
	validation: "{{ lookup('env', 'CERTBOT_VALIDATION') }}"

  tasks:
	- debug:
		msg: " {{ lookup('env', 'CERTBOT_DOMAIN') }} "

	- name: "Add challenge to route53 zone: {{ lookup('env', 'CERTBOT_DOMAIN') }} :"
	  route53:
		command: create
		profile: "{{ lookup('env', 'AWS_PROFILE') }}"
		zone: "{{ lookup('env', 'CERTBOT_DOMAIN').split('.')[1:] | join('.') }}"
		record: "_acme-challenge.{{ lookup('env', 'CERTBOT_DOMAIN') }}"
		type: TXT
		ttl: 60
		value: '"{{ validation }}"'
		wait: yes
		overwrite: true

# cat hosts.cfg
localhost ansible_connection=local ansible_python_interpreter=/usr/local/bin/python


 
Last edited:

mt7479

Cadet
Joined
Jan 13, 2018
Messages
1
I have a hard time getting this to work on FreeNAS-11.0-U4 (54848d13b). The original directory still gets populated with the certificates from the db, despite having changed the SSLDIR rc.conf varaiable.

Is there a way to check what the value of SSLDIR is ?
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504

JimSamLyn

Cadet
Joined
Feb 9, 2019
Messages
2
Hello,

I recently wanted to leverage the free official Let's Encrypt CA for using in some of my projects, for example for the GUI of FreeNAS itself. It's quite easy, and with a little hack you can automate the process completely, so you will never have to manually renew the certificate of FreeNAS and it will be valid forever. =) Well actually it's not a hack, as everything can be configured via the GUI of FreeNAS.

  1. Create a jail on FreeNAS for the installation of certbot, which is the client from Let's Encrypt to fetch and renew certificates
    a. update the ports-tree within the jail
    b. install ports-mgmt/portmaster, security/py-certbot & www/nginx
  2. The webserver (nginx) is used by certbot to verify that the FQDNs you are trying to certify are actually yours. This means that these names actually have to resolve to the IP of the jail and be reachable by the internet. If you are not comfortable with this, this is not for you.
  3. As we would like to use the name in the certificate for the GUI of FreeNAS, but from the internet it has to resolve to the jail, we have a little bit of contradiction here. I solve this problem by using split-DNS, which just means that the name for FreeNAS resolves differently when coming from the internet or the internal LAN. I achieve this goal by using dnsmasq in another jail of my FreeNAS, and setting it as my internal DNS-resolver/proxy.
    a. when coming from the Internet it should point to the IP of the jail, and it must be reachable from the internet.
    b. when coming from the internal LAN, it should point to the GUI of FreeNAS. The GUI of FreeNAS should have course not be reachable via internet.
  4. Now for the configuration of certbot: That's actually asthonishingly easy. It is just one command per name. You have to specify the webroot of the nginx-installation, which is /usr/local/www/nginx, and of course the FQDN.
    certbot --webroot -w /usr/local/www/nginx -d freenas.example.com certonly -n
  5. The certificate and all necessary files are now stored in /usr/local/etc/letsencrypt/live. Each time they are renewed (we will come to that later) they are automatically replaced there.
  6. You can do that for as many names you'd like. I do this for a variety of services in my network with different name. The goal is to automize the process for all of them. =)
  7. In the case of FreeNAS, it is quite easy to achieve. You could have course manually configure the generated certificates via the GUI. But you would have to repeat that each three month, as Let's Encrypt certificates are only valid three month. Instead, we do the following:
    a. Look at the directory /etc/certificates on your FreeNAS. Here the system stores and retrieves its configured certificates. The name of this directory is stored in the rc.d-variable SSLDIR. Don't touch this directory. It will be recreated on every reboot anyway.
    b. Instead, create a new directory anywhere you like within your ZFS-pools. Create symbolic links from the Let's Encrypt certificates to this new directory. Use exactly the same names for the targets of the links, so you recreate the file-structure basically. fullchain.pem links to the file ending with .crt, privkey.pem links to the file ending with .key.
    c. Now you just have to tell FreeNAS that it should use your new certificate-directory instead of its own. You can do that comfortably in System/Tunables/Add Tunable/" (type = rc.conf). The name of the variable is SSLDIR, the value is the path to the directory created by you before.
  8. Create a shellsript which executes the certbot-command from before. You might want to add a "service nginx restart" after that to reload the certificate for the GUI. Each time you run the certbot-command from before, it automatically checks if the certificate is to be renewed and will do so if needed. Call the shellscript via the cronjob-function on a regular basis. When the certificate of your FreeNAS is to be renewed, it will happen automatically now.
When implementing the solution, be sure to enable both HTTP+HTTPS (with no redirection), so if anything goes wrong, you do not lock yourself out. Good luck!
 

JimSamLyn

Cadet
Joined
Feb 9, 2019
Messages
2
Hello,

I recently wanted to leverage the free official Let's Encrypt CA for using in some of my projects, for example for the GUI of FreeNAS itself. It's quite easy, and with a little hack you can automate the process completely, so you will never have to manually renew the certificate of FreeNAS and it will be valid forever. =) Well actually it's not a hack, as everything can be configured via the GUI of FreeNAS.

  1. Create a jail on FreeNAS for the installation of certbot, which is the client from Let's Encrypt to fetch and renew certificates
    a. update the ports-tree within the jail
    b. install ports-mgmt/portmaster, security/py-certbot & www/nginx
  2. The webserver (nginx) is used by certbot to verify that the FQDNs you are trying to certify are actually yours. This means that these names actually have to resolve to the IP of the jail and be reachable by the internet. If you are not comfortable with this, this is not for you.
  3. As we would like to use the name in the certificate for the GUI of FreeNAS, but from the internet it has to resolve to the jail, we have a little bit of contradiction here. I solve this problem by using split-DNS, which just means that the name for FreeNAS resolves differently when coming from the internet or the internal LAN. I achieve this goal by using dnsmasq in another jail of my FreeNAS, and setting it as my internal DNS-resolver/proxy.
    a. when coming from the Internet it should point to the IP of the jail, and it must be reachable from the internet.
    b. when coming from the internal LAN, it should point to the GUI of FreeNAS. The GUI of FreeNAS should have course not be reachable via internet.
  4. Now for the configuration of certbot: That's actually asthonishingly easy. It is just one command per name. You have to specify the webroot of the nginx-installation, which is /usr/local/www/nginx, and of course the FQDN.
    certbot --webroot -w /usr/local/www/nginx -d freenas.example.com certonly -n
  5. The certificate and all necessary files are now stored in /usr/local/etc/letsencrypt/live. Each time they are renewed (we will come to that later) they are automatically replaced there.
  6. You can do that for as many names you'd like. I do this for a variety of services in my network with different name. The goal is to automize the process for all of them. =)
  7. In the case of FreeNAS, it is quite easy to achieve. You could have course manually configure the generated certificates via the GUI. But you would have to repeat that each three month, as Let's Encrypt certificates are only valid three month. Instead, we do the following:
    a. Look at the directory /etc/certificates on your FreeNAS. Here the system stores and retrieves its configured certificates. The name of this directory is stored in the rc.d-variable SSLDIR. Don't touch this directory. It will be recreated on every reboot anyway.
    b. Instead, create a new directory anywhere you like within your ZFS-pools. Create symbolic links from the Let's Encrypt certificates to this new directory. Use exactly the same names for the targets of the links, so you recreate the file-structure basically. fullchain.pem links to the file ending with .crt, privkey.pem links to the file ending with .key.
    c. Now you just have to tell FreeNAS that it should use your new certificate-directory instead of its own. You can do that comfortably in System/Tunables/Add Tunable/" (type = rc.conf). The name of the variable is SSLDIR, the value is the path to the directory created by you before.
  8. Create a shellsript which executes the certbot-command from before. You might want to add a "service nginx restart" after that to reload the certificate for the GUI. Each time you run the certbot-command from before, it automatically checks if the certificate is to be renewed and will do so if needed. Call the shellscript via the cronjob-function on a regular basis. When the certificate of your FreeNAS is to be renewed, it will happen automatically now.
When implementing the solution, be sure to enable both HTTP+HTTPS (with no redirection), so if anything goes wrong, you do not lock yourself out. Good luck!
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
When implementing the solution, be sure to enable both HTTP+HTTPS (with no redirection), so if anything goes wrong, you do not lock yourself out. Good luck!
Seriously, do not implement the solution in this thread. It's a creative hack, and very useful before the API supported the necessary methods to automate this properly--but now it does, and using the API means the middleware is fully aware of what's going on.
 

appliance

Explorer
Joined
Nov 6, 2019
Messages
96
"That's actually asthonishingly easy " - took few weeks.. after few attempts you get locked out and have to wait a long time. so it's a long run. neither standalone, manual, or webroot way worked out smoothly.. first you get blocking of these ridiculous ports by ISP, VPN and of course router, NAS or any test machine in line. removing redirections doesn't help as the bindings will interfere with the process no matter what ports are used by UI (e.g. 80->81, 443->444) or redirection turned off. The freenas nginx will still redirect letsencrypt out of its target webpage. DNS way is available for tiny fraction of providers. so the best way is to turn off nginx -s stop and use standalone http-01 method (https://letsdebug.net/ will be green before you shutdown nginx) after making sure the WAN IP makes its way to NAS through temporary VPN exlusions and firewall rules. Today i was lucky to finish the process, can't wait to repeat it in 3 months :) good luck
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
DNS way is available for tiny fraction of providers.
Automated DNS is available for over 50 providers using acme.sh, many of whom offer DNS service for free (manual DNS works with any DNS host that allows you to define TXT records; no API is required). Cloudflare works very well and doesn't cost anything, and combined with DNS-O-Matic (also free) it supports dynamic DNS as well.
after few attempts you get locked out
Only if you're testing using the production environment, which you shouldn't do--that's what the staging environment is there for. And even in the production environment, the rate limit for failed authorizations is per hour, so you'd only need to wait an hour to retry.
so the best way is to turn off nginx -s stop
No, the best way is to use DNS validation. If that simply isn't an option (which, honestly, it always is--even DNS manual mode would be less of a hassle than what you describe, though it's far from ideal), run your preferred client in a jail in standalone mode, and forward the appropriate ports there.

And, as I posted nine months ago, the "best way" is most certainly not to follow the method described in this thread--it was a creative hack that was necessary before the FreeNAS API supported the relevant methods, but now it does.
 

appliance

Explorer
Joined
Nov 6, 2019
Messages
96
Automated DNS is available for over 50 providers using acme.sh, many of whom offer DNS service for free (manual DNS works with any DNS host that allows you to define TXT records; no API is required). Cloudflare works very well and doesn't cost anything, and combined with DNS-O-Matic (also free) it supports dynamic DNS as well.
thanks. I have a DNS provider where I can edit TXT/A records and dynamic provider (DuckDNS) which refreshes the current IP. I'm not using any of them as first one doesn't know current IP and second one provides ugly DDNS subdomain name.
How can acme help me to automate the process? My DNS provider is not listed in any of their forms, i don't have any Cloudflare tokens (while the upstream domain manager are actually them), and I see DNSOMatic is again..asking for tokens.
If i correctly understand, i can do onetime manual refresh via TXT records, but that will not be automated.
So i either use static IP (which I can have but not prefer), or transfer domain to one of automated DNS providers, or use ugly DNS name, or use freeNAS VPN server.
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
I have a DNS provider where I can edit TXT/A records
What is that DNS provider? And you you have both an owned domain and a duckdns subdomain?
If i correctly understand, i can do onetime manual refresh via TXT records, but that will not be automated.
You can definitely do that. It isn't ideal, but it's still less intervention than setting up a VPN connection, stopping nginx, etc.
transfer domain to one of automated DNS providers
Note that you don't have to transfer the registration anywhere, you'd only transfer DNS hosting.
 

garm

Wizard
Joined
Aug 19, 2017
Messages
1,556
Cloudflare does dynamic DNS, works great, even with acme.sh
 

appliance

Explorer
Joined
Nov 6, 2019
Messages
96
What is that DNS provider? And you you have both an owned domain and a duckdns subdomain?

You can definitely do that. It isn't ideal, but it's still less intervention than setting up a VPN connection, stopping nginx, etc.

Note that you don't have to transfer the registration anywhere, you'd only transfer DNS hosting.
it's one of thousands of tiny webhosting providers. Actually i prepaid for a long time. Then i got a duckdns subdomain. Router has DDNS function to update DuckDNS.
So i tried Cloudflare, the censor of the internet, no ID checks etc, just anonymous email used, although it echoed many companies to quickly harvest data from just registered yet empty website. Wouldn't use proxied IPs with all their tons of functions as that'd definitely monitor and even modify my webserver data. IP passthrough is enough. So i transfered DNS managment to them and used same DDNS function and it works. And certbot will surely work too with API keys. Not bad! Also like every modification including initial transfer was promoted instantly, no waiting. I just don't like typing password into a script. Limiting file access means nothing. I will transform the script into interactive mode or wait until ACME tab will offer Cloudfare. Thanks.
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
I just don't like typing password into a script.
Understood, but unfortunately I can't do anything about that. Maybe one day the API will support tokens and/or limited user roles, which would be a great help for security. Another option in the interim would be to put acme.sh and the deploy script on a different machine, so at least the root password isn't stored in plain text on the server it belongs to.
 

appliance

Explorer
Joined
Nov 6, 2019
Messages
96
i can't store plain password anywhere because of principles, not even on a locked drive of a different machine, in fact never ever, so far i made the script interactive
Code:
#PASSWORD = deploy.get('password')
print("Enter FreeNAS password:")
PASSWORD = getpass.getpass(">")

but cannot use it with -reloadcmd i wonder how can i create a custom alert in freeNas to notify myself. Not lucky with search.
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
Not lucky with search.
If you can find where the cert lives in the FreeNAS filesystem, you can inspect it using openssl. You should be able to parse that output and raise an email alert when there's < 30 days remaining on the cert.

Alternatively, if you have the regular cron job running for acme.sh, you could change the --reloadcmd to email you when the cert is renewed, prompting you to run the deploy script.
 
Top