Let's Encrypt - Zertifikate automatisch auf einer FortiGate einspielen

Let's Encrypt bietet kostenlose TLS-Zertifkate an. Allerdings haben die nur eine Laufzeit von drei Monaten. Das bedeutet alle drei Monate ein neues Zertifkat anfordern und auf den betreffenden Geräten austauschen. Mit der Software certbot kann man das ganze bequem automatisieren. Sogar Dienste die das Zertifikat einbinden können damit neu gestartet werden. Was aber wenn man eine FortiGate hat, auf der kein Certbot läuft, aber man genau auf dieser die Zertifikate benötigt? Klar ganz einfach, ein wiederholender Termin für den Zertifikatstausch im Kalender, den man dann höchst wahrscheinlich bei der ersten Wiederholung nicht mehr mitbekommt oder etwas anderes wichtiges dazwischen kommt. Wäre doch schön, wenn der Certbot das machen könnte.

Warum eigentlich nicht.

Der Certbot kann auf einem Linux (oder auf welchem OS auch immer ;-)) via Cron täglich aufgerufen werden. Er prüft dann ob das Zertifkat abgelaufen und erneuert werden muss. Ist das Zertifkat abgelaufen erneuert er dieses und kann mit dem Schalter --post-hook 'CMD' einen Befehl ausführen, nachdem das neue Zertifkat vorliegt. "CMD" kann beispielsweise gegen ein "systemct reload smtpd.service" ersetzt werden um dem smtpd mitzuteilen das neue Zertifikat zu benutzen. Oder man kann an dieser Stelle auch ein Skript hinterlegen, welches dafür sorgt, dass die FortiGate ebenfalls mit dem neuen Zertifikat versorgt wird.

Das Skript

Was muss ein Skript - nennen wir es an dieser Stelle mal "fgt-crt" - können um ein Zertifikat auf eine Fortigate zu kopieren? Zunächst müssen wir die IP-Adresse bzw. den FQDN der FortiGate kennen. Auf dieser sollte auch ein SSH-Public-Key (für den Adminbenutzer auf der FGT) des Benutzers liegen mit dem wir das Zertifkat aufspielen wollen und zuguterletzt müssen wir wissen wo das Zertifkat auf unserem Rechner abgelegt ist. Diese Infos können wir schonmal in eine Konfigurationsdate schreiben. Gespeichert wird die Date unter /etc/fgt-crt.cfg oder im Benutzerverzeichnis unter ~/.fgt-crt.cfg.

# Where to finde the ssh private key?
SSH_KEY="/path/to/.ssh/id_rsa"

# Adminser to login with
TRGT_USER=admin

# Target IPs (separated by one whitespace)
TRGTS="192.168.x.y"

# Where to find the certificate and privatekey
CRTPATH="/etc/letsencrypt/live/fqdn"

Wir benutzen also (Überraschung!) ssh um das Zertifikat auf die FortiGate zu bekommen. Genaugenommen schreiben wir die CLI-Befehle für die Fortigate um ein neues Zertifkat abzuspeichen in eine Pipe die diese Befehle dann an eine ssh-Sitzung weiterleitet. Strukturiert betrachtet wird anfangs die Konfiguration eingelesen, dann das Zertifikat und der private Schlüssel in eine Variable eingelesen, ein schöner Name für das Zertifkat generiert und abschließend wird das Zertifikat auf der FortiGate abgelegt.

#!/bin/bash

# Find and read the configuration
set_config () {
        if [ -r /etc/fgt-crt.cfg ]; then
                # Check if configuration is found in /etc.
        # If yes set configvar
                FGTCFG=/etc/fgt-crt.cfg
        elif [ -r ~/.fgt-crt.cfg ]; then
                # If config is found in the backupuser 
        # home directory set it into the configvar
                FGTCFG=~/.fgt-crt.cfg
        else
                echo "No configuration file is found, please create one" \
        | /usr/bin/logger -s -i -t "FortiGate-Backup"
        exit 1
        fi
}

# Write certificate and privat key into vars
getCert () {
    CERTPEM=$(cat $CRTPATH/cert.pem)
    PKEYPEM=$(cat $CRTPATH/privkey.pem)
}

# Create a unique name
newCertName () {
    DAY=$(date +%d)
    MONTH=$(date +%m)
    YEAR=$(date +%y)
    CRTNAME="letsencrypt-$YEAR-$MONTH-$DAY"
}

# CLI-comands for FortiOS
fgtCreateNewCert () {
echo "config vpn certificate local"
sleep 1
echo "edit \"$CRTNAME\""
sleep 1
echo "set private-key \"$PKEYPEM\""
sleep 1
echo "set certificate \"$CERTPEM\""
sleep 1
echo "set range global"
sleep 1
echo "end"
sleep 1
echo "end"
sleep 1
echo "config firewall ssl-ssh-profile"
sleep 1
echo "edit \"Protect-Webserver\""
sleep 1
echo "set server-cert \"$CRTNAME\""
sleep 1
echo "end"
sleep 1
echo "exit"
}

# Run all parts and write certificate via ssh ti the fortigate
main () {
    set_config
    source $FGTCFG
    getCert
    newCertName
    for DEVICE in $TRGTS; do
        fgtCreateNewCert | ssh -t -t -i $SSH_KEY $TRGT_USER@$DEVICE 
    done
}

main

Ein kleiner Hinweis noch zu dem Skript: In der Konfiguration können mehere FortiGates angegeben werden. Diese sollten aber alle den selben administrativen Benutzer haben.

Aufruf des Skriptes mit certbot

Wir speichern das Skript einfach mal im Verzeichnis /usr/local/bin mit dem Namen fgt-crt ab. Der certbotaufruf in der Datei /etc/cron.d/certbot sollte dann um den Schalter --post-hook '/usr/local/bin/fgt-crt' erweitert werden.

Geprüft werdan kann das Ganze dann auch händisch indem das Skript auf der Kommandozeile aufgerufen wird. Danach sollte man das Zertifikat in der FortiGate finden. Wenn das Zertifikat dann im regulären Turnus vom certbot erneuert wird, sollte danach das neue Zertifikat auf der FortiGate in der Zertifikatsliste auftauchen.