Mittwoch, 31. August 2022

Wie funktioniert Wireguard im Detail

Ich habe pivpn mit wireguard installiert und das hat einfach funktioniert. Also wirklich einfach! Jetzt bin ich etwas tiefer vorgedrungen, um zu verstehen wie man die Konfiguration lesen muss und um Konfigurationen sichern und übertragen zu können. Für pivpn gibt es eine simple Backup/Restore Beschreibung (mit Stolperstellen siehe auch).

  • Wireguard unterscheidet in der Installation nicht zwischen Server und Client - nur die Konfiguration bestimmt: wer mit wem.
  • Wireguard implementiert im System ein vollwertiges Netzwerkinterface mit Tunnel - quasi eine virtuelle Netzkarte mit virtuellem Kabel. Die Netzwerkadministration erfolgt mit Standard Boardmitteln.
  • Die Endpunkte authentifizieren sich mit einer Public-Key-Authentifizierung.
  • Soll der Endpunkt ein VPN Router sein, muss er entweder schon der Router im Netzwerk sein oder NAT aktiviert haben.

Die Konfigurationsdatei für einen Endpunkt wird normal im INI Format erstellt - bei OpenWrt ist sie in der network config integriert. 

OpenWrt kann sowohl Server als auch Client Funktionalität abbilden, ich habe mich hier zunächst auf OpenWrt Router als VPN Einwahl Server beschränkt.

Nachtrag 7.9.2022: Während ich noch immer am Artikel schreibe bringt die neue Version OpenWrt 22.03 jede Menge Änderungen und meine ganzen Scripts für Wireguard sind hinfällig!?

Elemente der wireguard Konfiguration

Der Abschnitt Interface konfiguriert den lokalen Endpunkt und damit das Netzwerkinterface. Er enthält 

  • PrivateKey - der private Schlüssel des lokalen Endpunktes, 
  • Address - die Tunnel IP der lokalen Maschine mit Netzwerkmaske,
  • ListenPort - optional, wireguard startet selbst bei 51820,
  • MTU - optional, default Wert ist 1420

Der Abschnitt Peer konfiguriert die Netzwerkverbindung. Er enthält 

  • PublicKey - den öffentlichen Schlüssel des anderen Endpunktes zum Aufbau des Tunnels,
  • AllowedIPs - 
    • die IP die im Tunnel zulässig für die Verbindung ist,
    • die Netzwerke die durch den Tunnel erreichbar sind,
    • ist hier 0.0.0.0/0 eingetragen soll der gesamte Traffic durch den Tunnel gehen.
  • EndPoint - die IP und Port des anderen Endpunktes zum Aufbau des Tunnels.
  • PresharedKey - optional als zusätzliche Sicherheit, dieser Key muss auf beiden Seiten gleich eingetragen werden.


[Interface]
PrivateKey = xxx        # local endpoints private key
Address = 10.6.0.3/24   # host address & network segment for tunnel
# ListenPort = 51820    # optional, startpoint
# MTU = 1420            # optional, default

[Peer]
PublicKey = xxx       # remote endpoints public key
AllowedIPs = 192.168.2.0/24, 10.6.0.0/24

Für maximale Sicherheit verbleibt der private key immer auf dem Endpunkt wo er erzeugt und verwendet wird. Der daraus abgeleitete public key wird den anderen Endpunkten zur Anmeldung ausgehändigt. Das Ganze entspricht damit der typischen Verteilungsform im ssh public key Verfahren.

Mehrere Peer Abschnitte repräsentieren beim "Server" die Clients.

Typische Server / Client Einträge

Sowohl der [Interface]-Eintrag Address als auch der [Peer]-Eintrag AllowedIPs kann die Routing Tabelle des lokalen Endpunktes verändern und bestimmt somit das Routing durch den Tunnel!

Address - eine Host Adresse mit Netzwerksegment- oder Host-Maske aus dem privaten Bereich !

AllowedIPs - ist in Senderichtung wie eine Routingtabelle in Empfangsrichtung wie eine Zugangskontrolle. 

Auswirkung auf Routing

Address: 

  • Hostadresse mit Netzwerksegmentmaske im Interface Abschnitt Endpoint S - damit ist das gesamte Tunnelsegment erreichbar.
  • Hostadresse mit Hostmaske im Interface Abschnitt Endpoint C - damit wird Verbindung aufgebaut.

AllowedIPs : 

  • Hostadresse des Endpoint C im Peer Abschnitt Endpoint S (Dazu den Artikel beachten)
  • Netzwerkadresse des Tunnels im Peer Abschnitt Endpoint C - damit wird das Tunnelsegment erreichbar. Diese kann weggelassen werden, dann müssen weiter Adressen eingetragen werden die über den Tunnel genutzt werden sollen.
  • Zusätzliche weitere Netzwerksegmente die über diesen Tunnel erreichbar sein sollen, geht für beide Richtungen.
  • Wird im Endpoint C 0.0.0.0/0 eingetragen, wird der komplette ausgehende Verkehr durch den Tunnel geroutet - damit ist dieser Endpoint im lokalen Netzwerk nicht mehr erreichbar! 

