Eine öffentliche HTTP-Freigabe nur für die Zertifikatserneuerung: Diese Abhängigkeit wollten wir bei unserem Reverse Proxy loswerden. Die Anwendungen sollten weiterhin über HTTPS erreichbar sein. Der Nachweis für ein neues Zertifikat sollte dagegen über unsere öffentliche DNS-Zone bei Oracle erfolgen.
Anfang Oktober haben wir diesen Weg für unseren Caddy-Reverse-Proxy eingerichtet. Dabei mussten wir uns mit API-Rechten, unterschiedlichen DNS-Sichten und der Bereitstellung neuer Zertifikate beschäftigen. Die Ausstellung war am Ende erfolgreich. Bis Caddy das richtige Zertifikat auslieferte und ein wiederholbarer Ablauf daraus wurde, waren jedoch weitere Schritte nötig.
Den Domainbesitz über DNS nachweisen
Let’s Encrypt stellt Zertifikate aus, nachdem die Kontrolle über die betreffenden Namen geprüft wurde. Beim verbreiteten Verfahren HTTP-01 ruft die Zertifizierungsstelle eine Prüfdatei über Port 80 ab. DNS-01 verwendet stattdessen einen vorübergehenden TXT-Eintrag unter einem besonderen Namen in der öffentlichen DNS-Zone. Diese Verfahren beschreibt Let’s Encrypt in seiner Übersicht der Challenge-Typen.
Für uns passte DNS-01 zum gewünschten Aufbau. Die öffentliche Zone liegt bei Oracle Cloud Infrastructure. Ein ACME-Client kann dort über die API den benötigten Prüfwert setzen und nach der Validierung wieder entfernen. Der Webserver muss für diesen Nachweis keinen eingehenden HTTP-Zugang anbieten.
HTTPS über Port 443 bleibt für die Anwendungen erforderlich. Und wenn Port 80 geschlossen ist, funktioniert von aussen auch keine Weiterleitung eines HTTP-Aufrufs auf HTTPS. Links und gespeicherte Adressen müssen deshalb direkt HTTPS verwenden. DNS-01 übernimmt den Zertifikatsnachweis; die Zugriffsregeln für die Anwendungen bleiben eine eigene Aufgabe.
Ausstellung und Webserver getrennt betreiben
Wir verwenden einen kurzlebigen Container mit dem ACME-Werkzeug lego und dessen Oracle-DNS-Provider. Er übernimmt die Kommunikation mit Oracle und Let’s Encrypt. Caddy bekommt die fertigen Zertifikatsdateien und verwendet sie für die veröffentlichten Dienste.
Für den DNS-Zugriff richteten wir einen eigenen technischen Benutzer, eine Gruppe und einen API-Signaturschlüssel ein. Der private Schlüssel bleibt geschützt auf dem Host. Der ACME-Container bekommt Zugriff auf die benötigten Zugangsdaten. Caddy selbst braucht für die Auslieferung seiner Seiten keinen Oracle-API-Schlüssel.
Diese Aufteilung hat eine betriebliche Konsequenz: Caddy erneuert die von uns ausdrücklich geladenen Zertifikatsdateien in diesem Aufbau nicht selbst. Die Erneuerungsaufgabe auf dem Host gehört deshalb genauso zum Dienst wie der laufende Webserver. Ein grüner Caddy-Status allein sagt wenig darüber aus, ob das nächste Zertifikat rechtzeitig bereitgestellt wird.
Oracle meldete eine fehlende Berechtigung als 404
Die Rechte wollten wir möglichst eng halten: nur die betreffende Zone und die benötigten Challenge-Namen. Eine erste Policy enthielt zusätzlich eine Bedingung auf den Record-Typ TXT. Trotzdem scheiterten API-Aufrufe zum Lesen und Ändern der Domain-Records mit HTTP 404 und der Meldung NotAuthorizedOrNotFound.
Die Meldung führte zunächst nicht eindeutig zur Ursache. Eine eingeschränkte Diagnoseabfrage zeigte, dass die Zone erreichbar war. Die Gruppenzuordnung stimmte ebenfalls. Erst nachdem wir die zusätzliche TXT-Typbedingung entfernten, funktionierten die benötigten Aufrufe und später auch das Anlegen und Entfernen der Prüfwerte.
Die Begrenzung auf die Zone und die exakten Challenge-Namen blieb bestehen. An diesen Namen erlaubt die angepasste Policy allerdings Record-Zugriffe ohne die ursprüngliche Typbegrenzung. Das ist ein bewusst beschriebener Umfang; daraus sollte keine angeblich weiterhin ausschliessliche TXT-Berechtigung werden. Ebenso wenig haben wir daraus allgemeine Administratorrechte für die Automatisierung gemacht.
Für uns war der praktische Nachweis entscheidend: Ein erfolgreicher Leseaufruf mit einer leeren Ergebnisliste belegt noch keine Schreibberechtigung. Erst die erfolgreiche Validierung samt Bereinigung zeigte, dass der gesamte benötigte API-Ablauf funktioniert. Welche Bedingungen für welche Operationen gelten, lässt sich in der Oracle-Dokumentation zu DNS-Berechtigungen nachlesen.
Die interne DNS-Antwort passte nicht zur öffentlichen Zone
Ein zweiter Fehler lag an unserer Namensauflösung. Intern verwalten wir für bestimmte Dienste eigene DNS-Zonen. So können Geräte im eigenen Netz eine andere Antwort bekommen als ein Besucher aus dem Internet. Für den täglichen Zugriff ist diese Trennung beabsichtigt.
Bei der automatischen Ermittlung der öffentlichen DNS-Zone führte sie aber in die falsche Richtung. Der ACME-Client sah über den internen Resolver eine eigene untergeordnete Zone. Öffentlich war der gesuchte Name lediglich ein Eintrag innerhalb der übergeordneten Oracle-Zone.
Wir verglichen deshalb die internen und öffentlichen SOA-Antworten. Ein SOA-Eintrag beschreibt unter anderem die Zuständigkeit für eine DNS-Zone. Der Vergleich machte sichtbar, warum die automatische Auswahl anders ausfiel als erwartet.
Für die öffentliche Zonenermittlung gaben wir lego anschliessend ausdrücklich einen öffentlichen Resolver vor. Die internen DNS-Zonen und die Resolver der Anwendungen blieben erhalten. Die DNS-01-Prüfung muss schliesslich in der öffentlich zuständigen Zone stattfinden, die auch Let’s Encrypt abfragt.
Erst testen und dann das Produktionszertifikat binden
Vor der ersten produktiven Ausstellung verwendeten wir die Staging-Umgebung von Let’s Encrypt mit einem getrennten Datenverzeichnis. Dort konnten wir Berechtigungen, Zonenauswahl, das Setzen der TXT-Werte und deren Bereinigung testen. Ein Staging-Zertifikat gehört dabei nicht an den produktiven Webserver, weil Browser ihm normalerweise nicht vertrauen.
Auch die Containerpfade mussten stimmen. Ein erster Start scheiterte daran, dass das gewählte Ziel für den Datenmount im Image bereits als Datei existierte. Nach der Korrektur auf ein passendes Verzeichnis konnte lego seinen Zustand speichern und starten. Solche lokalen Startfehler wollten wir geklärt haben, bevor wir daraus DNS- oder Berechtigungsprobleme ableiteten.
Anschliessend gelang die Produktionsausstellung. Bei einer späteren Erweiterung um einen weiteren Dienst mussten sowohl die erlaubten Challenge-Namen als auch Domainlisten und Hostnamenprüfungen im Bereitstellungsablauf angepasst werden.
Ein neuer Caddy-Site-Block allein hätte dafür nicht genügt. Die DNS-Berechtigung, die Bestellung des Zertifikats und dessen tatsächliche Namensabdeckung müssen zusammenpassen.
Ein neues Zertifikat ist noch nicht bei Caddy angekommen
Nach der Ausstellung liegen Zertifikat, Zwischenzertifikatskette und privater Schlüssel zunächst im Arbeitsverzeichnis des ACME-Clients. Unser Bereitstellungsskript prüft die Dateien und legt bei einem neuen Zertifikat ein eigenes Releaseverzeichnis an. Ein gemeinsamer Verweis bestimmt, welches Paar aus Zertifikatskette und Schlüssel Caddy verwenden soll.
Vor dem Wechsel prüft der Ablauf unter anderem die Restgültigkeit, die Übereinstimmung der öffentlichen Schlüssel und die vorgesehenen Hostnamen. Dann wird der Verweis umgestellt, die Caddy-Konfiguration validiert und Caddy mit einem erzwungenen Reload neu geladen. Der erzwungene Reload ist hier relevant, weil sich die Dateiinhalte ändern können, obwohl der Pfad im Konfigurationstext gleich bleibt. Die Caddy-Befehlsdokumentation beschreibt diesen Fall.
Als Kontrolle verglichen wir den SHA-256-Fingerabdruck der Zertifikatsdatei auf dem Host mit dem Zertifikat, das der Browser tatsächlich erhielt. Beide stimmten überein. Damit war auch der Weg von der ausgestellten Datei bis zur ausgelieferten HTTPS-Verbindung geprüft. Beim später ergänzten Dienst funktionierten der externe Aufruf und die Anmeldung ebenfalls.
Für einen Rückfall muss ein passender vorheriger Zertifikatsstand verfügbar sein. Bei einem Fehler nach der Umschaltung kann ein Bereitstellungsskript den vorherigen Verweis wiederherstellen und erneut laden. Auch dieser Ablauf braucht einen kontrollierten Test. Nach einer Erweiterung der Namensliste deckt ein früheres Zertifikat möglicherweise nicht alle Dienste ab.
Regelmässig prüfen und nur bei Bedarf neu laden
Eine regelmässige Erneuerungsaufgabe gehört zu diesem Bereitstellungsmodell. Das Skript verhindert parallele Läufe, startet die Erneuerungsprüfung und vergleicht anschliessend den Fingerabdruck mit dem aktiven Zertifikat. Wenn sich nichts geändert hat, endet es erfolgreich, ohne Caddy neu zu laden.
Die manuellen Prüfungen und der getestete Aufgabenaufruf meldeten korrekt, dass noch keine Erneuerung nötig war. Zusätzlich haben wir die Bereitstellung vorhandener Produktionsdateien separat getestet. Diese beiden Ergebnisse beantworten unterschiedliche Fragen: Kann der Job starten und prüfen? Und kann ein bereitliegendes Zertifikat bis in Caddy übernommen werden?
Eine vollständige Betriebsprüfung umfasst ausserdem eine tatsächlich fällige Erneuerung, Fehlerbenachrichtigung und die Wiederherstellung des geschützten ACME-Zustands. Ein erfolgreicher Prüfaufruf ohne Erneuerungsbedarf ersetzt diese Tests nicht.
Die HTTP-Freigabe konnte entfallen
Mit DNS-01 war die eingehende HTTP-Freigabe für diesen Zertifikatsablauf nicht mehr erforderlich. Die bisherige öffentliche HTTP-Regel wurde entfernt; die dazugehörige bereits deaktivierte NAT-Regel haben wir bei der späteren Firewall-Bereinigung ebenfalls gelöscht. Die benötigten HTTPS-Zugänge blieben bestehen.
Der Aufwand ist damit an eine andere Stelle gewandert. Statt einer HTTP-Prüfdatei brauchen wir geschützte API-Zugangsdaten, passende DNS-Rechte und einen verlässlichen Bereitstellungsablauf. Dafür lässt sich die Zertifikatsausstellung unabhängig vom eingehenden HTTP-Zugang betreiben.
Besonders geholfen hat uns, die einzelnen Schritte getrennt zu prüfen: öffentliche Zonenzuständigkeit, API-Zugriff, Zertifikatsausstellung, Dateiprüfung und die tatsächlich ausgelieferte HTTPS-Verbindung. So wurde aus mehreren zunächst ähnlich aussehenden Fehlermeldungen ein verständlicher Ablauf, den wir bei der nächsten Erweiterung wieder verwenden können.