BITS Tutorial zur Nutzung der Let's Encrypt DNS Alias Challenge

Dieses Tutorial erklärt, wie der Let's Encrypt Client acme.sh mit dem Plugin dns_nsupdate auf einem Linux-System installiert und zur Nutzung der "DNS-01 challenge" im DNS-Alias-Modus konfiguriert werden kann.

Seit dem 1. August 2021 verwendet der acme.sh-Client, bei Neuinstallationen, ZeroSSL als Standard-Zertifizierungsstelle. Aktuell ist jedoch nur Let's Encrypt berechtigt Zertifikate unter der Domain uni-bielefeld.de auszustellen. Wie Let's Encrypt als Standard-Zertifizierungsstelle für neue Installation konfiguriert werden kann, wird in der Ankündigung der Änderung beschrieben. Für bestehende Installationen ändert sich auch in Zukunft nichts. Diese verwenden weiterhin Let's Encrypt als Zertifizierungsstelle.

Um dem Tutorial folgen zu können, sollte man den grundlegenden Umgang mit einem Terminal und einer weitgehend POSIX-kompatiblen Shell beherrschen. Grundlegende Kenntnisse der Funktion von DNS und Resource Records sind hilfreich, jedoch nicht erforderlich und können im Zweifel unter den verlinkten Seiten erworben werden.

In diesem Tutorial werden nicht die Vor- und Nachteile der "DNS-01 challenge" diskutiert. Diese können unter vorstehendem Link nachgelesen werden.

Die Begriffe SSL-Zertifikat, TLS-Zertifikat oder nur Zertifikat werden in diesem Tutorial synonym verwendet. Der zum Zertifikat gehörende Schlüssel wird als Private-Key bezeichnet.

Umfeld

Im Folgenden werden die Begriffe Subdomain und Zone synonym verwendet. Es sei darauf hingewiesen, dass diese Begriffe in einem anderen Kontext eine unterschiedliche Bedeutung haben können.

Als Beispiel-Domain wird example.org mit den beiden Subdomains foo.example.org und bar.example.org verwendet. Der Nameserver für alle in diesem Tutorial verwendeten Domains lautet ns.example.org.

Von der BITS-Abteilung "Netze" wird ein sogenannter TSIG-Key bereitgestellt und ein External-Host benannt, um die TXT-Resource-Records für die DNS-Challenge erstellen zu können. In diesem Tutorial werden folgender Name und Wert für einen TSIG-Key angenommen:

Das Ziel soll nun sein, ein Zertifikat ausgestellt zu bekommen, welches für die beiden folgenden Hostnamen gültig ist:

Dabei werden zwei Fälle gezeigt werden, die Austellung eines Zertifikats mit einem Fully Qualified Domain Name (FQDN) und die Austellung eines Zertifikats mit zwei FQDNs.

In diesem Tutorial wird als Webserver Apache in der Distribution RHEL 7 verwendet. Die entsprechende Service-Unit lautet demnach httpd.service. Wird ein anderer Webserver oder eine andere Distribution verwendet, ist der Name der Service-Unit entsprechend anzupassen (z.B. nginx.service).

Installation des acme.sh-Clients

Um ein neu ausgestelltes bzw. erneuertes Zertifikat und den dazugehörigen Private-Key in einem Webserver zu verwenden, muss die Webserver-Konfiguration neu geladen werden. Dazu sind meist Root/sudoer-Rechte erforderlich. In diesem Tutorial wird der acme.sh-Client unter einen User mit sudo-Berechtigung installiert.

Hinweis: Das Projekt empfiehlt die Nutzung von sudo ausdrücklich nicht. Weitere Informationen finden sich unter dem Link: https://github.com/acmesh-official/acme.sh/wiki/sudo

Hier wird dennoch die Einrichtung unter einem User mit sudo-Berechtigung dokumentier, da wir die Ausführung das acme.sh-Clients mit minimalen Rechten für sinnvoll erachten. Befehle sind entsprechend mit oder ohne sudo auszuführen, je nachdem welcher User verwendet wird.

