Seit der Firmware 9.6 unterstützt die Sophos UTM das automatisierte Verwalten von Let's Encrypt-Zertifikaten, um diese z.B. im WAF aber auch anderen Bereichen zu verwenden.
Leider nur ACMEv1, also keine Wildcard-Zertifikate. Aber immerhin gehen SAN-Zertifikate mit ziemlich vielen Alternate Subject Names (bei mir aktuell 16).
Nun hat man das Zertifikat auf der UTM, aber wie bekommt man es auf andere Systeme, z.B. den Webserver oder das Mailsystem?
Dafür habe ich mir quick & dirty ein Bash-Script zusammengebaut, das ein bestehendes Zertifikat mit dem auf der UTM vergleicht und bei Abweichung das der UTM herunterlädt und das lokale ersetzt.
Anschließend startet es die beteiligten Dienste neu, damit diese auch das neue Zertifikat verwenden.
Vorarbeiten
UTM API
Auf der UTM muss die API aktiviert und ein API-Key angelegt sein.
Außerdem benötigt man die REF-ID des abzufragenden Zertifikats. Diese bekommt man am Leichtesten, indem man sich an der API mit einem Browser anmeldet, oben rechts den Bereich "ca" auswählt und dann im Bereich "ca/host_key_cert" den GET des "/objects/ca/host_key_cert/" per "Try it out!" ausführt.
Dann die REF-ID des zugehörigen Zertifikatnamens im "Response Body" suchen.
Zielsystem
Auf dem Linux-System müssen cURL, jq und die mailutils installiert sein, außerdem muss das System in der Lage sein, Mails zu versenden (z.B. Postfix als Satellite oder Nullmailer und Konsorten).
Des Weiteren muss das Intermediate-CA-Zertifikat von Let's Encrypt passend im System vorhanden sein und im Script angegeben werden, falls die verwendete Software nicht die explizite Verwendung eines solchen erlaubt. Dovecot z.B. kennt keine Möglichkeit, ein Intermediate CA separat anzugeben, es muss im Public Cert mit eingefügt werden. Glücklicherweise können auch die meisten anderen System damit umgehen (z.B. Apache), so dass das kein Problem ist.
Falls man nur Programme betreibt, die eine separate Option für Intermediate CAs erlauben kann man diese Möglichekit natürlich nutzen und im Script die Variable intermediateca=false
setzen.
Script
/usr/local/bin/getcert_utm.sh
#!/bin/bash
# Fetch Let'sEncrypt cert from UTM and check if different to local.
# If yes, overwrite and reload affected services
# Needs curl, jq and mailutils installed
log="/usr/bin/logger -i -p local0.info -t getcert_utm.sh"
log_dbg="/usr/bin/logger -i -p local0.debug -t getcert_utm.sh"
log_err="/usr/bin/logger -i -p local0.warning -t getcert_utm.sh"
#debug=true
$log "Starting getcert_utm.sh"
utmapiurl="https://<myutm.mydomain.de>:4444/api/objects/ca/host_key_cert/<REF-ID of LE-cert>"
utmapikey="<your API key here>"
# do not use whitespaces or special characters in paths!
curloutfile="/tmp/utm-le-cert_all"
tmpkeyfile="/tmp/utm-le-cert.key"
tmpcertfile="/tmp/utm-le-cert.pem"
# must match the path and certificate name in your apache, dovecot, etc. config
dstkeyfile="/etc/ssl/private/mydomain.de_SAN_LE.key"
dstcertfile="/etc/ssl/mydomain.de_SAN_LE.pem"
# false (if can be used separately) or path to intermediate
intermediateca="/etc/ssl/lets-encrypt-x3-cross-signed.pem"
tmpmailtext="/tmp/mailtext.txt"
mailsubject="LE-Cert-Fetcher on "$(hostname)": "
mailto="<your email address>"
mailresult="Nothing changed"
restartservices=false
# services to restart after certificate change
services="dovecot.service postfix.service nginx.service"
errtext=""
errdetected=false
#API key to base64 for auth header
utmapikeybase64=$(echo "token:${utmapikey}" | base64)
certsubject=$(/usr/bin/openssl x509 -text -noout -in $dstcertfile |grep -oP '(?<=Subject: )[^,]+')
certvalidity=$(/usr/bin/openssl x509 -text -noout -in $dstcertfile |grep -oP '(?<=Not After : )[^,]+')
echo -e "Hello!\n" > $tmpmailtext
echo -e "This is to inform you about recent changes in our Let's Encrypt certificate with $certsubject:\nValid until: $certvalidity\n" >> $tmpmailtext
$log "Downloading cert from UTM API."
curl -k -X GET --header "Accept: application/json" --header "Authorization: Basic ${utmapikeybase64}" "${utmapiurl}" -o "$curloutfile" > /tmp/getcert_utm_curl 2>&1
if [ $? -eq 0 ]; then
$log "Extracting key and certificate from output."
jq -r '.key' $curloutfile > $tmpkeyfile
res1=$?
jq -r '.certificate' $curloutfile > $tmpcertfile
res2=$?
if [ $res1 -eq 0 ] && [ $res2 -eq 0 ]; then
$log "Comparing new and old files."
diff $tmpkeyfile $dstkeyfile
if [ $? -eq 0 ]; then
$log "Key has not changed."
echo "Key has not changed." >> $tmpmailtext
else
$log "Key has changed. Copying new key."
cp $tmpkeyfile $dstkeyfile
if [ $? -eq 0 ]; then
restartservices=true
mailresult="Key changed"
echo "Key has been exchanged by a newer version." >> $tmpmailtext
else
errdetected=true
errtext=$errtext"Error copying key to destination. "
mailresult="Error"
echo "ERROR copying key to destination" >> $tmpmailtext
fi
fi
if [ $intermediateca != false ]; then
$log "Appending intermediate ca to certificate."
cat $intermediateca >> $tmpcertfile
fi
diff $tmpcertfile $dstcertfile
if [ $? -eq 0 ]; then
$log "Certificate has not changed."
echo "Certificate has not changed." >> $tmpmailtext
else
$log "Certificate has changed. Copying new key."
cp $tmpcertfile $dstcertfile
if [ $? -eq 0 ]; then
restartservices=true
mailresult="Certificate changed"
echo "Certificate has been exchanged by a newer version." >> $tmpmailtext
else
errdetected=true
errtext=$errtext"Error copying certificate to destination. "
mailresult="Error"
echo "ERROR copying certificate to destination" >> $tmpmailtext
fi
fi
if [ "$errdetected" = true ]; then
mailresult="Error"
$log_err "Failure in setting certs: $errtext"
fi
else
mailresult="Error extracting cert"
echo "Could not extract cert or key from output. Check format of output, should be JSON containing .key and .certificate section." >> $tmpmailtext
$log_err "Could not extract cert or key from output. Check format of output, should be JSON containing .key and .certificate section."
fi
else
mailresult="Error getting cert"
echo "Could not connect to $utmapiurl or error in authentication. Check connection and URL settings." >> $tmpmailtext
$log_err "Could not connect to $utmapiurl or error in authentication. Check connection and URL settings."
fi
certdetails=$(/usr/bin/openssl x509 -text -noout -in $dstcertfile)
echo -e "\nBest regards,\nroot\n\n----------\n\nCertificate details:\n\n$certdetails" >> $tmpmailtext
mail -s "${mailsubject}${mailresult}" $mailto < "${tmpmailtext}"
rm "${curloutfile}" "${tmpkeyfile}" "${tmpcertfile}" "${tmpmailtext}"
if [ "$restartservices" = true ]; then
$log "Reloading \"$services\" as some data has changed"
systemctl reload $services
fi
$log "Finished."
#Finished
Finally
Am Schluss muss man das Script natürlich noch irgendwie regelmäßig ausführen, z.B. mit einem täglichen Cron-Job, oder SystemD, oder...