LUG Erding

VPN mit WireGuard


Dirk Geschke, LUG-Erding


Letzte Änderung: 31.8.2021


Einleitung

Bei WireGuard handelt es sich um eine VPN-Lösung, die im Linux-Kernel integriert ist. Dabei liegt der Schwerpunkt auf zwei Aspekten:

  1. einfach zu erstellen
  2. sehr schnell

Fangen wir hinten an: Da das Protokoll im Kernel integriert ist, spart man sich viel Zeit. Normalerweise würde der Kernel erst die Netzwerkdaten annehmen, dann ins Userland kopieren und dort würden sie dann wieder entschlüsselt um dann irgendwie in den TCP/IP-Stack wieder integeriert zu werden: Dazu muss es dann wohl wieder in den Kernel kopiert werden. Es wäre also besser, wenn die Daten gleich da blieben.

Da ist es dann wohl kaum verwunderlich, dass es performanter ist, wenn das meiste gleich da abgearbeitet wird.

Der erste Punkt ist natürlich auch von großem Interesse, wer sich schon einmal mit IPsec herumgeplagt hat, der weiß, wie komplex ein VPN werden kann.

Einschränkungen

Allerdings hat das im Fall von WireGuard auch ein paar Nebenbedingungen:

Das vereinfacht natürlich die Konfiguration deutlich, Damit wird natürlich die Konfiguration deutlich einfacher, hat aber eben den Nachteil, dass man nicht eben etwas anderes verwenden kann. Der Wechsel auf eine andere Verschlüsselung kann nicht einfach so erfolgen, sollte eine Verschlüsselung unsicher werden.

Curve25519 hat auch den Vorteil, dass hier nur ein 32-Byte Schlüssel verwendet werden muss oder anders ausgedrückt: Die Keylänge beträgt nur 256 Bit, das ist bei RSA mittlerweile drastisch länger. Die 32-Byte werden als Base64-kodierte Zeichenkette verwendet, das erleichtert den manuellen Schlüsseltausch deutlich.

Einrichtung

Die Webseite listet auch auf, wie man das Ganze dann aufsetzt, das ist recht einfach, an manchen Stellen aber etwas unpräzise.

Bei Debian ist es leicht zu installieren:

# apt install wireguard

Das installiert auch gleich wireguard-tools, das passende Kernelmodul ist beim Linux ja schon, wie eingangs erwähnt, dabei.

Dabei ist der erste Schritt ganz einfach, man benötigt die asymmetrischen Keys, also den public und private Key für das VPN. Das geht recht einfach:

# umask 077
# wg genkey > /etc/wireguard/privatekey

Das umask dient hier nur dazu sicher zu stellen, dass die Datei auch nur für den User root lesbar ist, sie enthält schließlich den private Key.

Wie kommt man nun an den public Key? Genau, der ist da schon mit enthalten:

# wg pubkey < /etc/wireguard/privatekey > /etc/wireguard/publickey

Damit haben wir die Keys. Das muss auf allen beteiligten Systemen erfolgen, jedes braucht sein eigenes Key-Paar.

Jetzt braucht man noch das Interface:

# ip link add dev wg type wireguard

und gibt diesem Interface eine IP-Adresse für das VPN, das kann ein Netz sein oder auch nur eine IP-Adresse, zum Beispiel:

# ip address add dev wg 172.22.0.1/32

Das muss analog auf allen anderen Systemen ausgeführt werden, nur die IP-Adresse muss natürlich angepasst werden, darf aber im gleichen Subnetz liegen! In der Regel reicht ein einzelne IP-Adresse, das Routing erfolgt per Interface, da wird kein direkter Router mit IP-Adresse benötigt.

Jetzt wird das eigentliche VPN aufgebaut, ich habe das mit zwei Debian-Systemen deb-wg1 und deb-wg2 aufgebaut:

deb-wg1:~# wg set wg listen-port 1234 private-key /etc/wireguard/privatekey \
 peer nJLvMQlmgFiOnp6Q9FC2BXQlUfjGwYZFcBwnd7RnFSE= allowed-ips 172.21.0.0/24 \
 endpoint 192.168.200.11:1234

und

