Categories
Linux Networking Systems Administration

Importing a LetsEncrypt Certificate Generated by acme.sh into a Fortigate Firewall Using CLI

These are some tips I’ve put together on how to create a certificate using acme.sh then import it into a FortiGate firewall for use on the SSL-VPN or similar.

Getting the Certificate and Key file

I won’t go into too much detail on this – just use the acme.sh documentation to get a key+certificate: https://acme.sh

Convert the Certificate and Key into a p12 file

To make this work we need need to first convert the certificate provided by acme.sh into a p12 file for the FortiGate:

# Create the p12 file - run this on the server that has
# the acme.sh tool and the files:
root@acmeserver #~/tftp> pwd
/root/tftp
root@acmeserver #~/tftp> 
openssl pkcs12 -passout pass:abc123 -export -in '/root/.acme.sh/example.org_ecc/fullchain.cer' -inkey '/root/.acme.sh/example.org_ecc/example.org.key' -out cert.p12
root@acmeserver #~/tftp> ls
cert.p12

We now have a p12 file saved, with the password set to “abc123” – obviously use a different password for your own file

Creating a temporary tftp server to allow FortiGate to fetch the file

The fortigate wants to download the cert from a tftp server. There are lots of ways to do this, but what I’ve done for my uses is installed the tftpy module in python and then use that to transfer the file. This allows me to run the tftp server only while transferring the file, then get rid of it once done.

# make sure we have the module installed
# I'm using a virtualenv called tftp-venv
(tftp-venv) root@acmeserver #~> pip list
Package       Version
------------- -------
pip           20.0.2 
pkg-resources 0.0.0  
setuptools    44.0.0 
tftpy         0.8.0  

# run the tftp server:
(tftp-venv) root@acmeserver #~> python -c 'import tftpy; tftpy.TftpServer("/root/tftp").listen("0.0.0.0", 69)'   

# This will then just wait, listening for tftp connections

Import the cert+key into the FortiGate

Now run the command to import the p12 into the fortigate:

# 10.1.1.11 == the ip of acmeserver
# cert.p12  == the filename we chose
# abc123    == obviously replace with your own p12 password
fortiwall # execute vpn certificate local import tftp cert.p12 10.1.1.11 p12 abc123

Done.

fortiwall #

You can now use this cert+key for your VPN or other uses. Hit <Ctrl+C> on your TFTP server prompt to kill it.

As a big script to run automatically:

This can be executed in a python script, run from the server holding the cert+key, to upload a new certificate whenever it changes:

#!/usr/bin/env python3

import paramiko
import time
import subprocess
import tempfile
import uuid
from pathlib import Path

# Config
__version__ = "1.0.0"
hostname = '10.1.1.19'
my_ip    = '10.1.1.11'
username = 'admin'
fg_password_file = '/root/.ssh/fortigate_pass'
cert_file = '/root/.acme.sh/*.h.jaytuckey.name_ecc/fullchain.cer'
key_file  = '/root/.acme.sh/*.h.jaytuckey.name_ecc/*.h.jaytuckey.name.key'

# read in the fortigate password
password = Path(fg_password_file).read_text().strip()
# temp dir to store the p12 file
tmpdir = tempfile.TemporaryDirectory()
# generate a temporary password for the p12
p12pass = str(uuid.uuid1())

print(f'generating p12 file from {cert_file} and {key_file} to {tmpdir.name}/cert.p12')
subprocess.run(['openssl','pkcs12','-passout',f'pass:{p12pass}','-export','-in',cert_file,'-inkey',key_file,'-out',f'{tmpdir.name}/cert.p12'], check=True)

print(f'starting up tftp server on dir {tmpdir.name}')
# Set up the virtualenv variables
env = { 'VIRTUAL_ENV': '/root/tftp-venv' } # Virtualenv with tftpy installed
p = subprocess.Popen(['/root/tftp-venv/bin/python', '-c', f'import tftpy; tftpy.TftpServer("{tmpdir.name}").listen("0.0.0.0", 69)'], env=env)


try:
    client = paramiko.SSHClient()  # create an SSH client
    # Set the policy to auto-connect to hosts not in known-hosts file
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    print(f"Connecting to fortigate on {hostname}")
    client.connect(hostname, username=username, password=password, look_for_keys=False, allow_agent=False)
    channel = client.invoke_shell(); time.sleep(5)
    command = f'execute vpn certificate local import tftp cert.p12 {my_ip} p12 {p12pass}\n'
    print('running command on fortigate:')
    print(command)
    channel.send(command); time.sleep(5)
    response = channel.recv(999999).decode('utf-8'); time.sleep(5)
    print('got response:')
    print(response)
    client.close()
finally:
    # kill the tftp server
    p.terminate()
    p.wait(timeout=5)
    tmpdir.cleanup()  # clean up and delete the temp dir

Leave a Reply

Your email address will not be published. Required fields are marked *