Seit Dezember ist nun ganz offiziell die Public Beta von Let’s Encrypt verfügbar. Die ersten Zertifikate laufen nun innerhalb der nächsten 2 Wochen aus, also auch bei mir (ich hatte ja auch Ende November bereits alles auf SSL-only umgestellt).

Mit einem Blick in den Kalender wurde es daher auch mal zeitig, den automatischen Renewalprozess ans Laufen zu kriegen, denn die Zertifikate sind ja bekanntlich immer nur 90 Tage gültig. Manuelles Eingreifen als Dauerlösung scheidet aus. Ich bin ja Entwickler, da ist jede Wiederholung eines Arbeitsschrittes schon eine zu viel.

Das Szenario

Ein Multi-Site-Konfiguration verschiedener Domains für einen Nginx, jede Domain hat ihre eigenes Zertifikat und Private Key. Diese Paare müssen jeweils validiert und ggf. erneuert werden.

Für den initialen Einrichtung hatte ich auch den offiziellen Client verwendet. Aber erstens ist dieser zu mächtig mit vielen Abhängigkeiten, denn ich will nur (neue) Zertifikate und keine Modifizierung irgendwelcher Konfigurationsdateien. Das Zweite wiegt noch schwerer: Dieser funktioniert (aktuell) nur als root vernünftig, was unter anderem auch daran liegt, dass er kurzfristig Port 80 nutzen muss. Das ist aber natürlich mit einem laufenden Webserver “unhandlich”.

Aber es gibt Alternativen: Und mal gar nicht so wenige. Prinzipiell sind Ideen wie der Caddy Server (ein “Webserver” mit eingebautem ACME-Client) interessant, aber nicht für diese Situation angebracht. Ein ander’ Mal vielleicht…

Exkurs
Der gesamte Prozess der Verifizierung der Domain geschieht über das Protokoll “Automated Certificate Management Environment” oder kurz ACME. Das wurde ursprünglich von Let’s Encrypt entwickelt und aktuell ist es als IETF Protokoll im Gespräch. Das Protokoll beschreibt, wie Clients (bspw. für Webserver) ein Zertifikat anfragen und bestätigen können. Daher kann prinzipiell jeder einen solchen Client (und natürlich auch Server!) bauen; fertige Plugins “out-of-the-box” für Webserver sind also auch bald möglich – Caddy macht es sogar schon vor.

Meine Wahl fällt auf letsencrypt.sh von Lukas Schauer: ein kleines und überschaubares Bashscript, welches sich ebenfalls einfach konfigurieren lässt. Da ich ja bereits “Bestandsdaten” hatte, wollte ich natürlich gerne die vorhandenen Zertifikate und Schlüssel “importieren”. Netterweise hat Lukas dafür schon etwas vorbereitet. 😎