deb-wg2:~# wg set wg listen-port 1234 private-key /etc/wireguard/privatekey \
 peer qpI8fXSUtuo/npoASz/iy0tlViZPkwJSEja5/mLeOxs= allowed-ips 172.21.0.0/24 \
 endpoint 192.168.10.11:1234

Dabei ist listen-port wohl selbsterklärend: Hier wird auf VPN-Pakete gewartet, diese werden von der Gegenstelle per UDP an die Adresse gesendet, die bei endpoint angegeben ist. Der private-key ist derjenige, welcher oben mit genkey erstellt wurde und hinter peer steht der public Key der jeweiligen Gegenseite.

Die allowed-ips geben an, welche Adressen auf das VPN zugreifen dürfen und mit endpoint wird die jeweilige Gegenstelle angegeben, also die IP-Adresse sowie der Port, der dort bei listen-port angegeben wurde. Idealerweise sind die identisch um Verwirrungen zu vermeiden, sie müssen es aber nicht sein.

Jetzt fehlt nicht mehr viel. Noch sind die Interfaces inaktiv, also setzen wir sie einmal auf den Status up:

# ip link set up dev wg 

Das muss natürlich bei allen beiteiligten Partnern (peers) erfolgen.

Jetzt könnten sie noch miteinander reden, aber woher weiß das System, was man in den Tunnel schieben soll? Es fehlen noch die passenden Routen:

deb-wg1:~# ip route add 172.22.0.1/32 dev wg
deb-wg2:~# ip route add 172.22.0.2/32 dev wg

Und schon geht ein ping:

root@deb-wg1:~# ping -c 4 172.22.0.2
PING 172.22.0.2 (172.22.0.2) 56(84) bytes of data.
64 bytes from 172.22.0.2: icmp_seq=1 ttl=64 time=1.73 ms
64 bytes from 172.22.0.2: icmp_seq=2 ttl=64 time=2.15 ms
64 bytes from 172.22.0.2: icmp_seq=3 ttl=64 time=3.03 ms
64 bytes from 172.22.0.2: icmp_seq=4 ttl=64 time=2.44 ms

--- 172.22.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 1.734/2.336/3.028/0.470 ms

Und ein tcpdump auf dem physikalischen Interface zeigt:

root@deb-wg2:~# tcpdump -n -i ens4 port 1234
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens4, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:19:19.129157 IP 192.168.10.11.1234 > 192.168.200.11.1234: UDP, length 128
18:19:19.129501 IP 192.168.200.11.1234 > 192.168.10.11.1234: UDP, length 128
18:19:20.130116 IP 192.168.10.11.1234 > 192.168.200.11.1234: UDP, length 128
18:19:20.130283 IP 192.168.200.11.1234 > 192.168.10.11.1234: UDP, length 128
18:19:21.132501 IP 192.168.10.11.1234 > 192.168.200.11.1234: UDP, length 128
18:19:21.132898 IP 192.168.200.11.1234 > 192.168.10.11.1234: UDP, length 128
18:19:22.134103 IP 192.168.10.11.1234 > 192.168.200.11.1234: UDP, length 128
18:19:22.134339 IP 192.168.200.11.1234 > 192.168.10.11.1234: UDP, length 128

Es sind wie vermutet UDP-Pakete...

Jetzt drängt sich die Frage auf:

Muss man diese Aufrufe alle jedesmal erneut durchführen?

Vereinfachter Aufruf

Es geht natürlich viel einfacher, dazu gibt es das Shellskript wg-quick in Verbindung mit einer Konfigurationsdatei.

Um an eine Konfigurationsdatei zu gelangen, bietet das wg-Kommando eine gute Hilfe an:

root@deb-wg1:~# wg showconf wg
[Interface]
ListenPort = 1234
PrivateKey = CDdQ1jbJDV/W/LSzEc6foARoK2ghTqHgazkhNir7oUw=

[Peer]
PublicKey = nJLvMQlmgFiOnp6Q9FC2BXQlUfjGwYZFcBwnd7RnFSE=
AllowedIPs = 172.22.0.2/32
Endpoint = 192.168.200.11:1234

Da steht nun der private Key im Klartext (ok: base64-kodiert). Aber das ist gar nicht so wichtig, idealer Weise wird das in eine Datei geschreiben:

