Wieso nicht einfach Cloudflare?
In den beiden letzten Artikeln habe ich mich des Problems angenommen, das heimische NAS via Cloudflare Tunnel aus dem Internet heraus erreichbar zu machen und dann auch noch abzusichern. Das Setup funktioniert soweit, aber es hat zwei Haken:
- Den ersten Haken erwähnte ich bereits im entsprechenden Post: Der Status des Streamings ist in der Community ungewiss
- Und natürlich geht unser Traffic über Cloudflare. Jeder darf selbst entscheiden, ob und in wie weit man Cloudflare vertrauen möchte, aber ich verstehe jeden, der seinen Traffic gern vollständig in der eigenen Hand hat.
Wie im Beitrag zum Setup des Cloudflare Tunnels erwähnt, haben manche von uns einfach nicht die Möglichkeit, Portfreigaben einzurichten und müssen deshalb Kompromisse eingehen. Wenn wir keine Portfreigaben einrichten können oder wollen, dann brauchen wir eine weitere Komponente, mit der wir aus dem Internet heraus auf unsere Services im Heimnetzwerk zugreifen können.
Eine Möglichkeit, die uns sehr viel Kontrolle ermöglicht, wäre ein eigener Zugriffstunnel mit Hilfe eines virtuellen privaten Servers, eines Reverse Proxies und WireGuard. Ganz ohne Cloudflare und genau dieses Setup möchte ich hier starten.
Ein Wort (bzw. Absatz) der Warnung
Ich sage so einfach, wir nutzen einen privaten virtuellen Server (kurz VPS), da dieser viel Kontrolle in die eigene Hand legt. Aber Kontrolle ist Macht und mit großer Macht kommt auch große Verantwortung (Onkel Ben). Ein virtueller privater Server - gerade, wenn dieser als Cloud Service irgendwo gehostet ist - ist ein vollständiger Server, der aus dem Internet heraus erreichbar ist. Ein Server, auf dem ihr vermutlich Admin-Rechte besitzt und der wahrscheinlich auch erstmal keine Firewall aktiviert hat. Kurzum, ein Server, den ihr selbst bestmöglich schützen müsst!
Klingt abschreckend, aber probiere es trotzdem aus! Meine Empfehlung für VPS Hosting Services ist Hetzner. Die bieten für den Anfang gute Tutorials zur Ersteinrichtung eines Ubuntu Servers und der grundlegenden Sicherheit bei frischen Servern. Außerdem sind die wirklich günstig. Aber Achtung, am Ende des Tutorials werden wir nftables nutzen, um den Datenverkehr auf unserem Server zu steuern. Mit nftables kann man aber auch das “Eingangstor” etwas schließen und Firewall-Regeln etablieren. Wenn ihr Hetzners Ersteinrichtung und Absicherung folgt, lasst erstmal die Finger von iptables und der ufw. Wir setzen diese Regeln am Ende per nftables um und wir wollen keine konkurrierenden Systeme für diese Regeln
Mit diesem Disclaimer komme ich aber auch schon zu den Voraussetzungen.
Unser Ziel
Mein (oder vermutlich unser) Problem ist noch immer, dass ich gern Services aus meinem Heimnetzwerk erreichen möchte, wenn ich unterwegs bin und dies kriege ich aufgrund von CGNat nicht “einfach so” gelöst.
Das Ziel ist deshalb, einen virtuellen privaten Server im Internet zu hosten (beispielsweise bei Hetzner) und diesen als Tunnel-Gateway zu meinem Heimnetz einzurichten.
| |
Mein VPS soll als Art Türsteher agieren und ein paar Anforderungen erfüllen:
- Eingehenden Traffic nur nur über VPN oder mit TLS verschlüsselt (HTTPS) erlauben
- Ich möchte alle gewünschten Endpunkte per VPN erreichen
- Ich möchte in der Lage sein, eine Auswahl an Endpunkten über HTTPS (das freie Internet) erreichbar zu machen
- Die Endpunkte sollen eine nette URL besitzen (in etwa so:
opencloud.meinedomain.de,photos.vpn.meinedomain.de, …) - HTTPS-Endpunkte im Internet möchte ich, sofern vorhanden, mit einer extra Authentifizierung (am besten SSO) absichern
Wir werden einige Dinge installieren und konfigurieren müssen, um dieses Setup zu erreichen. Speziell, wenn du auch ein NAS hast, das sich beim Thema WireGuard etwas querstellt. Deshalb ist dieser Beitrag in mehrere Teile aufgeteilt. Hier in Teil 1 wollen wir erstmal den Server weitestgehend vorbereiten und die notwendigen Apps installieren.
Welche Voraussetzungen gibt es?
Um diesem Tutorial folgen zu können, brauchst du…
- … einen Server, den du aus dem Internet heraus erreichen kannst und auf dem du root Rechte hast
- … eine Domain oder einen DynDNS, wie beispielsweise von IPv64 oder DuckDNS, die auf deinen Server zeigt
- … ein NAS mit mindestens einem Kernel Version 3.10 - ansonsten wird WireGuard nicht laufen (hier ist eine Kompatibilitätsliste)*
*Du brauchst natürlich kein NAS, wenn du ein anderes System, wie einen Raspberry Pi von außen erreichbar machen möchtest. Der zweite Teil dieses Tutorials befasst sich aber mit den Gegebenheiten eines NAS.
Wenn du also all diese Voraussetungen erfüllst, können wir loslegen.
Starten wir das Setup
Jetzt da es tatsächlich losgeht und wir bereits einen Überblick haben, was wir erreichen wollen, schauen wir uns die zu installierenden Komponenten einmal an:
| Rolle | Applikation | Grund | Installationsweise |
|---|---|---|---|
| VPN | WireGuard | Damit verbinden wir uns zum Heimnetz | nativ / bare metal |
| Reverse Proxy | Caddy | Zuständig für das Routing zu ansprechenden Subdomains und Bezug von Zertifikaten für HTTPS | nativ / bare metal |
| Container Manager | Docker | Zum Aufbau unserer weiteren Apps | nativ / bare metal |
| DNS Server | dnsmasq | Für ansprechende Routen über VPN (zum Zeitpunkt des Schreibens aber noch nicht sicher) | nativ / bare metal |
| VPN Admin Seite | wireguard-ui | Macht die Konfiguration der Clients sehr viel einfacher | Docker Container |
| SSO Provider | Authentik | Zur Absicherung unserer Routen ins Internet. Bietet besseren Auth-Schutz (optional) | Docker Container |
Dass Docker nativ installiert werden muss, wird klar sein. Bei WireGuard und Caddy habe ich mich dafür entschieden, weil diese Applikationen recht systemnah sind. Caddy gibt auf seiner Installationsseite sogar an, “Install Caddy as a system service. This is strongly recommended, especially for production servers.”. Daran halten wir uns natürlich, denn so kann Caddy zwischen den Netzwerkinterfaces eth0 und wg0 routen, ohne, dass wir große Routingtabellen anlegen müssen.
WireGuard nativ ergibt Sinn, damit eben genau dieses Netzwerkinterface wg0 auf dem Host liegt. WireGuard ist so auch unabhängig von Docker und wir können Automatisierungen nutzen, um Neustarts einzurichten, wenn die Konfig geändert wird (über watcher). Wie immer, mein Tutorial ist opinionated, dir steht frei, es anders umzusetzen.
WireGuard
Nun soll es aber losgehen. Als erstes installieren wir WireGuard. Meine Befehle setze ich auf einem Ubuntu Server ab und evtl. können diese bei deinem System abweichen. WireGuard gibt aber in ihrer Doku an, wie es auf jedem System installiert werden kann. Bei Ubuntu reicht:
| |
Dem Quickstart (für uns angepasst) folgend, müssen wir nun das Netzwerk Interface anlegen und mit unserer wg0 verknüpfen. Die Anlage des Interfaces sollte auch die entsprechenden Kernel Module laden.
| |
Dann vergibst du einen IP-Addressbereich, ich nutze im Tutorial das Beispiel 10.8.0.1/24. Dir steht frei, auch das anzupassen (denk dann aber später daran).
| |
Jetzt erstellst du die wg0.conf, die später deine Konfiguration enthält:
| |
Denn wenn diese Datei nicht existiert, können wir diese nicht mit der Konfig für das wg0 Interface verlinken:
| |
Und zum Schluss sollten wir das Interface noch starten:
| |
Nun teste bitte einmal, ob folgender Befehl einen Fehler wirft oder nicht (dann kommt kein Ergebnis):
| |
Sofern keine Meldung kommt, kannst du weiter machen. Denn als nächstes wollen wir den WireGuard Service immer neustarten, wenn sich diese Konfigdatei wg0.conf ändert. Dafür erstellen wir einen watcher, die diese Datei(en) überwacht und bei Bedarf einen Service aufruft, der wireguard neu startet.
Die benötigten Dateien werden angelegt:
| |
Und nun mit Leben befüllt:
| |
In diese .path Datei muss nun folgender Inhalt:
| |
Kurze Erklärung zu den Abschnitten:
[Unit] => Description: Beschreibt, wozu dieser Watcher erstellt wird (inkl. Link zu ManPage)[Path] => PathChanged: Enthält den Pfad, der auf Änderungen überwacht werden soll. Hier mit Variable für alle Configs. Bei uns wird dort nurwg0.confliegen.[Install] => WantedBy: Beschreibt den Systemzustand, ab welchem dieser Service ausgeführt werden kann (Beispiel nach einem Neustart).multi-user.targetheißt, das System muss bereit sein, dass sich alle User anmelden könnten. Also fertig hochgefahren.
Speichern und schließen:
| |
Nun geht es weiter mit der .service Datei. Erst öffnen:
| |
Dann mit folgendem Inhalt befüllen:
| |
Auch hier eine kurze Erläuterung:
[Unit]: Erst kommt wieder eine Beschreibung. Dann wird sichergestellt, dass das Netzwerk bereit ist. Die Limits verhindern eine Blockierung des Services nach zu vielen Restart-Versuchen des Services in kurzer Zeit.[Service]: Der danach kommende Befehl soll nur einmalig ausgeführt werden, dann kommt der eigentliche Restart-Befehl.[Install]: Zeigt die Verbindung zu dem eben erstellten Watcher, sodass diese der Watcher diesen Restart ausführen darf.
Auch diese Datei speichern und schließen:
| |
Jetzt müssen wir den Watcher noch aktivieren:
| |
Mit dem folgenden Befehl kannst du prüfen, ob der Watcher aktiviert ist:
| |
Und damit sollte WireGuard erstmal fertig vorbereitet sein. Weiter zum nächsten Schritt.
Caddy
Auf Caddys Installationsdokumentation habe ich bereits weiter oben schon einmal verwiesen. Und diese Schritte befolgen wir nun auch (so ungefähr 😉).
Erstmal müssen wir uns entscheiden, wo wir das Caddy Binary (die auszuführende Datei) auf dem Server ablegen. Der gewählte Pfad sollte sich in euer $PATH Variable wiederfinden, damit wir Caddy auch ausführen können, ohne immer den exakten Pfad angeben zu müssen. Die Speicherorte, die in $PATH enthalten sind, kannst du prüfen:
| |
Eine Mit : getrennte Liste an Ordner wird ausgegeben. Bei mir sind beispielsweise /usr/bin/ und /usr/local/bin enthalten. Einer dieser Ordner passt schon. Ich werde Caddy in /usr/local/bin ablegen (du musst erneut Pfade anpassen, wenn du es woanders haben möchtest).
| |
Dann laden wir die aktuelle Version von deren GitHub Releases herunter. Passe im folgenden Befehl bitte die zu ladende Version, sowie deine CPU-Architektur an:
| |
Die aktuelle Version zum Zeitpunkt, als ich das hier schreibe, ist 2.10.0 und ich habe eine amd64 Architektur. Du könntest ggf. auch arm64 haben.
Dann kann das dort enthaltene Caddy Binary entpackt werden :
| |
Danach kannst du das Archiv wieder löschen:
| |
Als nächstes müssen wir laut Doku eine caddy Gruppe und User anlegen, dann noch die entsprechende systemd unit Datei besorgen, diese laden und fertig. Dann können wir Routen im Caddyfile hinterlegen. Also weiter gehts, erst Gruppe und User mit eigenem Home-Verzeichnis (alles aus der Caddy Doku):
| |
| |
Es gibt zwei Caddy systemd unit Dateien. Eine für den Service des Reverse Proxies und eine für die API. Wir wollen den Service. Also speichern wir uns diese unter dem Pfad /etc/systemd/system/caddy.service. Wir laden das offizielle caddy.service aus deren GitHub und speichern es auf unserem Server.
| |
Wenn curl nicht verfügbar ist, sudo apt install curl und dann öffne nun die Datei mit nano…
| |
…und prüfe die Ordnerpfade des ExecStart und ExecReload (markierte Zeile 12 + 13):
| |
Dort steht im Standard /usr/bin/caddy, wenn du die Datei aber - wie ich - unter /usr/local/bin/caddy abgelegt hast, musst du den Pfad hier anpassen. Natürlich auf deinen gewählten Pfad. Außerdem siehst du hier, dass User und Gruppe mit “caddy” ausgefüllt sind, weshalb wir eben die Gruppe und den User angelegt haben. Dann ggf. speichern und schließen:
| |
Bevor wir caddy starten können, müssen wir nun noch ein Caddyfile anlegen. In dieser Datei speichern wir später all unsere gewünschten proxy Routen.
| |
Nun kannst du den ersten Inhalt einfügen und anpassen:
| |
Mit diesen Einträgen wird deine Domain auf den Port 5000 zeigen, über welchen später wireguard-ui erreichbar sein wird. Du kannst auch einen redir auf andere Seiten einrichten oder wireguard-ui via vpn.deinedomain.de oder so erreichbar machen. Ganz, wie du magst.
Wir müssen nun noch dafür sorgen, dass der User caddy, den wir angelegt haben, Lesezugriff auf dieses Caddyfile besitzt, deshalb:
| |
Jetzt kann caddy endlich gestartet werden:
| |
Und nun kannst den Status prüfen:
| |
Wenn dieser als active angezeigt wird, hat alles geklappt und du solltest deinedomain.de in den Browser eingeben können und wirst aktuell auf diese Webseite umgeleitet. Das werden wir später natürlich ändern, aber Caddy läuft somit!
Damit sind WireGuard und Caddy installiert 🎉
Docker
Noch motiviert? Dann geht es mit Docker weiter, das letzte Tool, das wir nativ installieren. Auch Docker bietet einen super Installationsanleitung für alle Systeme, an die ich mich für Ubuntu halte. Auch hier gebe ich der Vollständigkeit halber die Befehle wieder. Aber bitte immer prüfen.
Erst werden alle möglichen Pakete, über die Docker installiert sein könnte, deinstalliert. Auf einem frischen System sollte nichts davon vorhanden sein:
| |
Dockers apt repository muss erst eingerichtet werden, dafür müssen wir erst Dockers GPG Schlüssel holen:
| |
Danach das Repository zu deinen apt Quellen hinzufügen:
| |
Und aus diesen Quellen wird nun via apt install einfach Docker installiert:
| |
Und damit ist auch Dockers Installation abgeschlossen 🥳
wireguard-ui als Container installieren
Wir kommen nun zurück zu WireGuard, genauer zur Admin-Oberfläche. Der Entwickler stellt in seinem GitHub Repo verschiedene docker compose Templates zur Verfügung, um diese für gewisse Szenarien zu verwenden. Ein solches Szenario ist die Nutzung der Applikation mit einem nativ installierten WireGuard. Das wollen wir.
Auch hier sind meine Ordner wieder opinionated und du kannst dem folgen oder nicht. Dann denk aber bitte überall an die Anpassungen der Pfade, wenn diese von meinen abweichen. Ich lade meine docker compose Dateien in ~/docker/{ContainerName} und als Template will ich das vorgegebene compose file nutzen. Also laden wir uns das erstmal herunter (wozu wir uns vorher das Zielverzeichnis anlegen):
| |
Und dann laden wir die Datei, um sie uns anzuschauen:
| |
Diese sollte so aussehen:
| |
Auch, wenn version mittlerweile nicht mehr benötigt wird, belassen wir die Struktur der Datei so. Du kannst das Passwort hier ändern oder in eine .env Datei auslagern oder einfach so belassen und später in der UI ändern (aber bitte zügig, das Interface wird vorerst über das Internet erreichbar sein!). Ein SESSION_SECRET kannst du auch erstellen, wenn du deine Cookies und andere Settings über Sessions hinweg, wenn der Container neu startet, behalten willst. Um einen 32stelligen Code zu bekommen, kannst du folgenden Befehl nutzen:
| |
Sobald du alles angepasst hast, schließe nano mit
| |
Dann kannst du den Container starten:
| |
Nun läuft der Container, aber wir haben keinen Port deklariert und in unserem Browser können wir kein localhost:5000 nutzen, denn der Container läuft ja nicht auf unserem Rechner.
Jetzt kommt wieder Caddy ins Spiel und wir legen eine Route zu wireguard-ui an:
| |
Du kannst nun entweder den redir aus deiner Hauptdomain entfernen oder eine weitere Route anlegen. Auf jeden Fall muss am Ende folgende Route in deinem Caddyfile stehen - die Subdomain dabei ist dabei irrelevant, solange ein Wildcard Eintrag bei deinem deinem Domain Registrar vorliegt:
| |
Danach Caddy und auch das wg0 Interface neustarten (ich musste das Interface einmal explizit beenden):
| |
Nun öffne doch mal die von dir gesetzte URL im Browser und die WireGuard-UI sollte sich öffnen! 🥳
Wenn alles passt, dann wollen wir noch dafür sorgen, dass das WireGuard Interface immer automatisch startet, wenn der Server mal rebooted wird. Dafür müssen wir den Service enablen.
| |
Fertig! Dann kann’s weitergehen.
Routing für das VPN erlauben
Bisher sieht alles gut aus, aber der Teufel steckt im Detail und aktuell würde beim VPN noch einiges nicht ganz funktionieren und deshalb müssen wir uns nun dem Routing der Interfaces widmen. Ich mache das anhand von nftables, werde aber nur grundlegend auf die einzelnen Aspekte eingehen. Der Artikel wird sonst einfach zu lang.
Aber wir müssen zwei Dinge tun, um VPN-Verbindungen sauber zu erlauben. Portforwarding aktivieren und dafür gewisse Regeln setzen. Die Erlaubnis ist schnell erteilt:
| |
Suche dort die beiden Einträge
| |
und entferne die #, damit diese Zeilen nicht mehr auskommentiert sind. Dann die Änderungen speichern
| |
Danach die Änderungen aktivieren:
| |
Nun gehen wir an die nftables. Dieses Tool ist dabei mehr, als nur eine Firewall, es übernimmt auch Routing und NAT Übersetzungsregeln. Wichtig für uns ist, hier können wir einerseits festlegen, welche Pakete von unserem Server angenommen werden sollen und welche Pakete wie geroutet werden müssen. Man kann natürlich auch ausgehende Verbindungen konfigurieren. Das Tool ist mächtig! Wer lieber mit iptables arbeitet, macht das gern. Aber wenn du weiß, dass du lieber iptables nutzt, dann weißt du sicher auch, wie du die Regeln dort rein bekommst.
Wichtig, wenn du anfangs meinem Link zum Hetzner-Beitrag gefolgt bist und deinen Server so abgesichert hast (entweder per ufw oder mit iptables), dann solltest du diese wieder deaktivieren, wenn du nun nftables nutzt, um deinen Server zu konfigurieren. Wir wollen keine konkurrierenden Systeme. Alternativ kannst du das benötigte, grundlegende Regelwerk auch in iptables umsetzen. Du musst es dir dann nur übersetzen.
Mit diesem Hinweis können wir nun nftables nutzen und dessen Regelwerk wird in der Datei /etc/nftables.conf persistiert. Diese Datei werden wir also anpassen:
| |
Das grundlegende Regelwerk, was du für dein VPN brauchst, ist dieses hier und die Erklärung ist im Code-Block.
| |
In Zeile 61 musst du eventuell den Namen des öffentlichen Interfaces anpassen. Oft ist es eth0, dieser kann aber bei dir anders sein und müsste dann hier angepasst werden.
Und mit diesem Regelwerk bleibt dein Server sehr offen, es wird also kein eingehender Verkehr limitiert. Ich würde empfehlen, es noch etwas zu verschärfen und es so anzulegen:
| |
Mit dieser Konfiguration limitierst du den eingehenden Traffic so, dass nur die Ports Traffic durchlassen, die wir benötigen (ICMP, SSH, HTTP[S] und VPN) und zusätzlich wird der VPN-Traffic korrekt geroutet. Auch hier sind einige Anpassungen notwendig (siehe markierte Zeilen):
- Zeile 45: Wenn du deinen SSH-Port geändert hast, musst du dies in dieser Regel beachten, sonst sperrst du dich aus!
- In den Zeilen 92, 97 und 170 kann es sein, dass du das Netzwerkinterface
eth0umbenennen musst, wenn dein öffentliches Interface einen anderen Namen hat.
Ich hoffe, der Rest war aufgrund meiner Anmerkungen verständlich und wenn du dich für eine Konfiguration entschieden hast, speichere diese wieder:
| |
Danach können wir die Syntax überprüfen, die Konfig anwenden und nftables neustarten:
| |
Das war eine Handvoll, oder? Ich hoffe, mit den Kommentaren konnte die Konfiguration ein wenig nachvollzogen werden. Auch, wenn das zweite Regelset den Zugriff von außen etwas strikter filtert, reicht das als Absicherung eines Servers nicht komplett aus. Denk noch über Fail2Ban und eine dedizierte Firewall nach. Absicherung des Servers ist außerhalb des Fokuses für diesen Beitrag, aber da ich das Thema mit dem nftables Regelwerk ein wenig angeschnitten habe, wollte ich es nicht unkommentiert lassen.
Für heute fertig!
Und damit ist deine Konfiguration abgeschlossen!
Für diesen Beitrag soll es das auch gewesen sein, da dieser schon irre lang geworden ist und einfach sehr viel Konfiguration beinhaltet. Wir haben WireGuard (inklusive WireGuard-UI), Caddy und Docker installiert und darüber hinaus das Routing für die VPN-Nutzung vorbereitet. Das war Einiges! Im zweiten Teil widmen wir uns dann der eigentlichen Konfiguration des VPN Netzes. Wir werden WireGuard-UI nutzen, um VPN-Clients zu erstellen und wir werden unser NAS ins VPN-Netz bringen, was ebenfalls nochmal einiger Konfiguration bedarf.
Im dritten und letzten Teil geht es dann um die Absicherung der Routen. Welche Routen nur aus dem VPN heraus und welche öffentlich und wie implementieren wir einen Authentication Service. Bis dahin haben wir uns alle einen Kaffee verdient! ☕️
