Zertifikate von Let's Encrypt verwenden

Installation von acme-tiny

Mein Favorit der Let's Encrypt Clients ist acme-tiny. Es ist ein sehr kurzes Python-Skript. Das erleichtert einen Code-Review. Die Vorarbeiten für die Zertifikatserstellung werden leicht mit OpenSSL durchgeführt. Es benötigt keine root-Rechte und funktioniert gut. In der folgenden Beschreibung gehen wir davon aus, dass der Benutzer lets als normaler Benutzer auf einem OpenBSD-System mit nginx als Web-Server existiert.

Erstellen der Verzeichnisstruktur

Im Verzeichnis des Benutzers lets wird folgende Verzeichnisstruktur angelegt.

mkdir bin
mkdir src
mkdir reqs
mkdir certs

Clonen des Repositories

Damit man das Skript leicht installieren und eventuell auch aktualisieren kann wird das Repository in src geclont.

cd src
git clone https://github.com/diafygi/acme-tiny.git
cd

Das Skript kopiert man nach dem Code Review nach bin.

cp src/acme-tiny/acme_tiny.py bin
chmod +x bin/acme_tiny.py

Erstellen des privaten Schlüssels für den Let's Encrypt Account

Für den Account benötigt man ein Schlüsselpaar. Das kann man leicht mit dem folgenden Kommando erstellen. Aus Sicherheitsgründen sollte es auch nur für den Benutzer lesbar sein.

openssl genrsa 4096 > account.key
chmod 400 account.key

Erstellen eines Certificate Signing Requests (CSR)

Für jede Domain, für die Let's Encrypt ein Zertifikat ausstellen soll benötigt man einen Certificate Signing Request. Im Certificate Signing Request wird der öffentliche Schlüssel der Domain zum Unterschreiben an Let's Encrypt gesandt. Also müssen wir am besten für jede Domain einen eigenes Domain-Schlüsselpaar und einen Request erstellen.

openssl genrsa 4096 > example.key
openssl req -new -sha256 -key example.key -subj "/CN=example.com" > ./requests/example.csr

Jetzt wird der Schlüssel nur noch vom Server benötigt. Ich habe ihn als root (sudo/doas) in das Verzeichnis /etc/ssl/private verschoben.

Man kann auch ein Zertifikat für mehrere Domains in einem Zertifikat erstellen lassen. Das nutze ich zur Zeit aber nicht.

Konfiguration des Verzeichnisses für die Challenges

Let's Encrypt verifiziert mit einer Challenge, dass man die Kontrolle über die Domain für das Zertifikat hat. Dazu muss der Client eine Datei anlegen können, die der Let's Encrypt Server unter der Adresse http://domain/.well-known/acme-challenge/... abrufen kann. Dazu muss der Web-Server ein entsprechendes Verzeichnis haben.

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;
    }

    ...
}

Dieser Schritt hat mich den meisten Ärger gekostet, da meine Konfiguration des nginx als Reverse Proxy trotz scheinbar korrekter Pfadangabe irgendwie in den falschen Verzeichnissen gesucht hat. Eine Umstellung der Konfigurationsdateien hat da Wunder gewirkt. Man sollte nicht zu stark an den nginx-Konfigurationsdateien herumoptimieren.

Jetzt hat man die Vorarbeiten abgeschlossen und kann die Zertifikate anfordern.

Anfordern eines Zertifikats

Der folgende Aufruf fordert das Zertifikat für die Domain an.

python acme_tiny.py --account-key ./account.key --csr ~/requests/example.csr --acme-dir /var/www/challenges/ > ~/certs/example.crt

Für die Server benötigt man meistens eine Zertifikatskette. In der Zertifikatskette sind die Zertifikate der Domain und der unterschreibenden Certification Authority aneinandergehängt. Diese Kette kann man mit den folgenden Schritten erstellen.

curl -fsS https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem -o ~/certs/intermediate.pem
cat ~/certs/example.crt intermediate.pem > /etc/ssl/lets/example-chained.pem

Hier sieht man auch einen Kniff, den ich angewendet habe, um möglichst eine Trennung der Rechte zu erreichen. Der private Schlüssel der Domain (example.key), der nur zur Erstellung des Certificate Requests und später von Server verwendet wird liegt in einem Verzeichnis, auf das nur root zugreifen kann. Das Verzeichnis mit den Zertifikaten (/etc/ssl/lets/ gehört hingegen dem Benutzer lets, der nur einfacher Anwender ist. Alle dürfen auf das Verzeichnis nur lesend zugreifen.

Erneuern des Zertifikat

Da das Zertifikate nur 90 Tage gültig ist, muss man vor Ablauf der Frist ein neues anfordern. Das kann man wie oben beschrieben mit dem gleichen Aufruf von acme_tiny.py durchführen. Unter den unix-artigen Betriebssystemen kann man aber auch ein einfaches Skript erstellen und von cron ausführen lassen.

Das folgende Skript funktioniert unter OpenBSD und muss etwas für andere Unix-Artige angepasst werden. Nennen wir es renew_cert.sh.

#!/bin/sh
# Renew certificates with Let's Encrypt

base="/home/lets"
alias acme_tiny="$base/bin/acme_tiny.py"
chaineddir="/etc/ssl/acme"

accountkey="$base/account.key"
reqsdir="$base/reqs"
certsdir="$base/certs"
challengedir="/var/www/challenges"
intermediate_cert="$certsdir/intermediate.pem"
intermediate_cert_url="https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"

echo "Downloading intermediate certificate..."
# get intermediate certificate with curl
# -f: fail without writing html garbage to output file
# -s: silent mode without progress meter
# -S: output error message if download fails
curl -fsS $intermediate_cert_url -o $intermediate_cert && echo "Success."

# The script continues even if the download of the intermediate
# certificate fails. The old intermediate certificate should still
# exist and be useable.

for csr in $reqsdir/*.csr; do
  domain=`basename $csr .csr`
  echo "Requesting certificate for $domain..."
  acme_tiny --account-key $accountkey --csr $csr --acme-dir $challengedir > $certsdir/$domain.crt || (echo "Failed!" ; exit)
  echo "Success."
  cat $certsdir/$domain.crt $intermediate_cert >$chaineddir/$domain-chained.pem
done

echo "Reloading web server configuration..."
doas /usr/sbin/rcctl reload nginx || (echo "Failed!"; exit)
echo "Success."

Damit der Benutzer lets auch die Webserver-Konfiguration neu laden kann muss er entsprechende Rechte dafür haben. Das lässt sich unter OpenBSD schön für genau diesen Zweck eingeschränkt in der Datei `/etc/doas.conf` mit der folgenden Zeile konfigurieren. Unter anderen Betriebssystemen muss man entsprechend `sudo` konfigurieren und verwenden.

permit nopass lets as root cmd /usr/sbin/rcctl args reload nginx

Jetzt kann man noch den cron entsprechend konfigurieren, damit er einmal im Monat ein neues Zertifikat anfordert. Ich mag es, wenn ich dann Post bekomme.

MAILTO=cert@example.com
0 0 1 * * $HOME/bin/renew_certs.sh