Schlagwort-Archive: Let’s Encrypt

Let’s Encrypt: Nutzung des DNS-Alias-Modus mit dem acme.sh-Client

Dieses Tutorial erklärt, wie der Let’s Encrypt Client (LE-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.

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. Um dem acme.sh-Client keinen direkten Zugriff auf diese DNS-Zonen geben zu müssen, hat der DNS-Anbieter die DNS-Zone acme.foo.example.org definiert, welche für den Zweck der DNS-Challenge verwendet wird.

Der Nameserver für alle in diesem Tutorial verwendeten Domains lautet ns.example.org.

Vom DNS-Anbieter wird ein sogenannter TSIG-Schlüssel bereitgestellt, welcher berechtigt ist TXT-Ressource-Records in der oben genannten DNS-Zone acme.foo.example.org anlegen zu können. In diesem Tutorial werden folgender Name und Wert für einen TSIG-Schlüssel angenommen:

  • TSIG-NAME: acme-key
  • TSIG-WERT: SuperGeheim1111elf==
  • TSIG-Algorithmus: hmac-sha512

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

  • host.foo.example.org
  • mein-schoener-host.bar.example.org

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).

Sinn und Zweck des DNS-Alias-Modus

Der DNS-Alias-Modus kann verwendet werden, wenn man einem Programm wie dem acme.sh-Client keinen direkten API-Zugriff auf die DNS-Zonen der eigenen Domain geben möchte. In diesem Abschnitt werde ich beispielhaft erklären, warum eine Nutzung dieses Modus angeraten erscheint.

Bevor Let’s Encrypt ein Zertifikat ausstellt, muss der anfragende Client beweisen, dass er die administrative Kontrolle über die Domain hat. Im Falle der DNS-Challenge geschieht dies indem ein TXT-Record in der jeweiligen DNS-Zone erstellt wird. Der LE-Client benötigt somit Zugriff mit Schreibberechtigung auf die entsprechenden DNS-Zonen. Dies kann jedoch verheerende Folgen haben, wenn der Host mit dem LE-Client kompromittiert wird. Ein Angreifer könnte hierdurch API-Zugang erlangen und die Informationen einer (oder mehrere) DNS-Zonen löschen. Oder noch schlimmer, die Einträge manipulieren bzw. eigene Einträge hinzufügen.

In unserer Organisation hat daher nur autorisiertes Personal mit personalisierten Zugangsdaten Zugriff auf ausgewählte Zonen.

Eine wichtige Eigenschaft von personalisierten Zugangsdaten ist, dass diese nur von der Person verwendet werden, an die sie ausgehändigt wurden. Diese sollten tunlichst nicht in einen LE-Client eingetragen werden, den man anschließend für Monate oder Jahre sich selbst überlässt.

Der DNS-Alias-Modus funktioniert so, dass der DNS-Anbieter eine Zone einrichtet, in der dynamische Updates durch LE-Clients gestattet sind. Die Authentifizierung und Autorisierung kann mittels sogenannter TSIG-Schlüssel durchgeführt werden.

Als Sysadmin erstellt man nun einen CNAME-Eintrag in der eigentlichen DNS-Zone, z.B. foo.example.org welcher auf die Zone acme.foo.example.org zeigt. Der LE-Client legt während der DNS-Alias-Challenge eine TXT-Record in der Zone acme.foo.example.org an. Let’s Encrypt kann nun über den CNAME den erstellten TXT-Record validieren. Anschließend wird das Zertifikat an den LE-Client ausgestellt.

Wird der LE-Client kompromittiert, sind damit zwar auch die auf ihm gespeicherten Private-Keys kompromittiert (was schon schlimm genug ist), es kann jedoch darüber hinaus nur die sogenannte DNS-Alias-Zone manipuliert werden. Eine Manipulation der eigentlichen Zone bleibt ausgeschlossen.

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 einem 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

Da ich glaube, zu wissen, was ich tue, werde ich hier optional die Einrichtung unter einem User mit sudo-Berechtigung dokumentieren. 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).

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

Der vom DNS-Anbieter bereitgestellte TSIG-Schlüssel ist ausschließlich zum Erstellen von Records in der Zone acme.foo.example.org berechtigt. Mit dem CNAME-Record weisen wir quasi nach, dass wir die Kontrolle über die DNS-Zone haben und dort Ressource-Records erstellen dürfen, während der acme.sh-Client nur berechtigt ist Einträge in jenen Zonen zu erstellen, auf welche die CNAME-Records verweisen.