Konkretes Beispiel 

Hinweis: Diese folgende funktionierende Konfigurationen bitte nicht per Copy & Paste verwenden - sonst ist das Netzwerk zwar virtuell aber nicht mehr privat! Die eigenen Keys werden mit dem wg genkey|pubkey|genpsk Kommando erzeugt. Der Start einer Konfiguration erfolgt mit dem wireguard Tool wg-quick. 

Hier im Beispiel kommen zwei Key Pärchen zum Einsatz, diese werden so auf die INI Dateien verteilt:

Serverkonfiguration

[Interface]
PrivateKey = CPQJyuFRCoospbw6q23aOV6NENCzPDpvJc6Dr9ibg1A=   # server private key
Address = 10.6.0.1/24                                       # host ip & tunnel network segment
MTU = 1420
ListenPort = 51820

[Peer]
PublicKey = emqkXECnmtkbnVDNU0UHBgSuDNXQeqp/fcPoRHW/8GM=    # first remote client public key
AllowedIPs = 10.6.0.2/32

[Peer]
PublicKey = xxx       # second remote clients public key
AllowedIPs = 10.6.0.3/32

Eine passende Client Konfiguration

[Interface]
PrivateKey = 2M8G4JTwXgGiSYrVXLESSb9GGer8P7Zrs5hMQqrKJl4=    # first remote client private key
Address = 10.6.0.2/32                                        # host ip

[Peer]
PublicKey = pGN89OLvIF9CVpCwRGUUiAiN38mFT/aQ8ND+8T8lsCI=     # server public key
AllowedIPs = 10.6.0.0/24
Endpoint = server.ip:51820

Clientkonfiguration erzeugen

Typischerweise generiert man einen privaten key (wg genkey), erzeugt daraus einen public key (wg pubkey), der wird am Server eingetragen und final komplettiert man seine Client-Konfiguration mit dem public key vom Server sowie der Peer IP-Adresse. 

Interaktiv OpenWrt GUI

OpenWrt (ab Version 22.03): Die Erzeugung einer Clientkonfiguration ist jetzt in Luci eingebaut (Network / Interface / VPN Interface / Edit / Registerkarte Peers / Add peer) und man kann diese auch als QR Code ausgeben (Edit Peer / Configuration Export). Allerdings nicht vollständig, man muss manuell nacharbeiten. Auch der Import einer conf Datei ist möglich (anstatt Add peer - Import configuration as peer). 

OpenWrt (bis Version 21.02): Man muss in etwa so vorgehen:

  1. private key: Mit dem Wireguard Client einen neuen Tunnel erstellen oder den QR-Code auf der OpenWrt wireguard Statusseite einscannen (der QR Code wechselt bei jedem Refresh).
  2. Der WG Client erzeugt immer den public key und zeigt diesen an: in die Zwischenablage kopieren.
  3. Im Browser zum Webinterface OpenWrt verbinden und anmelden, Network / Interface / VPN Interface / Edit / Registerkarte Peers / add peer,
  4. den public key aus Zwischenablage einfügen, IP Addresse bestimmen und eintragen, speichern, save & apply,  network service neu starten.
  5. Zum Abschluss am Client die Konfiguration mit IP Adresse und Endpunkt vervollständigen, 
  6. final die Verbindung testen (z.B. mobiles Netzwerk).

... klingt umständlich - funktioniert aber! Wenn man weiß was man tun muss.

pivpn arbeitet per CLI

Man kann per Befehl einen neuen Client am Server erzeugen, sich die komplette config ausgeben lassen und am Client importieren. Die komplette config, inklusive privater Schlüssel, wird auf dem Server gespeichert. Das macht die Verteilung an mobile Clients einfach (QR Code einscannen). 

Linux Client in der Konsole erzeugen. 

Ich habe dafür mal zwei Einzeiler gemacht

printf "[Interface]\nPrivateKey = $(wg genkey)\nAddress = \n\n[Peer]\nPublicKey = \nEndpoint = \nAllowedIPs = \nPersistentKeepalive = 25\n"|tee wg1.conf
cat wg1.conf|grep PrivateKey|awk '{printf($3)}'|wg pubkey

Die erste Zeile erzeugt eine conf Datei als Gerüst, diese kann man mit nano editieren und vervollständigen.

Die zweite Zeile zeigt den PublicKey vom Peer um ihn per copy & paste in die Server Konfiguration einzutragen.

Split Tunnel und Routing

(ToDo) Bisher nur Notizen