Zur Installation führt man folgende Kommandos aus (Quelle):

git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install

Durch die Installationsroutine werden folgende drei Aktionen durchgeführt:

  1. Erstellen und Kopieren von acme.sh in das HOME-Verzeichnis ($HOME): ~/.acme.sh/. Unterhalb dieses Verzeichnisses werden die ausgestellten Zertifikate abgelegt.
  2. Erstellen eines Alias für: acme.sh=~/.acme.sh/acme.sh.
  3. Erstellen eines täglichen cron job, welcher die Restlaufzeit der Zertifikate prüft und diese ggf. verlängert.

Beispiel eines Cron-Jobs:

1 0 * * * "/home/alice/.acme.sh"/acme.sh --cron --home "/home/alice/.acme.sh" > /dev/null

Nach der Installation meldet man sich am besten einmal neu an, damit der Alias für acme.sh genutzt werden kann.

Anlegen der CNAME-Records

Nun legen wir die CNAME-Records an, welche für den DNS-Alias-Mode benötigt werden (Quelle). Möchte man nur einen FQDN in das Zertifikat aufnehmen, ist lediglich ein Record zu erstellen.

_acme-challenge.host.foo.example.org IN CNAME _acme-challenge.acme.foo.example.org
_acme-challenge.mein-schoener-host.bar.example.org IN CNAME _acme-challenge.acme.foo.example.org

Beantragung, Ausstellen und Installation eines Zertifikats

Die Überschrift deutet bereits an, dass der folgende Prozess aus drei Schritten besteht. Ich beginne mit einem optionalen Schritt, in dem ich die notwendige Konfiguration für einen sudo-User erkläre.

Optional: Konfiguration für non-root-User

Wer den acme.sh-Client unter dem User root installiert hat, kann diesen Abschnitt überspringen. An dieser Stelle wird die Konfiguration eines sudo-Users beschrieben.

Verwendet wird dabei ein Useraccount, der bereits über sudo-Berechtigungen verfügt, sich bei der Verwendung von sudo jedoch mit einem Passwort authentisieren muss. Im Folgenden nennen wir diesen Useraccount Alice.

Anpassung der sudoers-Datei

Da für die Installation des Let's Encrypt Zertifikats in einem Webserver ein Reload der Konfiguration des Webservers erforderlich ist und dies später automatisch (ohne Passworteingabe) geschehen soll, ist eine Anpassung der sudoers-Datei von Alice erforderlich.

Die sudoers-Datei von Alice (/etc/sudoers.d/alice) sieht zu Beginn wie folgt aus:

alice  ALL=(ALL) ALL