Dies geschieht aus Sicherheitsaspekten. Auf diese Art und Weise wird verhindert, dass ein kompromittierter Host mit acme.sh-Client die wichtigen Zonen überschreiben kann.

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-Schlüssel 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-Schlüssel in JSON-Notation enthält (vgl. Abschnitt Umfeld).

Wichtig: Der TSIG-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:

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

Protokoll zum Ausfall eines Webdienstes nach der fehlgeschlagenen Erneuerung eines Let’s Encrypt Zertifikats

In diesem Beitrag werden die Ursache und der Verlauf der Störung protokolliert, welche die vorübergehende Nichterreichbarkeit einer Seafile[1. Seafile – Wikipedia]-Installation zur Folge hatte, welche ich für den Freifunk Lippe[2. Webpräsenz Freifunk Lippe] betreibe.

Beschreibung der Umgebung

Die Seafile-Installation wird ausführlich in „Installation von Seafile auf einem Ubuntu/Debian Server“ beschrieben. Die aktuelle Installation wurde wie dort erwähnt zusätzlich mit HSTS (TLS-Kochbuch, Abschnitt 2.7) und HPKP (TLS-Kochbuch, Abschnitt 2.8) gesichert. Das benötigte TLS-Zertifikat stammt von Let’s Encrypt (Abschnitt 3.4.1, TLS-Kochbuch).

Zur automatisierten Verlängerung wird das Skript smartrenew.sh verwendet, welches in Abschnitt 5.4 im TLS-Kochbuch beschrieben wird.

Eingang der Störungsmeldung

Die Störungsmeldung ging am 04.02.2017 per E-Mail. Die Meldung wies auf einen Fehler bei der Erneuerung des Zertifikats durch Let’s Encrypt hin:


arsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying seafile.example.com...
Traceback (most recent call last):
  File "acme-tiny-by-frezbo/acme_tiny.py", line 200, in 
    main(sys.argv[1:])
  File "acme-tiny-by-frezbo/acme_tiny.py", line 196, in main
    signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, verifychallenge=args.verifychallenge, log=LOGGER, CA=args.ca)
  File "acme-tiny-by-frezbo/acme_tiny.py", line 150, in get_crt
    domain, challenge_status))