Split Tunnel: AllowedIPs mit einzelnen Segmenten z.B. 192.168.178.0/24 bewirken eine Teilung des Netzwerkverkehrs. 

DNS Namensauflösung lokal, im Internet oder im Tunnel. Diese Konfiguration muss man sich je nach Verwendung des Tunnels gut überlegen.  Hier habe ich ein paar Erklärungen gefunden. 

Wenn man aus dem Tunnel wieder "heraus" will: muss Routing am Endpunkt aktiv sein (net.ipv4.ip_forward).  Ist der Endpunkt nicht der Router im Netzwerk, muss NAT aktiviert werden.

Start des wireguard Interfaces

(ToDoBisher nur Notizen

Bei pivpn und OpenWrt wird das wireguard Interface automatisch gestartet. Bei einer manuellen Konfiguration von wireguard wird dieser Start nur vorbereitet, siehe meinem Beitrag verbundene Netzwerke.

Der Tunnel wird nur bei Bedarf aktiviert. Für Tests auf Verbindung muss man ihn verwenden - eine IP im Tunnel anpingen, oder z.B. den DNS Server so setzen, dass der Tunnel verwendet wird.

Ein paar Scripts für OpenWrt

Zur einfacheren Konfiguration auf einem OpenWrt Router habe ich ein paar Scripts geschrieben. Der Code wurde inspiriert von Beispielen im OpenWrt Wiki. Die Scripts sind sicher nicht perfekt und nur in meinem Anwendungsfall getestet!

Leider ist wget in OpenWrt nur ein Link auf uclient-fetch und damit eingeschränkt, z.B. fehlt Brace Expansion und Parameter -N. Damit muss jede Datei einzeln laden:

wget https://raw.githubusercontent.com/heinz-otto/scripts/master/uci/wireguard-import-key.sh
wget https://raw.githubusercontent.com/heinz-otto/scripts/master/uci/wireguard-export-conf.sh
wget https://raw.githubusercontent.com/heinz-otto/scripts/master/uci/wireguard-setup-server.sh
chmod +x wireguard-*.sh

Wireguard Server Interface auf dem OpenWrt Router erzeugen 

Mit dem Script "setup-server" kann man einen neuen wireguard Server auf OpenWrt erzeugen.

Beim OpenWrt Router erzeugt man ein Netzwerkinterface! Deshalb ist es wichtig diesem Interface eine Netzwerkadresse und keine Hostadresse zu geben (mask < 32), ansonsten entsteht kein Eintrag in der Routingtabelle. Man kann sich zwar verbinden aber diese Verbindung ist nutzlos.

# einen neuen Server mit default Werten: $(wg genkey) IP 192.168.9.1/24, Port 51820 und dem Namen vpn erzeugen 
./wireguard-setup-server.sh
# einen Server mit vorhandenen Werten erzeugen
./wireguard-setup-server.sh 'CPQJyuFRCoospbw6q23aOV6NENCzPDpvJc6Dr9ibg1A=' '10.6.0.1/24' 51820 wg0

Wireguard Peer auf dem OpenWrt Router importieren

Das Script "import-key" dient dazu einen Peer zu importieren, der public key genügt, zusätzlich kann ein Name  und ein PresharedKey sinnvoll sein. Hat man einen existierenden peer kann man so leicht den public key auf dem OpenWrt Router importieren und anschließend die fehlenden config Werte mit dem Script "export-conf" ausgeben.

Beim Import selbst wird kein Key extra direkt im Dateisystem gespeichert - alles wandert in die OpenWrt Konfiguration! In der folgenden Codebox sind 3 Beispiele zur Verwendung. Mit dem letzten Beispiel kann man das pivpn Scenario nachbilden bzw. Werte von einem pivpn Server übertragen. Im Beispiel 2 und 3 wird der erzeugte private key im Dateisystem gespeichert. 

Achtung! das flashen eines neues OpenWrt Images erhält zwar die Konfiguration, aber alle "eigenen" Daten im Dateisystem (auch installierte Software) gehen verloren!

# 1. vorhandene public key importieren
./wireguard-import-key.sh "pGN89OLvIF9CVpCwRGUUiAiN38mFT/aQ8ND+8T8lsCI="
# 2. neuen User anlegen - Speicher Ort für private key anlegen
mkdir -p /etc/wireguard/keys
./wireguard-import-key.sh $(wg genkey | tee /etc/wireguard/keys/vpnUser1_priv | wg pubkey) $(wg genpsk) "vpnUser1"
# 3. neuen User mit preshared key erzeugen (oder Werte aus einem wireguard Server nehmen)
username='vpnSuperUser1'
PublicKey=$(wg genkey | tee /etc/wireguard/keys/${username}_priv | wg pubkey)
PresharedKey=$(wg genpsk)
# die Werte in OpenWrt importieren
echo "$PublicKey $PresharedKey $username"|xargs ./wireguard-import-key.sh

Das jeweilige wireguard Interface muss nach dem import / neu anlegen neu gestartet werden. Entweder über die WebGUI oder per Kommadozeile (alternative Beispiele): 

ifdown wg0 && ifup wg0
ubus call network.interface.wg0 down && ubus call network.interface.wg0 up

Konfiguration ausgeben

Bei pivpn kann man sich eine config als Text oder QR Code ausgeben lassen. Das Script "export-conf" bildet dies Funktion nach. Mit dem Script kann man sich eine bestimmte  (sucht nach public key oder Name) - oder alle configs - als Text oder QR Code ausgeben lassen.

./wireguard-export-conf.sh [<PublicKey|Name>] [-qr]

Konfigurationen kopieren

Mit ein paar Zeilen Code, die man auf dem pivpn Server ausführt, kann man die wireguard Konfiguration auf den OpenWrt Router übertragen. Das Script "setup-server" wird dabei remote über ssh auf dem OpenWrt Router gestartet.

export remote=user@host
sudo -sE <<-'EOS'
WG_IF=wg0
WG_KEY=$( wg showconf $WG_IF|grep PrivateKey| awk '{printf($3)}' )
WG_ADDR=$( ip addr show $WG_IF|grep 'inet '|grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'| tr -d $'\n' )/32
WG_PORT=$( wg showconf $WG_IF|grep ListenPort| awk '{printf($3)}' )
# transfer to OpenWrt Router
echo "$WG_KEY $WG_ADDR $WG_PORT $WG_IF"|ssh ${remote} 'xargs ./wireguard-setup-server.sh'
EOS

Mit dem schon erwähnten "import-key" Script kann man in gleicher Art und Weise auch sämtliche Peer Konfigurationen von pivpn auf den OpenWrt Router übertragen. Achtung: Die private keys der peers bleiben exklusiv auf dem pivpn Server! Auch hier wird das Script "import-key" remote per ssh auf dem OpenWrt Router gestartet.

sudo -sE <<-'EOS'
for username in $( cat /etc/wireguard/wg0.conf|grep "### begin " | awk '{printf ($3 " ")}' ); do
   for var in PublicKey PresharedKey AllowedIPs; do
       val=$( cat /etc/wireguard/wg0.conf|grep "### begin ${username}" -A4|grep ${var}| awk '{printf($3)}' )
       eval "${var}='${val}'"
   done
   echo "$PublicKey $PresharedKey $username $AllowedIPs"|ssh ${remote} 'xargs ./wireguard-import-key.sh'
done
ssh ${remote} '/etc/init.d/network restart'
EOS

Backup & Restore

Ohne weiteres zutun wäre nach einem sysupgrade (flash new firmware) oder einem Reset to defaults, auch bei vorhandenem Standard Backup Archive, jegliche Erweiterung an installierter Software und vor allem die in /etc/wireguard erzeugten privaten Keys verloren! Um das zu verhindern gibt es eine kleine Erweiterung, beschrieben im OpenWrt Wiki

Zusätzlich wird noch der Pfad /etc/wireguard in der Datei sysupgrade.conf aufgenommen, damit gelangen auch die Keys in das Backup Archiv. Diese Datei kann auch über Luci WebUI eingesehen/editiert werden: System / backup Flash Firmware / Reiter Configuration. Alle Befehle kurz und bündig:

opkg update
opkg install libustream-mbedtls
uclient-fetch -O opkg-extras.sh "https://openwrt.org/_export/code/docs/guide-user/advanced/opkg_extras?codeblock=0"
. ./opkg-extras.sh
uclient-fetch -O hotplug-extras.sh "https://openwrt.org/_export/code/docs/guide-user/advanced/hotplug_extras?codeblock=0"
. ./hotplug-extras.sh
echo "/etc/wireguard/ >>/etc/sysupgrade.conf"

Ein ganz normales Backup Generate Archive erzeugt jetzt ein Archiv mit dem erwarteten Inhalt. Ich bin allerdings noch nicht sicher, ob man nach der Installation von Software Paketen einmalig das "Opkg Profile" sichern muss (erzeugt offenbar eine Datei /etc/config/opkg):

opkg save

Die Wiederherstellung des alten Zustandes dauert "einen kleinen Moment", nach dem ersten Neustart erfolgt noch ein Zweiter und Dritter, die dafür zuständigen hotplug extras enthalten bei einen schnellen Blick 10 Sekunden Pause - also ruhig bleiben und mit der ersten Anmeldung warten. Bei mir waren es bei Versuchen mit einer VM ca. 1,5 min.

ToDo? 

Variablen Namen vereinheitlich.

Literatur

https://www.thomas-krenn.com/de/wiki/WireGuard_Grundlagen#Funktionsweise

Code

Code Block

Text

1 Kommentar: