Wireguard Clients durch NordVPN tunneln

Idee

NordVPN bietet für die Verbindung diverse Anwendungen / Apps an. Möchte (oder kann) man diese nicht nutzen, weil das Endgerät dies zum Beispiel nicht unterstützt, gibt es zusätzlich noch Alternativen wie OpenVPN, IPSec oder Wireguard. Für die Menge der verfügbaren Server stehen dann entsprechende Konfigurationen bereit.
Das hier beschriebene Setup bietet folgende Vorteile:

  1. Webinterface zur Verwaltung der Clients
  2. jedes Endgerät hat einen eigenen Schlüssel, der unabhängig von den anderen Geräten entzogen werden kann
  3. einfache Einbindung von Geräten über einen Wireguard-Client
  4. ermöglicht Split-Tunnelling
  5. regelmäßiger Wechsel des aktiven Nordvpn-Servers (Container kann sich neu verbinden)
Zusammenfassung der Schritte
  1. NordVPN Token generieren
  2. private Key auslesen (mithilfe des Tokens)
  3. Docker-Compose Umgebung konfigurieren
  4. Skripte anlegen
  5. starten

Einrichtung

Docker / Compose installieren
# Ubuntu
apt-get install docker.io docker-compose

# Archlinux
pacman -Syu docker docker-compose
Private Key auslesen
temporärer Token

Der hier generierte Token sollte im Anschluss wieder gelöscht werden!

private Key

Der hier ausgelesene “private key” ist accountweit gültig und darf Dritten nicht zugänglich gemacht werden!

Bevor der “Private Key” für die eigentliche Verbindung ausgelesen werden kann, benötigen wir ein temporärer Token zum einmaligen verbinden:

https://support.nordvpn.com/Connectivity/Linux/1905092252/How-to-log-in-to-NordVPN-on-Linux-with-a-token.htm

Nun erstellen wir einen Container der mittels Token einloggt, eine NordLynx Verbindung herstellt und den dazu verwendeten Private Key zurückgibt:

TOKEN=<NORDVPN TOKEN>
docker run --rm --cap-add=NET_ADMIN --name nordvpn_key_fetch \
  -e TOKEN=${TOKEN} ghcr.io/bubuntux/nordvpn:get_private_key

<einige Ausgaben später...>

############################################################
IP: 10.5.0.2/32
Private Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
(^O^)############################################################
docker-compose.yml Konfiguration
Client Netzwerkeinstellung

Bitte die Kommentare oberhalb WG_ALLOWED_IPS beachten und die unerwünschte Konfiguration entfernen (auskommentieren)!

Mit ein wenig Phantasie lässt sich erkennen, dass mehrere NordVPN Container gestartet und mit Wireguard Containern verbunden werden können.

Hierbei ist allerdings nur eine Verbindung pro NordVPN Container möglich.
Möchte man mehr Verbindungen (z.B. pro Land / Kontinent) nutzen, benötigt man mehrere NordVPN-Container!

Die Variablen werden in der docker-compose.yml erklärt:

