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
geklont.
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 ein 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 zurzeit 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 herum optimieren.
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 Zertifikates
Da das Zertifikat 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