Let’s encrypt Suave

Wer noch nichts von Let’s encrypt gehört hat, sollte sich das Projekt auf jeden Fall mal anschauen. Aber hier in aller Kürze, worum es sich dreht, damit wir gleich weitermachen können. Let’s encrypt ist eine Certificate Authority (CA). Die ausgestellten Zertifikate werden von allen gängigen Browsern als sicher eingestuft, da die Zertifikate von Let’s encrypt von IdenTrust (DST) signiert sind. Ähnlich wie die Zertifikate von VeriSign, Thawtes oder Go Daddy ist IdenTrust als Root CA vorinstalliert.

Der Unterschied von Let’s encrypt zu anderen Zertifikatsherausgebern ist, dass die Zertifikate kostenlos sind und der Ausgabeprozess vollständig automatisiert ist. Für viele große Webserver gibt es bereits vollständig automatische oder zum großen Teil automatische Lösungen.

Im Zusammenspiel mit Suave ist hier allerdings momentan noch viel manuelles Eingreifen notwendig. Prinzipiell ist das erst mal kein Problem, allerdings ist die Gültigkeitsdauer der Let’s encrypt Zertifikate auf drei Monate beschränkt. Das ist leider häufig genug, so dass es mir zumindest notwendig erscheint den Prozess zu automatisieren.

Also legen wir los.

Vorbereitungen

Wir starten wie immer mit unserem Hello World Server und machen von dort ausgehend unsere Anpassungen.

Die API von Let’s encrypt folgt dem ACME Protokoll, aber weicht an bestimmten Stellen davon ab. Eine Auflistung dieser Abweichungen findet man hier. Diese Abweichungen hindern uns aber nicht daran eine ACME konforme Client Implementierung zu nutzen.

Wir könnten auch unseren eigenen Let’s encrypt-Client schreiben. Das wäre sicherlich eine gute Übung, da das ACME Protokoll auf einer REST-Architektur basiert, sprich die verschiedenen Ressourcen werden über Uris angesprochen, über HTTP Methoden manipuliert und man erhält in der Antwort die ggf. geänderte Ressource und die weiterführenden Uris.

Aber ich würde diesmal eine existierende ACME-Client Implementierung verwenden. Und zwar Certes. Fügen wir also Certes über Nuget unserem Hello Word Projekt hinzu.

Zertifikat anfordern

In der ersten Ausbaustufe werden wir erst mal nur den gesamten Workflow einmal durchführen. Sprich Zertifikat anfordern, die Challenge bedienen und den Server dann anschließend mit unserem brandneuen Zertifikat neustarten.

In einer späteren Ausbaustufe werden den Ablauf dann zeitgesteuert wiederholen und unser Zertifikat erneuern. Im Idealfall bevor die drei Monate abgelaufen sind und unser Server ein ungültiges Zertifikat ausliefert.

Damit ihr den folgenden Implementierungen leichter folgen könnt, an dieser Stelle der grobe allgemeine Ablauf, wie das erstmalige Anfordern eines Zertifikats erfolgt.

  1. Zuerst registriert man sich bei Let’s encrypt.
  2. Anschließend fordert man eine Autorisierung für einen bestimmten Host an.
  3. Daraufhin erhält man eine Aufgabe (Challenge) die man erledigen muss. Sobald man die Aufgabe erledigt hat, gibt man Let’s encrypt Bescheid.
  4. Falls die Aufgabe erfolgreich erfüllt wurde, erhält man die Autorisierung für diesen Host.
  5. Zu guter Letzt kann man nun mit dieser Autorisierung das Zertifikat herunterladen.

Nun zur eigentlichen Implementierung. Den großen Teil davon können wir aus der Get Started Anleitung von Certes übernehmen. Das Registrieren und Anfordern der Autorisierung sind unabhängig von der Challenge und bleiben immer gleich. Aber da Certes die asynchronen Aufrufe mit Tasks abbildet, fügen wir zuerst einen kleinen Wrapper um die Certes API ein, damit wir mit der mehr F# idiomatischen Async Monade arbeiten können.

Wie ihr sehen könnt, haben wir nur die relevanten Blöcke aus der Get Started Anleitung gebildet. Teilweise sind es nur die dünnen Wrapper, um die originale API, an anderer Stelle sind zusammengehörige Abläufe bereits integriert. Zum Beispiel habe ich die Auswahl der verschiedenen Challenges auf die HTTP Challenge reduziert, da es diese ist, die wir mit Suave umsetzen können.

Fast alle Funktionen unseres Moduls benötigen einen AcmeClient. Dann erzeugen wir diesen nun. Wichtig ist, dass man im Konstruktor die Einstiegsadresse angeben muss. Diese eine Adresse reicht, da sich die restlichen URLs aus den Server Antworten ableiten lassen.

Da wir noch in der Implementierung sind, verwenden wir den Staging Server von Let’s encrypt. Sobald unsere Implementierung fertig ist und wir auch das Thema adressiert haben, das wir unseren Let’s encrypt Account speichern, dann können wir auf den produktiven Server umstellen.

Das einzige was uns für den Abschluss unseres ersten Meilensteins noch fehlt, ist die Integration mit Suave.

Für den erfolgreichen Abschluss der HTTP Challenge, müssen wir einen bestimmten Text unter einer bestimmten URL erreichbar machen. Wer Suave schon kennt weiß, dass uns das vor keine größeren Probleme stellen sollte. Die einzige Schwierigkeit wird sich in Nebenläufigkeit zeigen, da wir den Server starten und aktive halten müssen und parallel Let’s encrypt informieren müssen, dass die Aufgabe erfüllt ist. Dann schauen wir uns mal eine Möglichkeit an, dieses Problem zu lösen.

So, dann schauen wir uns die wichtigsten Dinge noch an, bevor für dieses Mal Schluss ist. Zum einen wäre das die Route mit der wir das Token bereitstellen, um unsere Challenge zu erfüllen. Das Token ist nicht nur der Inhalt, sondern auch Teil des Pfades.

Das andere ist die Verwendung von startWebServerAsync. Die Funktion bedarf etwas Erklärung, was bereits damit anfängt, dass sie ein Tupel zurückliefert. Das erste Element ist ein Async-Wert, der verfügbar ist, sobald der Server bereit ist, Anfragen zu bedienen. Das zweite Element ist der eigentliche Server und muss noch selber gestartet werden. Wir starten also den Server mit dem zweiten Tupel Element und warten dann auf das erste Element. Danach können wir die Challenge als erledigt melden.

Nächstes Mal werden wir uns dann darum kümmern, dass unser Zertifikat erneuert wird. Bis dahin und danke für’s lesen.