version: "3.8"
services:
  nvpn_01:
    container_name: "wgeasy_nvpn_01"
    image: ghcr.io/bubuntux/nordlynx
    restart: unless-stopped
    environment:
      # privater Schlüssel zum Verbindung zu NordVPN Lynx (vorher ausgelesen)
      PRIVATE_KEY: "<SUPER_GEHEIMES_NORDVPN_SECRET>"
      # Subnetz des Wireguard Servers, muss mit WG_DEFAULT_ADDRESS zusammenpassen!
      NET_LOCAL: "192.168.33.0/24"
      # IP-Adresse des wg-Containers (siehe wg01.networks.default.ip4_address)
      WG_CONTAINER_IP: "172.18.18.11"
      # Skript mit Anpassungen, damit die Wireguard Clients erreicht werden
      PRE_UP: "/hooks/pre_connect.sh"
      # optional um bestimmte Server zu wählen (Beispiel hier ist DE)
      #QUERY: 'filters\[country_id\]=81'
    volumes:
    - /srv/docker/wg_easy/nvpn_01:/hooks:ro
    cap_add:
    - NET_ADMIN
    - NET_RAW
    networks:
      default:
        ipv4_address: 172.18.18.2

  wg_01:
    container_name: "wgeasy_wg_01"
    image: weejewel/wg-easy
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
      # FALLS ein Webserver davor geschaltet werden soll, kann dieser Port auch deaktiviert werden und die Container-IP als Ziel für z.B. nginx / Traefik / Apache genutzt werden
      # Alternativ bindet man den HTTP Port nur lokal und referenziert diese im Webserver
      #- "127.0.0.1:51821:51821/tcp"
    environment:
      # IP-Adresse des NordVPN-Containers, wird als Router für die Wireguard-Clients genutzt (siehe nvpn_01.networks.default.ipv4_address)
      CUSTOM_GW: "172.18.18.2"
      # Hostname / IP-Adresse des Servers, wird in der Client Config von Wireguard hinterlegt
      WG_HOST: "my.host.com"
      # Admin Password für das Webinterface
      # Empfehlung: keine Sonderzeichen, könnte (!) zu Fehlern führen
      PASSWORD: "<MEIN_SUPER_SICHERES_KENNWORT>"
      # Subnetz für Wireguard-Clients, MUSS mit NET_LOCAL von NordVPN zusammenpassen!
      # x wird von wgeasy ersetzt und muss so bleiben
      WG_DEFAULT_ADDRESS: "192.168.33.x"
      # DNS Server, wird von den Wireguard-Clients im Tunnel genutzt
      WG_DEFAULT_DNS: "1.1.1.1,9.9.9.9"
      # Standard MTU ist 1500, da Pakete durch einen Tunnel müssen, benötigen wir eine kleinere MTU
      WG_MTU: "1420"
      # alle 30 Sekunden wird die Verbindung überprüft
      WG_PERSISTENT_KEEPALIVE: "30"
      WG_PRE_UP: "/hooks/pre_up.sh"
      # optional, nur wenn sich Clients untereinander erreichen können sollen
      #WG_POST_UP: "/hooks/post_up.sh"

      # hier kann entschieden werden, welche Netzwerke / IPs durch den Tunnel geroutet werden sollen
      # WICHTIG: dies wird nur beim Generieren der Client-Config berücksichtigt, spätere Anpassungen haben KEINEN VERÄNDERNDEN Effekt auf bereits generierte Tunnel-Configs!
      # nachträgliche Änderungen müssen im Client selbständig nachgepflegt werden, oder der Tunnel neu angelegt und die Config heruntergeladen werden!

      # 2 Beispiele:

      # ALLE IP-Adressen werden über den Tunnel geroutet, hierbei können auch lokale Adressen möglicherweise nicht mehr erreicht werden (z.B. Drucker, Handys, ...)
      # sollte es zu Problemen kommen, das Netz 0.0.0.0/0 im Client hinterlegen (aus Gründen der Kompatibilität mit manchen Betriebssystemen wurde dieses hier in 2 x /1 geteilt)
      WG_ALLOWED_IPS: "0.0.0.0/1,128.0.0.0/1"

      # Sollen private IP-Adressen ausgenommen werden (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16):
      WG_ALLOWED_IPS: "0.0.0.0/5,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/3,160.0.0.0/5,168.0.0.0/6,172.0.0.0/12,172.32.0.0/11,172.64.0.0/10,172.128.0.0/9,173.0.0.0/8,174.0.0.0/7,176.0.0.0/4,192.0.0.0/9,192.128.0.0/11,192.160.0.0/13,192.169.0.0/16,192.170.0.0/15,192.172.0.0/14,192.176.0.0/12,192.192.0.0/10,193.0.0.0/8,194.0.0.0/7,196.0.0.0/6,200.0.0.0/5,208.0.0.0/4,224.0.0.0/3"
    volumes:
      # wird durch das Webinterface beschrieben, deswegen rw!
      - /srv/docker/wg_easy/wg_01_config:/etc/wireguard:rw
      - /srv/docker/wg_easy/wg_01_hooks:/hooks:ro
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    networks:
      default:
        ipv4_address: 172.18.18.11
networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.18.18.0/24
    driver_opts:
      com.docker.network.bridge.name: br_wgeasy
NordVPN Hook Skript

Der Befehl legt ein Skript für den NordVPN Container an, welches vor der Verbindungsherstellung durchgeführt wird.
Hierbei wird die Route zu den WG-Clients ausgetausch, wodurch diese über den WG-Container erreicht werden.

#File: /srv/docker/wg_easy/nvpn_01/pre_connect.sh

#!/bin/bash
# löschen der existierenden Route von NordPVN
ip route del $NET_LOCAL
# Route um das Wireguard-Netz über wgeasy Container zu routen
ip route add $NET_LOCAL via $WG_CONTAINER_IP
WG-Easy Hook

Wird vorm Start des Wireguard-Servers ausgeführt um die Umgebung abzusichern und das Routing via NordVPN vorzubereiten. Ist NordVPN nicht aktiv, besteht keine Möglichkeit in das Internet zu routen. Dies ist aber ein gewünschter Nebeneffekt.

Note

Das wg-easy Webinterface ist nur über den Docker-Host erreichbar, wenn dies nicht gewünscht ist, müssen Anpassungen (siehe optionale Freigaben) erfolgen!

#File: /srv/docker/wg_easy/wg_01_hooks/pre_up.sh

#/bin/bash
set -e

# Netzwerk und Standard-Gateway auslesen
NETWORK=${WG_DEFAULT_ADDRESS/.x/.0/24}
GW=$(/sbin/ip route | awk '/default/ { print $3 }')

# Webinterface vom Server erlaubt
iptables -I INPUT -s ${GW} -p tcp --dport 51821 -j ACCEPT

# optionale Freigaben

# Webinterface durch Wireguard Clients
#iptables -I INPUT -s ${NETWORK} -p tcp --dport 51821 -j ACCEPT

# Webinterface von überall erreichbar
#iptables -I INPUT -p tcp --dport 51821 -j ACCEPT


# DNS im Container erlauben
iptables -I INPUT -i lo -s 127.0.0.1 -j ACCEPT

# existierende Verbindungen werden erlaubt
iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Standard-Regel ist das Verwerfen von Paketen, Accept wird von wg-easy  später hinterlegt!
iptables -P INPUT DROP
iptables -P FORWARD DROP

# wg-easy legt eine masquerade (Source-Nat) Regel an, diese hebeln wir aus, damit im Nordvpn Container die Tunnel-IP-Adresse des Clients ersichtlich ist und nicht zwischen den Containern NAT betrieben wird
iptables -t nat -I POSTROUTING -j ACCEPT

# Pakete von WG-Clients werden durch eine gesonderte Routing-Tabelle behandelt, damit diese in den NordVPN Container geroutet werden
# Dies ist notwendig, damit der wg-easy Container selbst ein Gateway hat und das Webinterface erreichbar ist

# Routen für IP-Pakete von WG-Clients werden in Tabelle 1000 gesucht
ip rule add from ${NETWORK} lookup 1000

# Standard-GW für Tunnel-Clients über den NordVPN Container
ip route add default via ${CUSTOM_GW} table 1000

# alle privaten Netze werden geblockt, denn diese sollen nicht über NordVPN geroutet werden!
# weitere IP-Adressen (/32) oder Netze können hier hinterlegt
# Außerdem wird somit zusätzlich sichergestellt, dass WG-Clients sich untereinander nicht erreichen können.
# Sollen sich WG-Clients untereinander erreichen, kann im post_up.sh dies konfiguriert werden
ip route add blackhole 10.0.0.0/8 table 1000
ip route add blackhole 172.16.0.0/12 table 1000
ip route add blackhole 192.168.0.0/16 table 1000

