Automate update of SSL certificate

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
Well, some progress, though I'm sure it makes for very ugly code. It uses your code to upload the cert, then pulls down the list, iterates through it to get the ID number of the cert we just uploaded, and then (attempts to) set the active cert to the correct one. But at that last point, it's erroring out. Here's the code as it is now:
Code:
#!/usr/local/bin/python

"""
Import and activate a SSL/TLS certificate into FreeNAS 11.1 or later
Uses the FreeNAS API to make the change, so everything's properly saved in the config
database and captured in a backup.

Requires paths to the cert (including the any intermediate CA certs) and private key,
and username, password, and FQDN of your FreeNAS system.

Your private key should only be readable by root, so this script must run with root
privileges.  And, since it contains your root password, this script itself should
only be readable by root.
"""

import sys
import json
import requests
from datetime import datetime

PRIVATEKEY_PATH="/path/to/privkey.pem"
FULLCHAIN_PATH="/path/to/fullchain.pem"
USER="root"
PASSWORD="ReallySecurePassword"
DOMAIN_NAME="server_fqdn"
now = datetime.now()
cert = "letsencrypt-%s-%s-%s" %(now.year, now.month, now.day) # TODO: force two digits for month and day

print cert

# Load cert/key
with open(PRIVATEKEY_PATH, 'r') as file:
  priv_key = file.read()
with open(FULLCHAIN_PATH, 'r') as file:
  full_chain = file.read()

# Update or create certificate
r = requests.post(
  'https://' + DOMAIN_NAME + '/api/v1.0/system/certificate/import/',
  auth=(USER, PASSWORD),
  headers={'Content-Type': 'application/json'},
  data=json.dumps({
  "cert_name": cert,
  "cert_certificate": full_chain,
  "cert_privatekey": priv_key,
  }),
)

# TODO: Check response 201 and activate cert
if r.status_code == 201:
  print ("Certificate import successful")
else:
  print ("Error importing certificate!")
  print (r)
  sys.exit(1)

# Download certificate list
limit = {'limit': 0} # set limit to 0 to disable paging in the event of many certificates
r = requests.get(
  'https://' + DOMAIN_NAME + '/api/v1.0/system/certificate/',
  params=limit,
  auth=(USER, PASSWORD))

# print r.status_code # Should be 200
# print r.headers['content-type']
# print r.json()

if r.status_code == 200:
  print ("Certificate list successful")
else:
  print ("Error listing certificates!")
  print (r)
  sys.exit(1)
 
# Parse certificate list to find the id that matches our cert name
cert_list = r.json()

for index in range(100):
  cert_data = cert_list[index]
  if cert_data['cert_name'] == cert:
	cert_id = cert_data['id']
	break

# Set our cert as active
r = requests.put(
  'https://' + DOMAIN_NAME + '/api/v1.0/system/settings/',
  auth=(USER, PASSWORD),
  headers={'Content-Type': 'application/json'},
  data=json.dumps({
  "stg_guicertificate": cert_id,
  }),
)

if r.status_code == 200:
  print ("Setting active certificate successful")
else:
  print ("Error setting active certificate!")
  print (r)
  sys.exit(1)


But the part that tries to set the active cert errors out. Here's the output:
Code:
root@freenas11:~ # ./deploy_freenas.py
Certificate import successful
Certificate list successful
Cert ID: 7
Error setting active certificate!
<Response [405]>


Edit: It's noted in the sig, but this is under 11.1-U1.

Edit 2: As is so often the case, RTFM. Setting the active takes a PUT, not a POST. I've made that change above, and the code seems to work with that change. Output is now:
Code:
root@freenas11:~ # ./deploy_freenas.py
Certificate import successful
Certificate list successful
Cert ID: 8
Setting active certificate successful
root@freenas11:~ #


Edit 3: One limitation I'm noticing is that the API function that updates the certificate doesn't reload the web server at the same time. Thus, even though you've uploaded and activated a new cert, the web GUI will still be serving the old one until it restarts for some other reason. I don't immediately see an API call to force this reload.
 
