Jede Weboberfläche im Homelab zeigt eine Zertifikatswarnung, man klickt sie weg und gewöhnt sich daran. Bei mir kam Schlimmeres hinzu: Einige Dienste nahmen Anmeldungen im Klartext über HTTP entgegen, das Zertifikat der Firewall war seit 240 Tagen abgelaufen, und die Domänencontroller hatten gar keins, sodass LDAPS nicht nutzbar war. Ich habe deshalb eine eigene Zertifizierungsstelle aufgebaut: eine Root-CA, die offline bleiben soll, eine Sub-CA auf Windows und einen Automaten, der die Zertifikate der Webdienste selbst erneuert. Dazu kommt ein Dienst, der nach einem Windows-Update nicht mehr startete.
Die Hierarchie: zwei Stufen
Die Root-CA signiert genau einmal, nämlich das Zertifikat der Sub-CA, und gehört danach in die Schublade. Die Sub-CA ist eine Windows-VM mit den Active-Directory-Zertifikatdiensten als Enterprise Subordinate CA. Sie stellt alle Zertifikate im Alltag aus: für die Domänencontroller (LDAPS und Kerberos), für Weboberflächen und automatisch für Windows-Rechner in der Domäne.
| Ebene | Schlüssel | Laufzeit |
|---|---|---|
| Root-CA (offline, XCA) | RSA 4096, SHA-256 | 20 Jahre |
| Sub-CA (Windows) | RSA 4096, SHA-256 | 10 Jahre |
| Serverzertifikate | RSA 2048 | 2 Jahre |
Die Sperrliste der Sub-CA ist eine Woche gültig, mit drei Tagen Überlappung. Sie wird zusammen mit den CA-Zertifikaten über einen IIS auf derselben VM per HTTP veröffentlicht. HTTP statt HTTPS ist Absicht: Bei HTTPS bräuchte ein Client zur Prüfung des Zertifikats die Sperrliste, die er erst nach der Prüfung laden kann. Dieses Henne-Ei-Muster kommt weiter unten in anderer Form zurück.
Die Root-CA mit XCA und der Weg in die Schublade
Die Root-CA habe ich mit XCA erzeugt, einem grafischen Werkzeug für Schlüssel und Zertifikate, und zwar direkt auf der neuen CA-VM, bevor dort die Zertifikatdienste installiert waren. Das ist ein Kompromiss gegenüber einem echten Offline-Rechner. Ich wollte ihn mit diesen Regeln absichern:
- Solange der Root-Schlüssel auf der VM liegt, ist sie in keinem Backup-Job und in keiner Replikation, und es gibt keine Snapshots. Sonst landet der Schlüssel in Sicherungen, die man nie wieder vollständig loswird.
- Die Zeit zwischen Erstellung und Entfernung ist kurz: Minuten bis Stunden, nicht Tage.
- Zum Entfernen exportiere ich die XCA-Datenbank verschlüsselt, lösche den Schlüssel in XCA, überschreibe den freien Platz und prüfe, dass keine Datei mit dem Schlüssel mehr übrig ist. Auf einer virtuellen Platte lassen sich Reste im Speicher des Hosts nicht ganz ausschließen, auch deshalb keine Snapshots.
- Danach liegt der Schlüssel auf zwei USB-Datenträgern an getrennten Orten, einmal als verschlüsselte XCA-Datenbank und einmal als verschlüsselter PKCS#12-Export. Das Kennwort steht im Passwortmanager und ausgedruckt, getrennt von den Sticks.
- Beide Sticks teste ich auf einem anderen Rechner per Import und wiederhole das einmal im Jahr, denn USB-Speicher altert.