ValueError: seafile.example.com challenge did not pass: {u'status': u'invalid', u'validationRecord': [{u'url': u'http://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'hostname': u'seafile.example.com', u'addressUsed': u'IPv4-Adresse', u'port': u'80', u'addressesResolved': [u'IPv4-Adresse', u'IPv6-Adresse']}, {u'url': u'https://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'hostname': u'seafile.example.com', u'addressUsed': u'IPv4-Adresse', u'port': u'443', u'addressesResolved': [u'IPv4-Adresse', u'IPv6-Adresse']}], u'keyAuthorization': u'ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk.pW0Gip0L_WLx7XEnmH_3ZArt9Vi4TbokaUuSXsc1dm4', u'uri': u'https://acme-v01.api.letsencrypt.org/acme/challenge/u5hporNk0I5YunYlQQeJPKdRyd-9BKGMWjBhm_7Za9E/578797992', u'token': u'ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'error': {u'status': 403, u'type': u'urn:acme:error:
 unauthorized', u'detail': u'Invalid response from http://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk: "\r\n404 Not Found\r\n\r\n404 Not Found\r\n"'}, u'type': u'http-01'}
 * Reloading nginx configuration nginx
   ...fail!

Der Fehlermeldung ist zu entnehmen, dass die ACME-Challenge nicht verifiziert werden konnte, da der Server auf die Anfrage mit der Meldung „404 Not Found“ antwortete. Dieser Fehler konnte durch Aufruf der URL mit dem Programm wget bestätigt werden.

Ergebnis der Systemanalyse

Die Analyse der NGINX-Konfiguration ergab, dass der von Let’s Encrypt signierte öffentliche Schlüssel des Zertifikats nicht zum dazugehörenden privaten Schlüssel passte. Dies hatte zur Folge, dass sich die Webseite im Browser, der HSTS und HPKP unterstützt, nicht mehr aufgerufen werden konnte. Denn durch HSTS wird auch ein Aufruf der Seite über HTTP automatisch auf HTTPS umgeleitet. Hier wird nun durch HPKP ebenfalls festgestellt, dass der gepinnte Key nicht mit dem vom Webserver ausgelieferten übereinstimmte und der Browser verweigerte den Aufruf der Webseite.

Die Ursache hierfür lag in der fehlenden Fehlerbehandlung im folgenden Skript:


python acme-tiny-by-frezbo/acme_tiny.py --no-verify --account-key /var/www/seafile.example.com/ssl/account.key --csr /var/www/seafile.example.com/ssl/seafile.example.com.csr --acme-dir /var/www/seafile.example.com/public/.well-known/acme-challenge/ > /var/www/seafile.example.com/ssl/seafile.example.com.crt

cat /var/www/seafile.example.com/ssl/seafile.example.com.crt /var/www/seafile.example.com/ssl/lets-encrypt-x3-cross-signed.pem > /var/www/seafile.example.com/ssl/seafile.example.com_chained.crt

sudo service nginx reload

Zuerst versucht das Skript, das Zertifikat zu erneuern und in die Datei seafile.example.com.crt zu schreiben. Schlägt dieser Vorgang fehl, wird durch den zweiten Teil des Skripts trotzdem aus der korrupten Datei und dem Intermediate-Zertifikat die Zertifikatskette erstellt und in der Datei seafile.example.com_chained.crt gespeichert.

In diesem Fall trat bei der Erneuerung des Zertifikats ein Fehler auf. Es wurde jedoch trotzdem eine Datei namens seafile.example.com.crt mit einer Größe von 0 Byte erstellt. Daher fehlte das Zertifikat auch in der daraufhin erstellten Zertifikatskette. Dies hatte zur Folge, dass durch HPKP der Webbrowser den Zugriff auf die Domain verweigerte und die Anwendung nicht mehr erreichbar war.

Eine Überprüfung der Datensicherung ergab leider, dass das Verzeichnis mit den Zertifikatsdateien nicht Bestandteil des Backups war. Somit war die schnelle Wiederherstellung der Zertifikatskette nicht möglich.

Störungsbeseitigung

Um die Störung zu beseitigen, wurde zuerst die bestehende vHost-Konfiguration für die Seafile-Instanz deaktiviert. Anschließend wurde ein neuer vHost eingerichtet, der ausschließlich via HTTP das Verzeichnis ausliefert, welches die ACME-Challenge beinhaltet:


server {
        listen       80;
        server_name  seafile.example.com;

        root /var/www/seafile.example.com/public/;
        location / {
        index index.html index.htm index.php;
        }
}

Nun wurde das Skript zur Erneuerung des Let’s Encrypt Zertifikats erneut gestartet. Das Skript lief diesmal fehlerfrei durch und es wurde wieder eine vollständige Zertifikatskette erstellt und in der vorgesehenen Datei gespeichert.

Daher konnte die temporäre vHost-Konfiguration nun wieder deaktiviert und die ursprüngliche Konfiguration wieder aktiviert werden.

Maßnahmen zur Risikominimierung

Nachdem mich die Störung natürlich zur ungünstigsten Zeit (welche Zeit ist für eine Störung schon günstig?) erwischt hat, habe ich mir Gedanken gemacht, wie sich das Risiko minimieren lässt, dass mich der gleiche Ärger in Zukunft nochmal ereilt.

Datensicherung anpassen

Zuerst habe ich meine Datensicherung angepasst und das Verzeichnis, welches die Zertifikatsdateien enthält, mit ins Backup aufgenommen. Selbstverständlich ist das Backup nur die halbe Miete. Ob sich dieses erfolgreich wiederherstellen lässt, muss natürlich auch getestet werden.

Fehler bei der Skript-Ausführung abfangen

Wie weiter oben bereits beschrieben, begann die ganze Problematik damit, dass das Skript zur Zertifikatserneuerung weiterarbeitete, obwohl bereits bei der Erneuerung des Zertifikats ein Fehler aufgetreten war.

Zukünftig soll das Skript abbrechen, wenn bei der Verarbeitung ein Fehler auftritt. Dazu wurde es wie folgt erweitert:


function check() {
  if [ $1 -gt 0 ]; then
    echo "Uuups, hier ist was schiefgegangen"
    echo "exit $1"
    exit 1
  fi
}

python acme-tiny-by-frezbo/acme_tiny.py --no-verify --account-key /var/www/seafile.example.com/ssl/account.key --csr /var/www/seafile.example.com/ssl/seafile.example.com.csr --acme-dir /var/www/seafile.example.com/public/.well-known/acme-challenge/ > /var/www/seafile.example.com/ssl/seafile.example.com.crt

check $?

cat /var/www/seafile.example.com/ssl/seafile.example.com.crt /var/www/seafile.example.com/ssl/lets-encrypt-x3-cross-signed.pem > /var/www/seafile.example.com/ssl/seafile.example.com_chained.crt

check $?

sudo service nginx reload

Schlägt die Erneuerung des Zertifikats fehl, gibt das Kommando einen Exit-Status größer Null zurück. Dies wird von der check-Funktion erkannt und das Skript abgebrochen. Dadurch bleiben die alte Datei mit der Zertifikatskette erhalten und der Dienst bleibt verfügbar, bis das Problem mit der Zertifikatserneuerung behoben werden kann.

ejabberd mit Let’s Encrypt Zertifikat

In diesem Artikel wird dokumentiert, wie ein Let’s Encrypt[1. https://letsencrypt.org/ {en}] [2. Let’s Encrypt – Wikipedia] Zertifikat für ejabberd eingerichtet werden kann. Dabei stütze ich mich auf mein TLS-Kochbuch[3. TLS/SSL Kochbuch: Rezepte für die Verwendung von OpenSSL, HTTP Strict Transport Security (HSTS) und HTTP Public Key Pinning (HPKP)].

Die Dokumentation bezieht sich dabei auf meine konkrete Installation und ist nicht ohne Transferleistung auf andere Installationen übertragbar. Es ist also keine Schritt-für-Schritt-Anleitung! Der Artikel bietet über die Dokumentation hinaus jedoch auch Hintergrundinformationen und Wissenswertes für abweichende Konfigurationen.

Ausgangssituation

In meiner Umgebung ist ejabberd auf einem Ubuntu Server 14.04 LTS[4. Trusty Tahr – wiki.ubuntuusers.de] installiert und nutzt ein TLS-Zertifikat von CAcert[5. https://www.cacert.org/ {en}] [6. CAcert – Wikipedia].

Außer ejabberd läuft auf dem Server auch noch ein Webserver, welcher verschiedene Webseiten ausliefert.

Daneben ist bereits der Let’s Encrypt Client acme-tiny[7. Fork des acme-tiny Clients auf GitHub {en}] installiert, dessen Verwendung in Abschnitt 3.6.2 TLS-Kochbuch erläutert wird.

Der Ablauf im Überblick

Wie vorstehend bereits erwähnt, verwendet ejabberd bereits ein TLS-Zertifikat, welches nun durch ein Let’s Encrypt Zertifikat ersetzt werden soll. Der Ablauf gliedert sich dabei im Wesentlichen in zwei Teile. Zuerst wird ein Let’s Encrypt Zertifikat für den Hostnamen des ejabberd-Servers ausgestellt. Anschließend wird ejabberd konfiguriert, um zukünftig dieses Zertifikat zu nutzen.

Zur Durchführung der Domainvalidierung wird ein VirtualHost für den Hostnamen des ejabberd-Servers konfiguriert. Da sich die genaue Konfiguration je nach verwendeter Webserver-Software unterscheidet, wird hier nicht näher darauf eingegangen, sondern auf die Dokumentation des jeweiligen Webservers verwiesen.

Generierung des TLS-Zertifikats

Nun wird mit Hilfe von OpenSSL[8. OpenSSL: Cryptography and SSL/TLS Toolkit {en}] [9. OpenSSL – Wikipedia] ein privater Schlüssel und eine Zertifikatsanfrage (engl. Certificate Signing Request (CSR)) erstellt. Beide Schritte werden ausführlich in Abschnitt 3.1 TLS-Kochbuch beschrieben.

Ich persönlich generiere den privaten Schlüssel und den CSR stets offline auf einem sicheren Computer. Anschließend lade ich sie zum Server hoch. Die Zugriffsrechte auf den Schlüssel sind so eingeschränkt, dass nur root und der Benutzer, unter dem der Webserver ausgeführt wird, diesen Schlüssel lesen dürfen.

Da bereits eine Installation von acme-tiny auf dem Server existiert und bereits ein Account-Key erstellt wurde, wird dieser in dem Verzeichnis verlinkt, in dem auch der private Schlüssel und der CSR liegen.

Nun kann das TLS-Zertifikat angefordert werden. Wie dies im Detail funktioniert, wird in Abschnitt 3.6.2 im TLS-Kochbuch beschrieben.

Sind bis hier alle Schritte erfolgreich gewesen, liegen nun Schlüssel und Zertifikat in einem Verzeichnis vor.

$ python acme-tiny/acme_tiny.py --account-key /var/www/sites/ssl/account.key --csr /var/www/sites/ssl/request.csr --acme-dir /var/www/sites/public/.well-known/acme-challenge/ > /var/www/sites/ssl/cert.crt
Parsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying fqdn...
fqdn verified!
Signing certificate...
Certificate signed!

$ ls -l /var/www/sites/ssl/
-rw-r—– 4 root root account.key
-rw-r—– 1 root www-data cert.crt
-rw-r—– 1 root root priv.key
-rw-r—– 1 root root request.csr

Zertifikat für ejabberd präparieren

Der Dienst ejabberd erwartet, dass der private Schlüssel, das Zertifikat und die Zertifikatskette in einer Datei übergeben werden.[10. Ejabberd SSL Certificate – Raymii.org {en}] Dazu wird noch die Zertifikatskette von Let’s Encrpyt[11. Let’s Encrypt: Chain of Trust {en}] heruntergeladen und ebenfalls in /var/www/sites/ssl/ gespeichert.

Das folgende Skript zeigt, wie das Zertifikat erzeugt, die Zertifikatsdatei für ejabberd zusammengesetzt und der Dienst neugestartet wird:

#!/bin/bash
# Datum: 2016-11-13
# Autor: Joerg Kastning <webmaster(aet)my-it-brain(Punkt)de>
#
# Beschreibung:
# Dieses Skript dient der Erneuerung des TLS-Zertifikats fuer
# jabber.my-it-brain.de über Let's Encrypt mit dem Client acme-tiny-by-frezbo.

DIR='/var/www/sites/ssl'

# Erneuerung des Zertifikats
python acme-tiny-by-frezbo/acme_tiny.py --account-key ${DIR}/account.key --csr ${DIR}/request.csr --acme-dir /var/www/sites/fqdn/.well-known/acme-challenge/ > ${DIR}/cert.crt

# Erzeugung der Zertifikatsdatei fuer ejabberd
cat ${DIR}/priv.key ${DIR}/cert.crt ${DIR}/lets-encrypt-x3-cross-signed.pem >${DIR}/ejabberd_cert.pem

sudo chown root:ejabberd ${DIR}/ejabberd_cert.pem
sudo chmod 0640 ${DIR}/ejabberd_cert.pem
sudo mv ${DIR}/ejabberd_cert.pem /etc/ejabberd/ejabberd_cert.pem

# Neustart von ejabberd
sudo service ejabberd restart

Abschluss der Migration

Durch den im vorangegangenen Abschnitt dargestellten Code wird das neue Let’s Encrypt Zertifikat an der gleichen Stelle und unter dem gleichen Dateinamen wie das alte Zertifikat abgelegt. Auf diese Weise muss die Konfiguration von ejabberd nicht weiter angepasst werden. Der Dienst lädt nach einem Neustart das neue Zertifikat. Die Migration ist damit abgeschlossen.

Let’s Encrypt Zertifikate besitzen eine Gültigkeit von 90 Tagen.[12. Let’s Encrypt: Why ninety-day lifetimes for certificates? {en}] Um die Erneuerung des Zertifikats zu automatisieren, wird das Skript „SmartRenew.sh“ verwendet (siehe Abschnitt 5.4 im TLS-Kochbuch).

Quellen und weiterführende Links:

Der eigene Mailserver – TLS-Migration zu Let’s Encrypt

Durch die Ankündigung von Mozilla, den Zertifizierungsstellen StartCom und WoSign das Vertrauen entziehen zu wollen,[1. Golem: Mozilla will Startcom und Wosign das Vertrauen entziehen] [2. Mozilla’s proposed conclusion for community discussion, regarding the matter of WoSign and StartCom. {en}] entstand für mich Handlungsbedarf. Denn ich wollte die Zertifikate dieser Anbieter nun so schnell wie möglich loswerden.

In Teil 2 meiner Artikelreihe „Der eigene Mailserver“ wird ein TLS/SSL-Zertifikat von StartSSL verwendet, um dieses zur Absicherung von Postfix, Dovecot und dem Webmailer Roundcube zu verwenden. An dieser Stelle halte ich nun die Umstellung auf TLS-Zertifikate von Let’s Encrypt fest.

Für die Bereitstellung und automatisierte Erneuerung der Zertifikate verwende ich certbot-auto.[3. Automatically enable HTTPS on your website with EFF’s Certbot, deploying Let’s Encrypt certificates. {en}] Dessen Installation und Nutzung wird in Abschnitt 3.6.1 TLS-Kochbuch[4. TLS-Kochbuch – Rezepte für die Verwendung von OpenSSL, HTTP Strict Transport Security (HSTS) und HTTP Public Key Pinning (HPKP)] beschrieben.

Das von cerbot-auto erzeugte Zertifikat und der dazugehörige private Schlüssel werden unter dem Pfad /etc/letsencrypt/live/ abgelegt. Um das Zertifikat in die gewünschten Dienste einzubinden, ist der Pfad zum Zertifikat in den Konfigurationsdateien der Dienste zu aktualisieren. Das folgende Listing gibt den Namen der Konfigurationsdateien und der anzupassenden Parameter wieder:

# /etc/nginx/sites-available/roundcube
ssl_certificate /etc/letsencrypt/live/FQDN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/FQDN/privkey.pem;

# /etc/postfix/main.cf
smtpd_tls_cert_file = /etc/letsencrypt/live/FQDN/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/FQDN/privkey.pem

# /etc/dovecot/conf.d/99-mail-stack-delivery.conf
ssl_cert =

Die Konfiguration der Dienste muss anschließend neu geladen werden. Um dies bei zukünftigen Erneuerungen des Zertifikats zu automatisieren, habe ich ein kleines Skript renew_certs.sh erstellt, welches täglich via Cron ausgeführt wird.

#!/bin/sh
/home/USERNAME/certbot/certbot-auto renew --quiet --no-self-upgrade
sudo service nginx reload
sudo service postfix reload
sudo service dovecot reload

certbot-auto renew erneuert das Zertifikat, sobald die Gültigkeitsdauer kleiner 30 Tage ist. Durch die tägliche Ausführung soll sichergestellt werden, dass die Aktualisierung auch dann rechtzeitig gelingt, sollte die Schnittstelle von Let’s Encrypt vorübergehend nicht erreichbar sein.

Mein TLS/SSL-Kochbuch

Heutzutage werden immer mehr Kommunikationsverbindungen im Internet mit TLS/SSL-Verbindungen geschützt. Die Verschlüsselung hilft, die Vertraulichkeit der zwischen Sender und Empfänger übertragenen Daten zu schützen und sollte daher standardmäßig aktiviert sein. Doch bereitet der Einsatz von TLS/SSL-Verschlüsselung noch immer vielen Administratoren und Betreibern verschiedenster Anwendungen Kopfschmerzen. Zu undurchsichtig scheint der Dschungel aus Zertifizierungsstellen, Zertifikaten, Zertifikatsanfragen, öffentlichen und privaten Schlüsseln zu sein. Verschiedenste Validierungsverfahren und Dateiformate für Zertifikate tragen nicht gerade dazu bei, den Durchblick zu behalten. Bereits die Erstellung einer Zertifikatsanfrage gerät dabei häufig genug zu einem Problem. Die richtige Konfiguration der zu sichernden Server bzw. Dienste erscheint kompliziert und Fehler in der Konfiguration führen nicht selten zur Nichterreichbarkeit einer Webseite. Die Folge: Immer noch wird viel zu häufig auf den Einsatz von TLS/SSL-Verschlüsselung verzichtet.

Mit meinem TLS-Kochbuch möchte ich dazu beitragen, etwas Licht ins Dunkel zu bringen. Darin finden sich Rezepte mit praktischen Tipps für die Verwendung von TLS/SSL-Verschlüsselung mittels OpenSSL, HTTP Strict Transport Security (HSTS) und HTTP Public Key Pinning (HPKP).

Aus dem Inhalt

Nach der Einleitung führt Kapitel 2 in das Thema ein und definiert die wesentlichen Begriffe. Es bildet die Grundlage für das Verständnis der daran anschießenden Kapitel.

In Kapitel 3 geht es um die Implementierung von TLS/SSL. Hier werden verschiedene Methoden zur Generierung von privaten Schlüsseln und Certificate Signing Requests vorgestellt. Darüber hinaus werden einige Zertifizierungsstellen kurz vorgestellt, bei denen Zertifikate beantragt werden können. Im Folgenden wird auf die Implementierung von TLS/SSL-Zertifikaten in verschiedenen Diensten eingegangen. Hier wird insbesondere die Implementierung von Zertifikaten der recht jungen Zertifizierungsstelle „Let’s Encrypt“ erläutert, mit deren Hilfe sich der Prozess der Zertifikatserneuerung automatisieren lässt.

Der Implementierung des HTTP Public Key Pinning (HPKP) widmet sich Kapitel 4. Das Pinning-Verfahren wird am Beispiel des Webservers NGINX erläutert und getestet.

Zum Schluss werden die in den einzelnen Kapiteln vorgestellten Techniken und Methoden nochmals zusammenfassend an einem konkreten Beispiel in Kapitel 5 verdeutlicht.

Hier geht es zum Text

Mir persönlich hat die Arbeit an folgendem Dokument geholfen, mich detailliert mit der Thematik auseinanderzusetzen und mein Wissen zu erweitern und zu vertiefen. Ich freue mich, wenn mein Kochbuch auch euch dabei hilft, ein besseres Verständnis für das Thema zu entwickeln.

Dies ist die erste Ausgabe meines TLS/SSL-Kochbuchs und es gibt sicher noch viel mehr, was man zu diesem Thema schreiben kann. Fehler sowie Wünsche zum Inhalt zukünftiger Ausgaben können gern an die E-Mail-Adresse tls-rezepte(aet)my-it-brain(Punkt)de gemeldet werden.

Downloads zum Text

Erster bekannter Missbrauch eines Let’s Encrypt Zertifikats

Dennis Schirrmacher berichtet in seinem heise-Artikel „Erste Malvertising-Kampagne mit Let’s-Encrypt-Zertifikat“ über die missbräuchliche Verwendung eines Let’s Encrypt Zertifikats.

Laut dem Artikel ist es Online-Gaunern gelungen, eine Subdomain für eine legitime Domain einzurichten und für diese ein Let’s Encrypt Zertifikat zu beantragen. Mit Hilfe der legitimen Domain und des vertrauenswürdigen Zertifikats wird Besuchern der Webseite Schadcode untergeschoben. Im konkreten Fall wird ein Online-Banking-Trojaner auf den Computern der Opfer installiert.

Die missbräuchliche Nutzung ist kein spezielles Problem von Let’s Encrypt. Es handelt sich vielmehr um ein generelles Designproblem der öffentlichen TLS/SSL-Infrastruktur, welches ich bereits in der Einleitung von „Certificate Pinning mit NGINX“ beschrieben habe.

Let’s Encrypt versucht den Missbrauch durch den Einsatz kurzlebiger Zertifikate einzuschränken. Dieser Versuch läuft meiner Ansicht nach jedoch weitgehend ins Leere. Denn auch Online-Gauner können die Erneuerung der ergaunerten Zertifikate mit dem Let’s Encrypt Client automatisieren.

Einen deutlich effektiveren Schutz gegen die genannte Art von Missbrauch bieten Verfahren wie „Public Key Pinning Extension for HTTP“[1. RFC 7469 – Public Key Pinning Extension for HTTP] [2. HTTP Public Key Pinning – Wikipedia (de)] und „HTTP Strict Transport Security (HSTS)“[3. RFC 6796 – HTTP Strict Transport Security (HSTS)] [4. HTTP Strict Transport Security – Wikipedia (de)]. Speziell mit Hilfe des Certificate Pinning kann ein Browser erkennen, ob ein TLS/SSL-Zertifikat zur besuchten Domain gehört oder nicht. Nähere Informationen dazu und wie das Certificate Pinning für den Webserver NGINX konfiguriert werden kann, finden sich im Artikel „Certificate Pinning mit NGINX“. Viel Spaß beim Lesen.