Last edited:

gary_1

Explorer
Joined
Sep 26, 2017
Messages
78
Nice work :)

I wonder if setting any of the other variables that impact the server cause a service restart or not, for example if you changed the stg_guiport or https version.

If they do, I'd expect setting a new cert to also do a restart. If they don't then I guess that's expected behaviour and an additional api may be needed to reload (or failing that restart) the service. That or perhaps scripting a service reload/restart, but it'd be nice to keep usage via api for a number of reasons.
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
Well, it's easy enough in any event to do subprocess.run() and reload nginx. I don't see an API call to do the reload, though it's possible I'm missing it. But it does seem, as you said, that this ought to be doable through the API. https://redmine.ixsystems.com/issues/28035 submitted for dev input.
 
Joined
Jan 5, 2017
Messages
2
Right, so the bug report says "please do GET /legacy/system/restart-httpd/ after configuration update.", but when I do:

Code:
# Rest of script here, which works

# Restart Web UI
r = requests.get(
  'https://' + DOMAIN_NAME + '/legacy/system/restart-httpd/',
  auth=(USER, PASSWORD),
  headers={'Content-Type': 'application/json'},
 )

.. nothing appears to happen as the cert isn't new after a refresh.

if I test with:
Code:
curl -vu USERHERE:PASS -Li https://DOMAIN/legacy/system/restart-httpd/


I get a 302 to /, then a 302 to /account/login/?next=/, then a 200 back and the login page from that call. What am I missing? The only Google results for "/legacy/system/restart-httpd/" are the bug, and soon this topic.
 
Last edited:

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
.. nothing appears to happen as the cert isn't new after a refresh.
I'd noticed that, but thought it was a fluke. Apparently not. Updated the bug.
 

danb35

Hall of Famer
Joined
Aug 16, 2011
Messages
15,504
So they're adding an API method to do the reload, which I guess will be available in 11.1-U2. At this point, though, it looks like subprocess.run("service", "nginx", "reload") is the way to go. But that means this script must run on the FreeNAS box.
 

Jason Keller

Explorer
Joined
Apr 2, 2015
Messages
61
Has anyone gotten the restart API to work? I'm on 11.1-U5 and it only gives me a 405 back no matter what I try to push to it. I left a note in the bug tracker about it.
 

dich

Cadet
Joined
Aug 7, 2017
Messages
8
I don't know that I'll be able to code this (I know no Python), but the API documentation has been updated, and there are methods to list certs and to set the active cert for the web GUI. The method to set the active takes as its parameter an integer cert ID, which appears to be a simple counter--the first cert on the system is ID 1, the second is ID 2, etc., and it continues to increment even if certs are deleted (so the ID is never reused). Unfortunately, it looks like the function to import a cert doesn't return the ID of the imported cert. So, it looks to me like the process would go like this:
  • Assign a unique name to the cert (I'd go with letsencryptddmmm or something similarly date-based, representing the date issued).
  • Import the cert using that name.
  • List the certs, and grab the cert ID matching the name we used above.
  • Set the active cert to the ID obtained above.
  • Optionally delete old certs.
Seems conceptually simple enough. The devil will be in the details, no doubt.

FWIW, I ran into trouble with a memory leak on 11.1 causing scrubs to kill the system. It appears to be fixed on 11.1-U1 (and scrubs take 60% less time).

You don't really need to code python to use the API, you know. Check 'curl' as a tool, you can do everything from a bash script or even command line, for what it matters.
Plus, the API allows you to set the ID of the cert you upload, so I think the intended use is to list the certs, note the largest counter, add 1 when importing your's and then set that one as active.
Don't forget to restart ix-nginx and then nginx services after you did the above steps.
P.S. Somehow, the TrueNAS support guy didn't seem to know there is a way beyond the GUI :)
 

StarkJohan

Explorer
Joined
Mar 27, 2015
Messages
62
This seems to work on 11.2-u7

Code:
/api/v1.0/system/settings/restart-httpd/
 
Top