root@deb-wg1:~# wg showconf wg > /etc/wireguard/wg.conf

Nun kann man damit (chmod 600 /etc/wireguard/wg.conf nicht vergessen, sollte der umask nicht mehr seinen obigen Wert haben) das VPN ganz einfach starten:

root@deb-wg1:~# wg-quick up /etc/wireguard/wg.conf
[#] ip link add wg type wireguard
[#] wg setconf wg /dev/fd/63
[#] ip -4 address add 172.22.0.1 dev wg
[#] ip link set mtu 1420 up dev wg
[#] ip -4 route add 172.22.0.2/32 dev wg

Voila, das Shellskript wg-quick erledigt alles für uns und das VPN ist schon weider da!

In der Sektion [Interface] kann man per Address = auch noch angeben, auf welcher Adresse auf eingehende VPn-Pakete gewartet werden soll, ansonsten lauscht es auf allen Interfaces.

Das Interface ist vom Typ Point-to-Point:

root@deb-wg1:~# ip link show dev wg
9: wg: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN
mode DEFAULT group default qlen 1000
   link/none

Daher sieht der Routing-Eintrag auch so seltsam aus: Es wird das Interface als Ziel genommen, keine Router-IP-Adresse!

Beendet werden kann das VPN auch recht schnell:

root@deb-wg1:~# wg-quick down /etc/wireguard/wg.conf
[#] ip link delete dev wg

Also alles ganz einfach!

Besonderheiten

Wer bis hier aufgepasst hat, wird sich gleich eine Frage stellen: Wie gebe ich denn der Gegenstelle bekannt, welche Netze bei mir existieren?

Das ist nun der etwas irritierende Part: Der Eintrag allowed-ips gibt an, was denn alles in den Tunnel geroutet wird. Das bestimmt also jede Seite für sich! Es ist also nicht so, dass vorab definiert wird, welche Netze auf welcher Seite liegen. Es kann alles hineingeroutet werden, was in allowed-ips steht und das Feld pflegt jeder Teilnehmer selber.

Natürlich bentötigt die Gegenseite auch noch eine Rückroute auf den VPN-Server und es darf auch nicht vergessen werden, das Routing zu aktivieren:

# echo 1 > /proc/sys/net/ipv4/ip_forward

Damit klappt es dann auch mit den Nachbarn jenseits des VPN-Servers...

Sollen nicht alle Netze erlaubt werden, werden noch anderweitig Filtermöglichkeiten wie iptables/nftables benötigt oder man verlässt sich auf fehlende Routen.

Aber wer weiß, vielleicht ist per se schon NAT aktiv?

Fazit

Es ist wohl leicht zu erkennen, das VPN ist leicht aufgesetzt, es ist stabil und sehr schnell.

Was allerdings passiert, wenn es eine Sicherheitslücke in Curve25519 oder ChaCha20 gibt, ist eine andere Frage. Dafür ist ein Einrichtung deutlich einfacher.

Eine interessante Option gibt es auch noch für Firewalls und vor allem VPN-Servern hinter NAT-Routern:

PersistentKeepalive = seconds

Damit wird alle paar seconds ein Paket gesendet, um den Verbindungsstatus aufrecht zu erhalten. Manche Firewalls schließen sonst Ports, was jetzt bei UDP nicht so dramatisch ist: Die öffnen sich dann wieder, sollte ein neues Paket eintreffen. Aber hier spielt manchmal die Richtung eine Rolle. NAT-Firewalls könnten auf die Idee kommen, die Pakete dann zu verwerfen und nicht mehr an das eigentliche Ziel weiterzuleiten. Diese halten die Zuordnung und damit den offenen Port in der Regel nicht sehr lange offen, wenn keine Netzwerkpakete eintreffen.

Insgesamt ist es aber eine schöne, einfache und schnelle VPN-Lösung. Sicherlich wird diese vermutlich nicht im großen Stil eingesetzt, Verbindungen zu anderen VPN-Geräten, die kein WireGuard nutzen, sind auch nicht möglich.

Wer aber einmal ein VPN benötigt, das schnell eingerichtet, performant und zuverlässig ist, der wird mit WireGuard seine Freude haben!


Dirk Geschke, dirk@lug-erding.de