Homeassistant (interne Dienste) mit öffentlichen Zertifikat benutzen (LetsEncrypt)
Disclaimer und Info
Du bist für die Absicherung deiner Installationen selbst verantwortlich.
Dieser Artikel lässt bewusst Punkte aus, da im Rahmen eines Beitrags nicht jeder Kontext vollständig erklärt werden kann, ohne dass der Text unendlich lang wird. Daher ist es wichtig, dass du deine Systeme grundsätzlich verstehst.
In diesem Beitrag geht es darum, wie man öffentliche Zertifikate für Home Assistant auch intern nutzen kann. Die hier vorgestellte Vorgehensweise richtet sich an sicherheitsaffine Personen, die einen hohen Anspruch an IT-Sicherheit haben.
Dazu wird ein Zertifikatsserver in einer geschützten, lokalen Umgebung eingerichtet, der allen Systemen Zertifikate bereitstellen und zentral verwalten kann. Dieses Vorgehen ist komplexer als der einfachste Weg über ein Home Assistant Add-on (installierbar im Add-on Store): Home Assistant Let's Encrypt Add-on
Auch dazu habe ich einen separaten Beitrag <<hier>> erstellt. Von dieser Variante rate ich jedoch ausdrücklich ab, da man hierbei die DNS-API-Zugänge direkt in der Konfiguration hinterlegen muss. Wird die eigene Instanz aus irgendeinem Grund kompromittiert, verliert man im schlimmsten Fall sofort die gesamte DNS-Zone.
Für mich persönlich ist dieses Risiko nicht tragbar, da dadurch mehrere Domains „verloren“ wären und ich manuell eingreifen müsste. Aus diesem Grund erhält kein Server bei mir direkten Certbot-Zugriff (außer im Fall einfacher HTTP-01-Challenges). Wenn ich DNS-Challenges nutze, erfolgen diese ausschließlich lokal auf einem gehärteten, geschützten System. Die so erzeugten Zertifikate werden anschließend von diesem Server aus an alle Systeme verteilt.
Das hat mehrere Vorteile:
- zentrale Verwaltung an einem Ort
- Fehlerbehebung für mehrere Systeme gleichzeitig
- deutlich erhöhte Sicherheit im Hinblick auf meine Domains
Einleitung
In den letzten Wochen habe ich begonnen, den Schritt von Windows und Android hin zum Apple-Ökosystem zu wagen – nachdem ich mich jahrelang dagegen gewehrt habe. Da meine Freundin bereits seit Jahren ein iPhone nutzt, war mir von Anfang an klar, worauf ich mich einstellen muss.
Eines der größten Themen ist der Umgang mit internen, eigenen Zertifikaten. Apple hat dabei relativ strenge Richtlinien. Das bedeutet in der Praxis zwar nicht viel mehr Aufwand als bei Android, erfordert aber in jedem Fall zusätzliche Arbeit. Zudem akzeptiert Apple nur Zertifikate mit kürzerer Gültigkeit, wodurch eine automatisierte Verwaltung praktisch unerlässlich wird.
Bislang habe ich dafür meine eigene kleine CA auf Basis von XCA betrieben, mit der ich interne Zertifikate ausgestellt und alle Geräte manuell mit den notwendigen Daten versorgt habe. Das ist arbeitsintensiv und fehleranfällig, da jedes Gerät den Zertifikaten manuell vertrauen muss.
Die naheliegende Alternative ist die Verwendung öffentlicher Zertifikate – zum Beispiel über Let’s Encrypt – auch für interne Dienste. Genau das werden wir in diesem Beitrag am Beispiel von Home Assistant umsetzen.
Hauptteil
Wichtige generelle Dinge
Domain
Um öffentliche Zertifikate – beispielsweise von Let’s Encrypt – nutzen zu können, benötigt man zwingend eine offizielle Domain, die einem auch gehört. Der Grund dafür ist, dass Let’s Encrypt (und andere CAs) bei der Ausstellung überprüfen muss, ob du tatsächlich der Inhaber der Domain bist.
Dies geschieht über eine DNS-Challenge:
Dabei weist man nach, dass man Kontrolle über die Domain hat, indem man einen speziellen Eintrag im DNS setzt.
Auf Basis dieser erfolgreichen Validierung wird anschließend das Zertifikat ausgestellt.
Wichtig: Die IP-Adresse des Systems spielt dabei keine Rolle – entscheidend ist allein der Nachweis der Domain-Inhaberschaft.
Für die Automatisierung der Zertifikatserneuerung ist es erforderlich, dass ein passendes DNS-Plugin verfügbar ist. Eine Übersicht der offiziellen Plugins findest du hier:
Certbot DNS-Plugins (offizielle Doku)
Darüber hinaus gibt es noch weitere, von der Community bereitgestellte Plugins – zum Beispiel für Netcup. Hier lohnt sich eine kurze Suche, z. B. nach:
certbot dns plugin netcup
Eine sehr praktische Übersicht findet man außerdem in den Docs des Let’s Encrypt Add-ons für Home Assistant. Dort sind sowohl offizielle als auch Community-Plugins gelistet: Unterstützte DNS-Provider im Home Assistant Let’s Encrypt Add-on (Damit sparst du dir das mühsame Recherchieren und kannst direkt prüfen, ob dein Provider dabei ist. Die Liste hat keine Garantie auf Vollständigkeit. Das sind alles Plugins, die im Homeassistant AddOn vorinstalliert sind.
DNS
Ein wichtiger Punkt beim Einsatz öffentlicher Zertifikate für interne Dienste ist der richtige Umgang mit DNS. Grundsätzlich gilt:
Öffentliche DNS-Zonen können keine internen IP-Adressen enthalten (z. B. aus dem privaten Adressraum 192.168.x.x, 10.x.x.x oder 172.16–31.x.x).
Entsprechend ist es nicht möglich, in deiner externen DNS-Zone direkt interne Hosts zu hinterlegen.
Die Lösung dafür heißt Split-DNS:
Man betreibt eine interne DNS-Zone, die den gleichen Domainnamen wie die öffentliche Zone verwendet.
In dieser internen Zone weist du den gewünschten Hostnamen (z. B. homeassistant.meinedomain.de) einer internen IP-Adresse zu.
Von extern zeigt der gleiche Name hingegen auf die öffentliche IP (z. B. deine Firewall oder dein Reverse Proxy).
So wird erreicht, dass:
- Externe Clients (z. B. Smartphone im Mobilfunknetz) über die öffentliche IP auf den Dienst zugreifen können.
- Interne Clients (z. B. Geräte im Heimnetz) denselben Hostnamen nutzen, intern aber auf die lokale IP weitergeleitet werden.
Man hat dabei zwei gängige Varianten:
- Gleicher Name, unterschiedliche IP-Adressen ->
Sowohl extern als auch intern wird derselbe FQDN (homeassistant.meinedomain.de) genutzt.
Der interne DNS-Server liefert die private IP, während der öffentliche DNS die öffentliche IP zurückgibt.
Vorteil: Einheitlicher Zugriff, keine Unterschiede für die Nutzer.
- Separate Einträge für intern und extern ->
Beispiel: homeassistant.meinedomain.de zeigt extern auf die öffentliche IP, während intern ein separater Name wie ha-intern.meinedomain.de auf die private IP zeigt.
Vorteil: klare Trennung; Nachteil: Nutzer müssen zwei verschiedene Namen kennen.
In den meisten Fällen ist die erste Variante (Split-DNS mit identischem Namen) die elegantere Lösung, weil man nur einen Hostnamen braucht und alle Zertifikate eindeutig dazu passen. In diesem Beitrag werde ich jedoch unterschiedliche Namen nutzen, da Homeassistant explizit einen internen und externen Namen haben möchte. Weiterhin kann es vorkommen, dass der DNS Cache ggf. bei einem Netzwerkwechsel nicht gelehrt wird und damit dann auch im externen Netz versucht wird auf die interne Adresse zuzugreifen (wenngleich ich das bis dato bei keinem meiner Geräte erlebt habe...)
Umsetzung
Zielsetzung:
Unser Ziel ist es, einen internen Dienst (im Beispiel Home Assistant) mit einem öffentlichen Zertifikat nutzbar zu machen.
In meinem Fall verwende ich unterschiedliche Subdomains für den internen und den externen Zugriff. Man kann aber genauso gut die gleiche Subdomain für beide Szenarien einsetzen (Siehe dazu die Erklärung im orherigen Absatz).
Aufbau meines Systems:
Interne URL: hass.domain.de → löst auf 192.168.2.12
Externe URL: hasssrv.domain.de → löst auf die öffentliche IP eines Reverse Proxy, der über ein VPN in das interne VLAN weiterleitet
Ziel:
Wir möchten nun erreichen, dass sowohl für hasssrv.domain.de als auch für hass.domain.de ein gültiges Let’s Encrypt Zertifikat genutzt wird.
Vorgehen – erster Schritt:
Dafür müssen wir zunächst das Zertifikat beantragen. Dazu nutzen wir die DNS-Challenge, um Let’s Encrypt nachzuweisen, dass wir die Inhaber der Domain sind. Auf Basis dieser Challenge wird das Zertifikat für die gewünschte(n) Subdomain(s) ausgestellt. Der vollständigkeit halber sei noch gesagt, dass man mit der DNS Challenge auch sog. wildcard Zertifikate erhalten kann (diese sind für alle subdomains gültig). Ich nutze jedoch eigentlich nie entsprechende Zertifikate, daher nutze ich Namenbezogene).
Zertifikat erhalten
Zuerst müssen wir ein gültiges Zertifikat beantragen.
Dafür nutze ich meinen internen Zertifikatsserver, der alle Domain-Zertifikate verwaltet und automatisch auf meine Systeme verteilt.
Falls du nicht weißt, wie man ein DNS-Challenge-Zertifikat erstellt, empfehle ich dir, die Schritte in diesem Beitrag von mir zu befolgen:
-> Let’s Encrypt: separaten Zertifikat-Server aufsetzen mit DNS-01-Challenge (Das gesamte Prozedere hier noch einmal aufzuschreiben wäre unnötig – zumal sich Details hin und wieder ändern und ich den Artikel ohnehin bald aktualisiere. Darum verweise ich direkt auf die Anleitung ^^).
In dem verlinkten Beitrag erkläre ich außerdem, wie man den Deployment-Server aufbaut, der die Zertifikate automatisch auf unterschiedliche Systeme verteilt. Falls du das nicht benötigst, kannst du diesen Teil einfach überspringen.
Kurzfassung:
Du benötigst ein Zertifikat, das für alle Dienste und Subdomains gültig ist, die du absichern möchtest.
In meinem Beispiel sind das:
- hass.domain.de (interne URL)
- hasssrv.domain.de (externe URL über den Reverse Proxy)
sudo certbot certonly \\
--authenticator dns-netcup \\
--dns-plugin-credentials /dns/.credentials.ini \\
--dns-plugin-propagation-seconds 900 \\
--non-interactive \\
--expand \\
--server https://acme-v02.api.letsencrypt.org/directory \\
-d 'hass.domain.de' \\
-d 'hasssrv.domain.de' \\
--agree-tos
--email mail@mymail.de
--deploy-hook '/opt/deploy.py -domainspath="$RENEWEDLINEAGE" -reneweddomains="$RENEWEDDOMAINS"'
sudo certbot certonly \ # Holt nur das Zertifikat, ohne es zu installieren (Wir verschieben es ja woanders hin)
--authenticator dns-netcup \ # Nutzt das Netcup-DNS-Plugin für die Challenge (Meine Domains liegen da...)
--dns-plugin-credentials /dns/.credentials.ini \ # Pfad zur Datei mit den Netcup-API-Zugangsdaten
--dns-plugin-propagation-seconds 900 \ # Wartezeit (in Sekunden), bis DNS-Änderungen garantiert wirksam sind
--non-interactive \ # Führt alles automatisch ohne Rückfragen aus
--expand \ # Fügt neue Domains zum bestehenden Zertifikat hinzu, falls nötig
--server https://acme-v02.api.letsencrypt.org/directory \ # Verwendet den offiziellen Let’s Encrypt ACME-Server
-d 'hass.domain.de' \ # Erste Domain, die abgesichert werden soll
-d 'hasssrv.domain.de' \ # Zweite Domain, die abgesichert werden soll
--agree-tos \ # Akzeptiert automatisch die Let’s Encrypt Nutzungsbedingungen
--email mail@mymail.de \ # Kontaktadresse für wichtige Zertifikatsmeldungen
--deploy-hook '/opt/deploy.py -domainspath="$RENEWED_LINEAGE" -reneweddomains="$RENEWED_DOMAINS"'
# Script, das nach Erneuerung das Zertifikat automatisch verteilt (anderer Beitrag)
DNS vorbereiten
Jetzt haben wir ein Zertifikat, das für unsere internen Dienste funktioniert.
Damit unsere Geräte dieses Zertifikat auch akzeptieren, müssen sie den Dienst über die richtigen Hostnamen erreichen können. Nur so passt der Name im Zertifikat mit dem aufgelösten DNS-Namen überein und die Verbindung wird als vertrauenswürdig eingestuft.
Dafür legen wir entsprechende A-Records an:
- Einen öffentlichen Eintrag bei unserem Domain-Anbieter (Netcup)
- Einen internen Eintrag im lokalen DNS-System (Split-DNS)
hasssrv.domain.de IN A <<deine IP Adresse)
Im internen DNS-System legst du den A-Record für dieselbe Subdomain an, nur mit der internen IP oder, wie in meinem Fall, ein "extra" Eintrag:
hass.domain.de IN A 192.168.2.12
Fertigstellen
Im letzten Schritt muss das Zertifikat noch auf den Home Assistant Server gebracht und dort eingebunden werden.
Der Standard Weg wäre, das Zertifikat manuell in den Ordner /config/ssl (oder wo auch immer du die Zertifikate haben willst...) zu kopieren und anschließend in der configuration.yaml einzutragen.
In meinem Setup erledigt das jedoch mein Deployment-Server:
Er verschiebt das Zertifikat automatisch auf einen gemeinsamen Share zwischen Home Assistant und dem Zertifikatsserver.
Von dort wird es per Command Template (definiert in Homeassistant) in den /config/ssl-Ordner kopiert.
So stelle ich sicher, dass beim Start von Home Assistant immer ein aktuelles Zertifikat vorhanden ist.
In meinem Beispiel sieht die Config für http dann so aus:
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.100.0/24
server_port: 443
ssl_certificate: ssl/domain.de/fullchain.pem
ssl_key: ssl/domain.de/privkey.pem
ip_ban_enabled: true
login_attempts_threshold: 2
cors_allowed_origins:
- https://hasssrv.domain.de
- https://hass.domain.de
Der Vollständigkeit halber zeige ich hier noch mein YAML-Snippet, mit dem ich das Zertifikat automatisch vom gemeinsamen Share kopiere.
Das ist insbesondere dann hilfreich, wenn du – wie ich – einen separaten Zertifikatsserver aufgesetzt hast, der die Zertifikate zentral verwaltet und verteilt:
copy_cert: "cp -R /share/tls_certificates/domain.de /config/ssl/"
Da Let’s Encrypt-Zertifikate eine maximale Gültigkeit von 90 Tagen haben, muss Home Assistant spätestens innerhalb dieses Zeitraums das Zertifikat neu einspielen und anschließend neu starten.
In meinem Setup erledige ich das über das Home Assistant Plugin “Certificate Expiry” in Kombination mit einer Automation:
Trigger: 20 Tage vor Ablauf des Zertifikats
Aktion:
- Neues Zertifikat vom Share in den SSL-Ordner kopieren
- Home Assistant automatisch neustarten
- Überprüfung, ob sich das Ablaufdatum wieder auf ~90 Tage verlängert hat
-->Fallback: Sollte die Verlängerung fehlschlagen, erhalte ich sofort eine Benachrichtigung auf mein Smartphone.
-> Damit habe ich immer noch 20 Tage Puffer, um den Fehler manuell zu beheben.
Back…