# eventuell andere lokale IP-Adressen auf dem WG Host wie Pihole, oder als exklude vom VPN Tunnel
# ip route add x.x.x.x via ${GW} table 1000

Das post_up.sh Skript ist optional!

#File: /srv/docker/wg_easy/wg_01_hooks/post_up.sh

#/bin/bash
set -e

# optionale Route (Wireguard Clients können sich untereinander erreichen)
# Diese Regel muss in der post_up.sh stehen, weil pre_up.sh vor Erstellung des Wireguard Interfaces ausgeführt wird.
# Das hat zur Folge dass die Regel nicht angelegt wird ("no such device / interface")
ip route add ${NETWORK} dev wg0 table 1000
Nachbereitung der Hook Scripts
cd /srv/docker/wg_easy/

# Berechtigung der Skripts anpassen, damit diese ausführbar sind
find . -name "*.sh" -exec chmod +x {} \;
Container start
cd /srv/docker/wg_easy/
docker-compose up -d

Im Anschluss sollten die beiden Container gestartet werden.
Ob das Webinterface direkt erreichbar ist, hängt stark von der gewählten Konfiguration ab …

Warning

Ich rate dringend zur Absicherung des Webinterfaces! Beispiele:

  • Reverse-Proxy (z.B. nginx) und das Webinterface per SSL absichern
  • Interface nur lokal vom Server verfügbar machen und mittels SSH Tunnel zuzugreifen!
    Anleitung zum Thema SSH-Tunneling

Ignoriert man alle Warnungen und macht das Webinterface öffentlich erreichbar, wäre es hier zu erreichen: http://my.host.com:51821/
(my.host.com muss durch die IP-Adresse des Hosts ersetzt werden!)

Tipps & Tricks

NordVPN Server finden

Die hier hinterlegte Liste kann sich zwischenzeitlich ändern, daher ist eine eigene Abfrage ratsam…

docker run --name nvpn_temp -ti --rm --entrypoint /bin/bash ghcr.io/bubuntux/nordlynx:latest


curl "https://api.nordvpn.com/v1/servers/countries" | jq -r '.[] | "\( .name ) \( .id )"'
Albania 2
Argentina 10
Australia 13
Austria 14
Belgium 21
Bosnia and Herzegovina 27
Brazil 30
Bulgaria 33
Canada 38
Chile 43
Colombia 47
Costa Rica 52
Croatia 54
Cyprus 56
Czech Republic 57
Denmark 58
Estonia 68
Finland 73
France 74
Georgia 80
Germany 81
Greece 84
Hong Kong 97
Hungary 98
Iceland 99
India 100
Indonesia 101
Ireland 104
Israel 105
Italy 106
Japan 108
Latvia 119
Lithuania 125
Luxembourg 126
Malaysia 131
Mexico 140
Moldova 142
Netherlands 153
New Zealand 156
North Macedonia 128
Norway 163
Poland 174
Portugal 175
Romania 179
Serbia 192
Singapore 195
Slovakia 196
Slovenia 197
South Africa 200
South Korea 114
Spain 202
Sweden 208
Switzerland 209
Taiwan 211
Thailand 214
Turkey 220
Ukraine 225
United Arab Emirates 226
United Kingdom 227
United States 228
Vietnam 234

Erklärungen

Split-Tunneling

Es kann entschieden werden, welcher Traffic durch den VPN-Tunnel geroutet wird und welcher nicht. Es wird also nicht per se der komplette Netzwerkverkehr durch den Tunnel geroutet.

Referenzen

Nutzung der NordVPN API

https://sleeplessbeastie.eu/2019/02/18/how-to-use-public-nordvpn-api/

Docker Container (bubuntux)