Um Alice nun das Recht zu geben, die Webserver-Konfiguration ohne Passworteingabe neu zu laden, wird folgende Zeile hinzugefügt. Dabei ist die Reihenfolge der Zeilen zu beachten (siehe sudoers(5). Die Bearbeitung der Datei wird mit dem Kommando sudo visudo -f /etc/sudoers.d/alice durchgeführt.

alice  ALL=(ALL) ALL
alice  ALL=(ALL) NOPASSWD: /bin/systemctl reload httpd.service

Verzeichnis für TLS-Zertifikate und Private-Keys erstellen

Zur weiteren Nutzung durch den Webserver, sollen die Zertifikate und Private-Keys in einem Verzeichnis gespeichert werden, auf das Alice Schreibrechte hat. Der Verzeichnisname kann dabei abweichend vom folgenden Beispiel frei gewählt werden. Im Beispiel wird die Gruppe apache verwendet. Diese ist ggf. an das eigene System anzupassen:

sudo mkdir -m 750 /etc/letsencrypt
sudo chown alice.apache /etc/letsencrypt

Die folgenden Schritte sind für root und alice gleich. Nur muss alice einige Befehle halt mit sudo auführen. Diese sind entsprechend gekennzeichnet. root lässt ein führendes sudo einfach weg.

Zertifikat beantragen und austellen

Wie eingangs erwähnt, wird ein TSIG-Key und das Plugin dns_nsupdate genutzt, um dynamische Zonen-Updates auf dem Nameserver durchzuführen. Um den acme.sh-Client entsprechend zu konfigurieren, wird zuerst eine Key-Datei erstellt, welche den TSIG-Key in JSON-Notation enthält (vgl. Abschnitt Umfeld).

Wichtig: Der TSIK-Schlüssel erlaubt dynamische Zonen-Updates im DNS. Er ist geheim zu halten und bestmöglich zu schützen.

Der Name der Datei ist dabei frei wählbar. Ich empfehle die Datei im HOME-Verzeichnis des Users abzulegen, welcher den acme.sh-Client ausführt und die Datei nur für diesen User lesbar zu machen (chmod 0400 DATEINAME). Beispiel:

cat ~/.nsupdate.key
key "acme-key" {
  algorithm hmac-sha512;
  secret "SuperGeheim1111elf==";
};

chmod 0400 ~/.nsupdate.key

Als nächstes werden notwendige Informationen über den Nameserver und unseren TSIG-Key verfügbar gemacht. Die folgenden Kommandos sind einmalig auszuführen. Die Werte werden vom acme.sh-Client später automatisch in ~/.acme.sh/account.conf gespeichert (Quelle).

export NSUPDATE_SERVER="ns.example.org"
export NSUPDATE_KEY="/home/alice/.nsupdate.key"

Nun ist es endlich soweit, dass ein Zertifikat ausgestellt werden kann. Für die in diesem Tutorial verwendeten Werte lautet der Befehl dazu wie folgt:

Fall 1: Ein FQDN in einem Zertifikat
acme.sh --issue --dns dns_nsupdate -d host.foo.example.org --challenge-alias acme.foo.example.org
Fall 2: Zwei FQDNs in einem Zertifikat
acme.sh --issue --dns dns_nsupdate -d host.foo.example.org -d mein-schoener-host.bar.example.org --challenge-alias acme.foo.example.org

Der folgende Code-Block zeigt ein konkretes Beispiel unter Verwendung der Staging-Umgebung.

acme.sh --issue --staging --dns dns_nsupdate -d host.foo.example.org -d mein-schoener-host.bar.example.org --challenge-alias acme.foo.example.org

[Fr 4. Sep 09:52:41 CEST 2020] Using ACME_DIRECTORY: https://acme-staging-v02.api.letsencrypt.org/directory
[Fr 4. Sep 09:52:42 CEST 2020] Using CA: https://acme-staging-v02.api.letsencrypt.org/directory
[Fr 4. Sep 09:52:42 CEST 2020] Create account key ok.
[Fr 4. Sep 09:52:42 CEST 2020] Registering account: https://acme-staging-v02.api.letsencrypt.org/directory
[Fr 4. Sep 09:52:43 CEST 2020] Registered
[...]
[Fr 4. Sep 09:52:43 CEST 2020] Creating domain key
[Fr 4. Sep 09:52:44 CEST 2020] The domain key is here: /home/alice/.acme.sh/host.foo.example.org/host.foo.example.org
.key
[Fr 4. Sep 09:52:44 CEST 2020] Multi domain='DNS:host.foo.example.org,DNS:mein-schoener-host.bar.example.org'
[Fr 4. Sep 09:52:44 CEST 2020] Getting domain auth token for each domain
[Fr 4. Sep 09:52:46 CEST 2020] Getting webroot for domain='host.foo.example.org'
[Fr 4. Sep 09:52:46 CEST 2020] Getting webroot for domain='mein-schoener-host.bar.example.org'                                             
[Fr 4. Sep 09:52:46 CEST 2020] Adding txt value: 2tG2NCvV23KGUNCSGeVO3P_UVaOVAG4t0ehbv1cJbtY for domain:  _acme-challenge.acme.foo.example.org
[Fr 4. Sep 09:52:46 CEST 2020] adding _acme-challenge.acme.foo.example.org. 60 in txt "2tG2NCvV23KGUNCSGeVO3P_UVaOVAG4t0ehbv1cJbtY"
[Fr 4. Sep 09:52:46 CEST 2020] The txt record is added: Success.
[Fr 4. Sep 09:52:46 CEST 2020] Adding txt value: yFbL8EF6xQre3v3RfYiCYQ8X4KH0gykagD9_oRfIvgA for domain:  _acme-challenge.acme.foo.example.org
[Fr 4. Sep 09:52:46 CEST 2020] adding _acme-challenge.acme.foo.example.org. 60 in txt "yFbL8EF6xQre3v3RfYiCYQ8X4KH0gykagD9_oRfIvgA"
[Fr 4. Sep 09:52:46 CEST 2020] The txt record is added: Success.
[Fr 4. Sep 09:52:46 CEST 2020] Let's check each DNS record now. Sleep 20 seconds first.
[Fr 4. Sep 09:53:07 CEST 2020] Checking host.foo.example.org for _acme-challenge.acme.foo.example.org                       
[Fr 4. Sep 09:53:07 CEST 2020] Not valid yet, let's wait 10 seconds and check next one.
[Fr 4. Sep 09:53:20 CEST 2020] Checking mein-schoener-host.bar.example.org for _acme-challenge.acme.foo.example.org
[Fr 4. Sep 09:53:21 CEST 2020] Domain mein-schoener-host.bar.example.org '_acme-challenge.acme.foo.example.org' success.
[Fr 4. Sep 09:53:21 CEST 2020] Let's wait 10 seconds and check again.
[Fr 4. Sep 09:53:32 CEST 2020] Checking host.foo.example.org for _acme-challenge.acme.foo.example.org
[Fr 4. Sep 09:53:32 CEST 2020] Domain host.foo.example.org '_acme-challenge.acme.foo.example.org' success.
[Fr 4. Sep 09:53:32 CEST 2020] Checking mein-schoener-host.bar.example.org for _acme-challenge.acme.foo.example.org
[Fr 4. Sep 09:53:32 CEST 2020] Already success, continue next one.                                                                          
[Fr 4. Sep 09:53:32 CEST 2020] All success, let's return
[Fr 4. Sep 09:53:32 CEST 2020] Verifying: host.foo.example.org
[Fr 4. Sep 09:53:35 CEST 2020] Pending
[Fr 4. Sep 09:53:38 CEST 2020] Success
[Fr 4. Sep 09:53:38 CEST 2020] Verifying: mein-schoener-host.bar.example.org                                                               
[Fr 4. Sep 09:53:41 CEST 2020] Success
[Fr 4. Sep 09:53:41 CEST 2020] Removing DNS records.
[...]
[Fr 4. Sep 09:53:41 CEST 2020] Removed: Success
[Fr 4. Sep 09:53:41 CEST 2020] Verify finished, start to sign.
[Fr 4. Sep 09:53:41 CEST 2020] Lets finalize the order.
[Fr 4. Sep 09:53:41 CEST 2020] Le_OrderFinalize='https://acme-staging-v02.api.letsencrypt.org/acme/finalize/15482274/142528495'
[Fr 4. Sep 09:53:42 CEST 2020] Downloading cert.
[Fr 4. Sep 09:53:42 CEST 2020] Le_LinkCert='https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa6e5a9922acccb49619cc8808f6f0916dc0'
[Fr 4. Sep 09:53:43 CEST 2020] Cert success.
[...]
[Fr 4. Sep 09:53:43 CEST 2020] Your cert is in  /home/alice/.acme.sh/host.foo.example.org/host.foo.example.org.cer
[Fr 4. Sep 09:53:43 CEST 2020] Your cert key is in  /home/alice/.acme.sh/host.foo.example.org/host.foo.example.org.key
[Fr 4. Sep 09:53:43 CEST 2020] The intermediate CA cert is in /home/alice/.acme.sh/host.foo.example.org/ca.cer
[Fr 4. Sep 09:53:43 CEST 2020] And the full chain certs is there:  /home/alice/.acme.sh/host.foo.example.org/fullchain.cer

Damit liegen alle benötigten Dateien auf unserem Host. Im nächsten Schritt werden diese in die korrekte Lokation installiert.

Installation der Zertifikate für Apache

Das folgende Beispiel gilt für den Webserver Apache und ist der Dokumentation des acme.sh-Clients entnommen. Auf der verlinkten Seite findet sich ein weiteres Beispiel für den Webserver NGINX.

Das Kommando im folgenden Beispiel enthält am Ende den Parameter --reloadcmd "sudo systemctl reload httpd.service. Das sudo ist notwendig, wenn man einen User mit sudo-Berechtigungen wie z.B. Alice nutzt (siehe oben). Als root lässt man sudo einfach weg. Ggf. muss der Name der Service-Unit an das eigene System angepasst werden.

acme.sh --install-cert -d host.foo.example.org --cert-file /etc/letsencrypt/host.foo.example.org.cer --key-file /etc/letsencrypt/host.foo.example.org.key --fullchain-file /etc/letsencrypt/host.foo.example.org.fullchain.cer --reloadcmd "sudo systemctl reload httpd.service"

Hat alles funktioniert, erzeugt obiger Befehl folgende Ausgabe:

[Fr 4. Sep 12:36:22 CEST 2020] Installing cert to:/etc/letsencrypt/host.foo.example.org.cer
[Fr 4. Sep 12:36:22 CEST 2020] Installing key to:/etc/letsencrypt/host.foo.example.org.key
[Fr 4. Sep 12:36:22 CEST 2020] Installing full chain to:/etc/letsencrypt/host.foo.example.org.fullchain.cer
[Fr 4. Sep 12:36:22 CEST 2020] Run reload cmd: sudo systemctl reload httpd.service
[Fr 4. Sep 12:36:22 CEST 2020] Reload success

Hinweis: Der Webserver bzw. VirtualHost des Webservers muss so konfiguriert werden, dass die Zertifikate aus dem entsprechenden Pfad auch verwendet werden. Diese Konfiguration ist nicht Gegenstand dieses Tutorials. Es wird hierzu auf die Dokumentation des genutzten Webservers verwiesen.

Die Dateiberechtigungen für oben installierte Dateien sind nach der Installation einmalig zu korrigieren und ggf. mit chown und chmod so zu konfigurieren, dass nur Alice und der Webserver-User Zugriff darauf haben. Wichtig: Der Private-Key darf unter keinen Umständen von allen Usern gelesen werden dürfen. Folgender Code-Block zeigt eine mögliche Berechtigung:


ls -l /etc/letsencrypt/
total 12
-rw-r-----. 1 alice apache 1964  4. Sep 15:09 host.foo.example.org.cer
-rw-r-----. 1 alice apache 3644  4. Sep 15:09 host.foo.example.org.fullchain.cer
-rw-r-----. 1 alice apache 1679  4. Sep 15:09 host.foo.example.org.key

Die Dateiberechtigungen bleiben bei einer Erneuerung des Zertifikats und beim Überschreiben der existierenden Dateien erhalten.

Die vorgenommenen Einstellungen und Befehle werden in der Datei ~/.acme.sh/account.conf gespeichert. In der Standard-Einstellung wird das Zertifikat automatisch alle 60 Tage erneuert, in den angegebenen Pfaden installiert und ein Neustart des Webservers durchgeführt.

Weiterführende Quellen und Links

  1. A Technical Deep Dive: Securing the Automation of ACME DNS Challenge Validation: https://www.eff.org/deeplinks/2018/02/technical-deep-dive-securing-automation-acme-dns-challenge-validation
  2. How-to install acme.sh: https://github.com/acmesh-official/acme.sh/wiki/How-to-install
  3. Install from Git: https://github.com/acmesh-official/acme.sh#2-or-install-from-git
  4. Set up Let’s Encrypt certificate using acme.sh as non-root user: https://gist.github.com/Greelan/28a46a33140b65c9a045573ca460f044