Schritt-für-Schritt vom Import bis zur Cron:

  • Annahme: www-data ist die Gruppe des Webserver-Users.
  1. Wir legen einen neuen dedizierten UNIX-Account an, nennen wir ihn doch bill (Name d. Red. bekannt). Denn Bill is smart!

    1. Nicht vergessen: bill darf sich nicht einloggen können; am Besten kein Passwort und keinen Public Key hinterlegen.
    2. bill wird Mitglied der Gruppe www-data (als Default-Gruppe): usermod -g www-data bill
  2. Das Git-Repository klonen wir uns einfach in sein Home-Verzeichnis: git clone https://github.com/lukas2511/letsencrypt.sh.git

    1. In das Verzeichnis wechseln.
    2. Testweise ./letsencrypt.sh -e starten. Sollte kein Fehler kommen, erstmal alles gut. Ansonsten dafür sorgen, dass openssl und curl installiert sind.
  3. Wir legen das Verzeichnis /etc/letsencrypt.sh an und passen die Rechte so an, dass nur Mitglieder der Gruppe www-data dort lesen und bill zusätzlich schreiben darf: chown bill:www-data /etc/letsencrypt.sh && chmod 750 /etc/letsencrypt.sh

  4. Vorbereitungen

    1. In bill wechseln… su – bill
    2. … und in das eben erstellte Verzeichnis /etc/letsencrypt.sh wechseln.
    3. Eine leere domains.txt erstellen (siehe domains.txt.example)
    4. Jeweils die config.sh.example und hooks.sh.example aus dem Repository kopieren und ablegen, dabei den Suffix .example entfernen.
    5. In der nun frischen config.sh das BASE_DIR setzen: BASEDIR=/etc/letsencrypt.sh
    6. Einen symbolischen Link auf /var/www/letsencrypt erstellen: ln -s /etc/letsencrypt.sh/.acme-challenges /var/www/letsencrypt
    7. Das eben erwähnte Linkziel anlegen: mkdir /var/www/letsencrypt
    8. Aus Sicherheitsgründen sollten die Dateien und Verzeichnisse nur für bill zu schreiben sein.
  5. Letsencrypt.sh-Konfiguration

    1. Falls schon mit Hilfe des offiziellen Client Zertifikate installiert wurden:
      1. Mit dem Importscript (siehe hier) werden alle Zertifikate ermittelt und sauber in /etc/letsencrypt.sh/certs/$DOMAIN geschrieben.
      2. Einen eventuell vorhandenen Account Key kann man mit dem zweiten Importscript auch ermitteln; falls vorhanden einfach unter /etc/letsencrypt.sh/private_key.pem ablegen.
    2. Falls noch keine Zertifikate angelegt wurden, dann muss man die Datei domains.txt selber editieren – oder auch nachträglich korrigieren.
  6. Nginx-Konfiguration

    1. In Punkt 4 wurde bereits das Verzeichnis /var/www/letsencrypt für die Response-Challenges angelegt. Da das Protokoll vorsieht, das der Webserver unter Port 80 (!) und dem Pfad /.well-known/acme-challenge die Antworten liefern kann, muss jeweils bei jeder Seite eine kleine Ergänzung eingefügt werden. Relativ am Anfang innerhalb der HTTP-Direktive sollte daher ein Verweis auf eben genanntes Verzeichnis stehen. Im Folgenden ein Beispiel zusammen mit HTTPS-Always:
      server {
      	listen 80;
      	server_name _;
      	
      	location /.well-known/acme-challenge {
      		alias /var/www/letsencrypt;
      	}
      
      	location / {
      		add_header Strict-Transport-Security "max-age=31536000; preload";
      		return 301 https://$server_name$request_uri;
      	}
      }

    Es muss sicher gestellt werden, dass für alle(!) Domains (inkl. Subdomains) das Verzeichnis über HTTP erreichbar ist. Am Besten eine Datei mit “Hello World” ablegen und die Erreichbarkeit versuchen. Idealerweise mit curl, denn der Browser forciert u.U. die HTTPS-Erreichbarkeit (etwa durch HSTS oder Plugins). 2. Und schließlich muss der Pfad zum Zertifikat und zum Schlüssel umgebogen werden. 1. Entweder: In der Nginx-Site-Konfiguration wird auf den neuen Pfad gezeigt: /etc/letsencrypt.sh/certs/$DOMAIN/fullchain.pem und ~privkey.pem 2. Oder: Man kopiert sich die Dateien im Hook (siehe unten).

  7. Hooks

    1. Letsencrypt.sh bietet aktuell drei Hooks an, davon ist interessant: deploy_cert (nachdem ein neues Zertifikat erstellt wurde)
      1. Falls man das Zertifikat erst noch an die richtige Stelle kopieren will (als bill).
      2. Es bietet sich an, den Webserver neu zu starten: sudo service nginx reload
  8. Cron finally

    1. Anschließend runden wir das Ganze noch ab: Wir richten eine wöchentliche Cron auf, der letsencrypt.sh aufruft und die Renewals automatisch anwirft. Beispiel: 0 0 * * Sat (cd /home/bill/letsencrypt.sh; ./letsencrypt.sh -c > /home/bill/last_renew_check.log) für einen wöchentlichen Check am Samstag.

Zusammenfassung

Mit der Anleitung haben wir nun einen wöchentlichen Cronjob, der jeweils prüft, ob die konfigurierten Domains ein Zertifikat haben und ob dieses noch gültig ist. Dabei wird bereits im Voraus ein neues generiert. Die Verifikation geschieht ganz regulär über ACME, der Webserver liefert die entsprechenden Daten im laufenden Betrieb.

letsencrypt.sh liegt im Homeverzeichnis eines dedizierten Benutzers ($HOME/letsencrypt.sh), die Konfiguration unter /etc/letsencrypt.sh.

Die Zertifikate und Schlüssel liegen in /etc/letsencrypt.sh/certs (BASE_DIR).

Alle Pfade lassen sich natürlich ändern, dafür einfach in der Dokumentation nachschauen.

Anmerkungen

  • Man kann auch ein anderes BASE_DIR (anstatt /etc/letsencrypt.sh verwenden; die config.sh muss  weiterhin unter /etc/letsencrypt.sh (oder lokal) vorhanden sein.
  • bill is smart und darf deswegen natürlich keinen Service einfach neu starten. Das lässt sich mit sudoers lösen: bill ALL = NOPASSWD: /usr/sbin/service nginx *