Das Bild zeigt den Zielzustand. Ganz erreicht habe ich ihn noch nicht: Die Sticks, der Import-Test und das Entfernen des Schlüssels von der VM stehen noch aus, und bis dahin weiche ich bewusst von meinen eigenen Regeln ab, damit die CA nach einem Ausfall aus der Sicherung zurückkommt.
Ein Termin, den man nicht vergessen darf: Auch die Sperrliste der Root-CA läuft ab, bei mir am 8. Oktober 2027. Sie muss etwa einmal im Jahr neu ausgestellt und veröffentlicht werden, und dafür muss der Root-Schlüssel kurz aus der Schublade. Wer das vergisst, bekommt Meldungen, dass Clients Zertifikate nicht prüfen können.
Die Sub-CA schlank und eng halten
Auf der Sub-CA ist bewusst wenig eingeschaltet: keine Web-Registrierung, keine Standardvorlagen. Veröffentlicht sind eine Vorlage für die Domänencontroller, eine eigene für Webserver mit ausschließlich dem Zweck Serverauthentifizierung und das Autoenrollment für die Windows-Rechner. Benutzer-, Smartcard- und Unterzertifizierungsstellen-Vorlagen sind nicht veröffentlicht. Die Option, Alternativnamen über Anforderungsattribute zu setzen, ist aus, und die Ereignisse der CA werden protokolliert.
Die Domänencontroller habe ich nacheinander umgestellt: erst einer, dann mit dcdiag und repadmin /replsummary geprüft, danach der zweite. Ein Test mit openssl s_client gegen Port 636 und der Root als Vertrauensanker meldete anschließend „Verification: OK“.
Für alle anderen Dienste brauchte ich vor dem ersten Zertifikat DNS-Namen, denn fast alles wurde bis dahin per IP aufgerufen. Die IP kommt zusätzlich als Alternativname ins Zertifikat, damit alte Lesezeichen nicht brechen.
Automatische Erneuerung für alles, was kein Windows ist
Windows-Rechner erneuern ihre Zertifikate über Gruppenrichtlinien selbst. Für Proxmox, den Backup-Server, die Firewall, die DNS-Filter, Home Assistant und GitLab gilt das nicht, und die Zertifikatdienste von Windows sprechen kein ACME. Ich habe deshalb einen kleinen Erneuerungs-Automaten in Python geschrieben. Er läuft auf dem Management-Server als systemd-Timer, einmal am Tag, unter einem eigenen Systembenutzer. Beim letzten Blick in die Doku, am 16. Oktober, hatte er 14 Ziele bearbeitet, ohne Fehler.

Für jedes Ziel aus einer Zielliste macht er dasselbe:
- Prüfen, ob das Zertifikat in weniger als 30 Tagen abläuft oder einen falschen Namen trägt. Wenn nicht, passiert nichts.
- Schlüssel und Anforderung auf dem Ziel erzeugen. Der private Schlüssel verlässt das Ziel nie.
- Die Anforderung per WinRM bei der Sub-CA einreichen, mit einem eigenen Dienstkonto ohne Admin-Rechte, das nur die Webserver-Vorlage nutzen darf.
- Das ausgestellte Zertifikat gegen Schlüssel, Namen und Kette prüfen und das alte Zertifikat sichern.
- Mit einem Handler je Dienst einspielen.
- Per TLS-Handshake prüfen, ob Aussteller, Name, Kette und Ablauf stimmen. Wenn nicht, den alten Stand zurückspielen und Alarm schlagen.
Einige Schutzregeln sind fest eingebaut. Die Alternativnamen kommen ausschließlich aus der Zielliste, nie aus der Anforderung, und Namen außerhalb der eigenen Domäne oder Adressen außerhalb des LANs verweigert der Automat. Die Root ist per Fingerabdruck festgenagelt. Es wird immer nur ein Ziel nach dem anderen bearbeitet, bei Diensten mit Partner wie den zwei DNS-Filtern nie beide gleichzeitig. Ein Probelauf erzeugt und prüft nur Anforderungen. Und das Monitoring prüft unabhängig vom Automaten, wie viele Tage jedes Zertifikat noch gilt: Warnung bei 30 Tagen, kritisch bei 14.
Den Gegentest habe ich auch gemacht: Dieselbe Anforderung reichte ich absichtlich mit anderen Vorlagen ein, und die CA lehnte sie ab. Das Dienstkonto darf also wirklich nur, was es soll.
Was je Dienst anders ist
Bei Proxmox tauscht pvenode cert set nur das Zertifikat der Weboberfläche. Der Verkehr im Cluster bleibt unberührt.
Beim Proxmox Backup Server binden die Knoten den Backup-Speicher über den Fingerabdruck des Zertifikats. Nach einem Tausch schlagen die Backups fehl, bis der Speichereintrag angepasst ist. Deshalb teste ich danach direkt einen Backup-Job, nicht erst in der Nacht.
Bei Home Assistant wirkt in meiner Version ein http:-Block in der configuration.yaml nicht mehr, weil die Einstellungen nach einer Migration nur noch aus dem internen Speicher gelesen werden. Erst der dritte Versuch lief durch. Außerdem braucht Home Assistant mit manchen Integrationen mehrere Minuten bis zum Start, der Handler wartet deshalb bis zu sieben Minuten auf HTTPS, bevor er zurückrollt.
Die Firewall steht in meinem Plan ganz am Ende, weil ein Aussperren dort am meisten kostet. Eingespielt wird nur mit Konfigurationssicherung und offener SSH-Sitzung als Rückweg.
Der Tag, an dem die CA nicht mehr startete
Eines Nachts installierte Windows Updates und startete die CA-VM automatisch neu. Sieben Minuten nach dem Neustart versuchte der Zertifikatsdienst zu starten und scheiterte mit einem Sperrprüfungsfehler: Die Sperrliste der Root sei offline. Beim Start prüft der Dienst sein eigenes CA-Zertifikat, und dazu gehört die Sperrliste. Die liefert der IIS aus, und der läuft auf derselben VM. Vermutlich war der Webserver zu diesem Zeitpunkt noch nicht bereit.
Das Tückische kam danach: Wenig später war die Sperrliste wieder erreichbar, aber der Dienst blieb gestoppt. Windows versucht es von selbst kein zweites Mal. Im Alltag fällt das nicht auf, denn ausgestellte Zertifikate bleiben gültig, nur neue Ausstellungen und neue Sperrlisten bleiben aus. Läuft die CA ein paar Tage nicht, läuft irgendwann die Sperrliste ab, und dann gibt es Prüffehler an Stellen, die mit der CA scheinbar nichts zu tun haben.
Gegen den Fehler helfen ein verzögerter Dienststart, damit der Webserver vor der CA bereit ist, und eine Wiederherstellungsaktion, damit Windows den Dienst nach einem Fehlschlag selbst neu startet. Beides geht mit Bordmitteln:
sc.exe config CertSvc start= delayed-auto
sc.exe failure CertSvc reset= 86400 actions= restart/60000/restart/600000
Beides habe ich noch am selben Tag gesetzt. Nach dem nächsten Update-Neustart am 15. Oktober lief der Dienst nach zwei Minuten von selbst. Eine Lücke blieb: Windows wertet einen Start, der mit einem Fehler abbricht, nicht als Absturz, und dann greift die Wiederherstellung nicht. Dafür braucht es zusätzlich sc.exe failureflag CertSvc 1.
Das Henne-Ei-Muster von der Sperrliste steckt also noch einmal darin: Ein Dienst hängt beim Start von etwas ab, das auf derselben Maschine erst nach ihm bereit ist. Das sieht man im Normalbetrieb nicht, weil alles schon läuft. Es zeigt sich beim Neustart, und der passiert gern nachts und unbeaufsichtigt durch ein Update.
Was noch offen ist
- Die Sicherung der Sub-CA mit Datenbank und Schlüssel steht noch aus. Ohne sie lässt sich die CA nach einem Totalverlust nur mit einem neuen Zertifikat unter derselben Root aufbauen, und alte Zertifikate ließen sich nicht mehr sperren. Ein Skript samt Restore-Test in einer abgeschotteten VM ist vorbereitet.
- Das Flag
failureflagwar bei meinem letzten Blick noch nicht gesetzt, und das Monitoring kennt die CA-VM bisher nur per Ping. Ein Agent mit einer Prüfung auf den Dienst und den IIS ist vorbereitet. - Der Automat soll auf jedem Ziel nur noch einen fest vorgegebenen Befehl ausführen dürfen, der Zertifikat und Schlüssel ablegt und den Dienst neu lädt.
Schreibe einen Kommentar