VPN con Wireguard: semplice e veloce

vpn Fonte: Photo by Stefan Coders on Pexels.com

Perché wireguard?

  1. Perché wireguard?
  2. Crittografia di Wireguard
  3. Un po' di teoria
  4. Installazione
    1. Generazione chiavi
  5. Attivazione del nodo
    1. PC/Laptop
    2. Mobile
  6. Debug
  7. Topologie
  8. Point-To-Site
    1. Primo scenario
      1. Configurazione wireguard Endpoint A
      2. Configurazione wireguard Host B
      3. Configurazione routing/filtering Host B
      4. Test
      5. Tips
    2. Secondo scenario
      1. Configurazione wireguard Endpoint A
      2. Configurazione wireguard Host B
      3. Configurazione routing/filtering Host B
      4. Test
      5. Tips
    3. Terzo scenario
      1. Configurazione routing/filtering Host B
      2. Configurazione gateway Sito B
      3. Test
      4. Tips
    4. Differenze fra i 3 approcci

Crittografia di Wireguard

Le primitive crittografiche di Wireguard si basano su:

Un po' di teoria

Wireguard implementa una vpn L3 solo tunneling, incapsulando pacchetti IP all'interno di pacchetti UDP per il trasporto. Ciò implica che funzioni come dhcp, essendo L2, non possono trovare spazio all'interno di wireguard, ergo l'indirizzamento (ip, subnet, dns) deve essere fatto staticamente.

Avendo un'anima P2P, ogni nodo (peer) wireguard è sia client che server. Ciò permette di realizzare topologie anche piuttosto complesse con uno sforzo relativamente ridotto.

Un peer wireguard è composto da due tipi di sezioni:

I nodi si autenticano a vicenda scambiandosi e convalidando le chiavi pubbliche. Ogni chiave pubblica è associata all'IP di un peer.

Quando un peer deve inviare dei pacchetti, AllowedIPs si comporta come una tabella di instradamento. Il peer esamina l'IP di destinazione di quel pacchetto e lo cerca negli AllowedIPs dei suoi peer per capire a chi inviarlo. Se lo trova, ruota il pacchetto nel tunnel, altrimenti no.

Quando si ricevono pacchetti, AllowedIPs si comporta come una lista di controllo degli accessi. Il peer esamina l'IP di origine e se rientra nell'AllowedIPs di uno dei suoi peer, accetterà il pacchetto altrimenti lo scarterà.

Per esperienza personale, il 99% degli errori di configurazione su wireguard viene fatto proprio sul campo AllowedIPs perché non se ne comprende bene il funzionamento.

Uno degli errori più comuni è quello di definire dei peer in cui gli ip contenuti nei campi AllowedIPs si sovrappongono. In base al funzionamento di wireguard infatti, non posso avere chiavi pubbliche diverse per uno stesso ip perché il peer, pur essendo in grado di ricevere pacchetti, non riuscirebbe a inviarli, non sapendo come ruotarli dovendo scegliere fra più peer.

Installazione

Come già detto, wireguard è presente di base nel kernel linux. Bisogna installare solo gli strumenti di interfacciamento per la configurazione su ogni host.

Per sistemi debian-based:

apt install wireguard wireguard-tools

Generazione chiavi

Per semplicità, suppongo di generare tutte le chiavi che mi occorrono, del server (host B) e del client (Endpoint A) in questo caso. Oltre alle chiavi, userò anche una pre-shared key per il client. Non è obbligatorio ma mi costa zero e aumenta la resistenza post-quantistica.

#server
wg genkey > server_wg.key
wg pubkey < server_wg.key > server_wg.pub

#client
wg genkey > endpoint-a_wg.key
wg pubkey < endpoint-a_wg.key > endpoint-a_wg.pub

# Creazione psk
wg genpsk > endpoint-a.psk

Attivazione del nodo

Una volta che la configurazione del nodo è pronta, va attivato il servizio se ci troviamo su un pc. Altrimenti bisognerà importare la configurazione sul device.

PC/Laptop

Sia esso un client o un server, una volta configurato il peer, salva il file file sotto /etc/wireguard/ col nome dell'interfaccia virtuale che hai scelto, nel mio caso, wg0.conf

Attivazione/Disattivazione vpn on-demand:

wg-quick up wg0 #attiva la vpn
wg-quick down wg0 #disattiva la vpn

Attivazione/Disattivazione vpn come servizio:

systemctl enable wg-quick@wg0
systemctl start/stop wg-quick@wg0.service

Mobile

Se il provisioning delle configurazioni si fa attraverso un pc, per portare facilmente la configurazione su un device mobile, si può generare un qrcode del file (supponiamo sia mobile.conf) e importarlo.

# Installazione qrencode (debian-based)
apt install qrencode

#Crea il qr-code e dallo in pasto a al client
qrencode -t ansiutf8 < mobile.conf

Debug

Wireguard è un modulo del kernel e quindi la sua attività viene raccolta dai log del kernel (/var/log/kern.log o /var/log/messages).

Bisogna verificare che sul kernel si possa abilitare il dynamic_debug_control, controllando se è presente:

ls /sys/kernel/debug/dynamic_debug

Se lo è, si carica il modulo, si setta il dynamic debug e si potrà esaminare log del kernel ad es. con journalctl -fk

modprobe wireguard
echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control

Potresti dover installare resolvconf sul server.

Quando wireguard fa salire l'interfaccia, la registra su systemd-resolve attraverso resolvconf e se non è installato potrebbe verificarsi un malfunzionamento.

Topologie

Le principali topologie che possono essere realizzate con wireguard sono:

Il canale sicuro fra i nodi wireguard viene creato attraverso le primitive crittografiche di cui sopra, ogni peer viene identificato da una chiave asimmetrica.

Non è necessario approfondire la parte di crittografia per capire come configurare wireguard. Ci fidiamo che faccia un buon lavoro.

Ciò su cui invece vale la pena soffermarsi, è la parte di instradamento, essenziale per l'implementazione delle topologie di cui sopra.

Io mi soffermerò sulla topologia più comune, point-to-site, che permette di unire un endpoint che esegue wireguard, con un altro endpoint situato all'interno di una LAN in cui c'è un host wireguard.

easy-point-to-site

Tanto per fissare le idee indicherò:

Osservazioni banali: – In una topologia point-to-point, host wireguard ed endpoint coincidono su entrambi gli estremi della tratta. – In una topologia point-to-site, host wireguard ed endpoint coincidono solo sul “point” (l'altro endpoint è dietro ad un host wireguard). – In una topologia site-to-site, gli endpoint sono sempre dietro gli host wireguard.

Point-To-Site

Avrò un Sito A che comprende il solo endpoint dove non ho la possibilità di gestire routing e filtering e un Sito B dove, al contrario, potrò impostare le mie policy di instradamento e filtraggio e dove sarà presente l'host wireguard (indicato come Host B, di solito) che fungerà da server.

La configurazione di un peer si divide in 3 tronconi:

Gli scenari che andremo a considerare riguarderanno la direzione delle comunicazioni e le strategie adottate per realizzarle:

Prendendo uno scenario reale come riferimento, suppongo che la parte “point” sia dietro un NAT che nasconde l'ip privato dell'endpoint e permette solo connessioni già stabilite. Nella parte “site”, la lan è protetta da un firewall-router-nat che espone il solo ip pubblico.

Nella configurazione di filtering/routing sul firewall (ufw nel mio caso) di Host B, ci sarà da tenere conto delle politiche di inoltro. Per semplificarci la vita si potrebbe abilitare qualunque inoltro di default ma sarebbe un po' pericoloso quindi, a meno di casi particolari, anche gli inoltri saranno filtrati.

Primo scenario

Nel primo scenario, considererò il caso punto-sito, in cui un endpoint (con wireguard) si collega, nella LAN attraverso un host wireguard, all'endpoint che eroga un servizio.

point-to-site Point-To-Site

Obiettivo: L'endpoint del sito B (192.168.1.3) ha un servizio web https sulla porta 443, che non è raggiungibile dall'esterno. Possiamo usare wireguard per permettere all'Endpoint A di raggiungere il servizio sugli endpoint del sito B, per mezzo dell'host wireguard B. Affinché l'host B possa inoltrare i pacchetti dall'Endpoint A all'Endpoint B si ricorre al SNAT (MASQUERADING).

point-to-site-masquerading Point-To-Site: Masquerading

Configurazione wireguard Endpoint A

[Interface]
Address = 10.0.0.2/32
SaveConfig = true
PrivateKey = <Contenuto di endpoint-a_wg.key>

[Peer]
PublicKey = <Contenuto di server_wg.pub>
PresharedKey = <Contenuto di endpoint-a.psk>
Endpoint = 200.76.182.23:51820
AllowedIPs = 192.168.1.0/24

Dettagli:

Configurazione wireguard Host B

[Interface]
Address = 10.0.0.1/24
SaveConfig = true
ListenPort = 51820
PrivateKey = <Contenuto di server_wg.key>

# endpoint A
[Peer]
PublicKey = <Contenuto di endpoint-a_wg.pub>
PresharedKey = <Contenuto di endpoint-a.psk>
AllowedIPs = 10.0.0.2/32

Dettagli:

Non poteva essere altrimenti. In una topologia point-to-site,

Inoltre, sempre in una topologia point-to-site, è sempre il point ad iniziare la connessione per cui:

Configurazione routing/filtering Host B

Il resto della configurazione non riguarda più wireguard direttamente ma riguarda l'insieme di policy di routing e filtering da fare sull'host B per permettere ai pacchetti dell'Endpoint A di essere inoltrati all'Endpoint B attraverso l'host B.

Affinchè i pacchetti possano viaggiare dall'Endpoint A all'Endpoint B:

Quindi:

# abilita il forward
sysctl -w net.ipv4.ip_forward=1
# inoltra tutto il traffico in arrivo da wg0 verso il solo Endpoint B
ufw route allow in on wg0 out on eth0 proto tcp to 192.168.1.3 port 443;
# masquerading
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

N.B. potrei ancora raffinare queste regole ricorrendo alla tabella Mangle per marcare i pacchetti ai quali applicare il masquerading.

Questi comandi possono far parte della configurazione. Nella sezione [Interface] si possono usare i costrutti PreUp, PreDown, PostUp e PostDown per rendere dinamici alcuni passi di configurazione prima/dopo l'apertura/chiusura del tunnel. E così, la configurazione del server diventa:

[Interface]
Address = 10.0.0.1/24
SaveConfig = true
ListenPort = 51820
PrivateKey = <Contenuto di server_wg.key>

# IP forwarding
PreUp = sysctl -w net.ipv4.ip_forward=1
PreUp = ufw route allow in on wg0 out on eth0 proto tcp to 192.168.1.3 port 443
PostDown = ufw route delete allow in on wg0 out on eth0 proto tcp to 192.168.1.3 port 443

# IP masquerading
PreUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# IP filtering
PreUp = ufw allow in on eth0 proto udp from 0.0.0.0/0 to 192.168.1.2 port 51820;
PostDown = ufw delete allow in on eth0 proto udp from 0.0.0.0/0 to 192.168.1.2 port 51820;

# endpoint A
[Peer]
PublicKey = <Contenuto di endpoint.a_wg.pub>
PresharedKey = <Contenuto di endpoint-a.psk>
AllowedIPs = 10.0.0.2/32

Test

Da Endpoint A (192.168.10.10) possiamo raggiungere il servizio come se fossimo nella lan del sito B, quindi:

curl https://192.168.1.3

risponderà al browsing.

Tips

La combinazione AllowedIPs / routing è la chiave per gestire qualunque instradamento. Nell'esempio visto finora abbiamo fatto in modo che:

In questo modello, qualunque altro endpoint abilitato alla VPN wireguard potrebbe accedere al servizio dell'Endpoint B. Vale la pena dare un'occhiata ad un altro paio di combinazioni che si trovano agli opposti del nostro esempio. In un caso, ruoteremo tutto il traffico verso la vpn, in un altro consentiremo solo a determinati peer, non a tutti, di fruire di determinati servizi.

1. Inoltro di tutto il traffico nella vpn In questo modo, l'Endpoint A lavora come se facesse parte della lan del sito B. È una modalità totalmente anonimizzante. Da usare solo con dispositivi trusted perché un eventuale traffico malevolo verrebbe ruotato nel tunnel e risulterebbe proveniente dal sito B

Su Endpoint A:

...
AllowedIPs = 0.0.0.0/0
...

Su host B:

...
ufw route allow in on wg0 out on eth0;`
...

2. Indirizzamento puntuale su servizi specifici È il caso in cui alcuni peer debbano raggiungere solo determinati servizi e non altri. Supponiamo di avere altri due endpoint, Endpoint A1 e Endpoint A2.

Su Endpoint A1 e A2 (in realtà AllowedIPs potrebbe essere puntuale):

...
AllowedIPs = 192.168.1.0/24
...
...
PreUp = ufw route allow in on wg0 out on eth0 proto tcp from 10.0.0.3 to 192.168.1.4 port 3306
PreUp = ufw route allow in on wg0 out on eth0 proto tcp from 10.0.0.4 to 192.168.1.5 port 22
PostDown = ufw route delete allow in on wg0 out on eth0 proto tcp from 10.0.0.3 to 192.168.1.4 port 3306
PostDown = ufw route delete allow in on wg0 out on eth0 proto tcp from 10.0.0.4 to 192.168.1.5 port 22
...

Secondo scenario

Nel secondo scenario, ribalterò il punto di vista e prenderò in esame il caso in cui sia un host del sito B a contattare un endpoint esterno con wireguard (sito-punto). Non il classico scenario road-warrior in cui un endpoint deve raggiungere la rete aziendale o domestica, ma il suo opposto.

site-to-point Site-To-Point

Obiettivo: L'host del sito A (192.168.10.10) ha un servizio web https sulla porta 1443, che non è raggiungibile dall'esterno ma vogliamo che lo sia dal sito B. Con wireguard, possiamo usare l'interfaccia virtuale per esporre il servizio su 10.0.0.2, verso gli endpoint del sito B, per mezzo dell'host wireguard B. Affinché l'host B possa inoltrare i pacchetti dall'Endpoint B all'Endpoint A si ricorre al DNAT (PORT FORWARDING).

point-to-site-port-forwarding Site-To-Point: Port-Forwarding

La configurazione di wireguard su host B rimane sostanzialmente identica rispetto a prima. A cambiare sarà soprattutto la parte di filtering e routing.

Configurazione wireguard Endpoint A

[Interface]
Address = 10.0.0.2/32
SaveConfig = true
PrivateKey = <Contenuto di endpoint-a_wg.key>

[Peer]
PublicKey = <Contenuto di server_wg.pub>
PresharedKey = <Contenuto di endpoint-a.psk>
Endpoint = 200.76.182.23:51820
AllowedIPs = 192.168.1.0/24
PersistentKeepalive = 25

Su Endpoint A c'è una piccola modifica.

Dettagli:

Configurazione wireguard Host B

[Interface]
Address = 10.0.0.1/24
SaveConfig = true
ListenPort = 51820
PrivateKey = <Contenuto di server_wg.key>

# endpoint A
[Peer]
PublicKey = <Contenuto di endpoint-a_wg.pub>
PresharedKey = <Contenuto di endpoint-a.psk>
AllowedIPs = 10.0.0.2/32

La configurazione di Host B rimane sostanzialmente invariata.

Configurazione routing/filtering Host B

Affinchè i pacchetti possano viaggiare dall'Endpoint B all'Endpoint A:

Quindi

...
# IP forwarding
# abilita il forward
PreUp = sysctl -w net.ipv4.ip_forward=1
# inoltra tutto il traffico in arrivo da eth0 verso l'Endpoint A
PreUp = ufw route allow in on eth0 out on wg0 proto tcp to 10.0.0.2 port 443
PostDown = ufw route delete allow in on eth0 out on wg0 proto tcp to 10.0.0.2 port 443

# IP port forwarding
# inoltro della porta 1443 dall'interfaccia lan verso la porta 443 dell'Endpoint A
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.0.0.2:1443
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.0.0.2:1443

# IP filtering
PreUp = ufw allow in on eth0 proto udp from 0.0.0.0/0 to 192.168.1.2 port 51820;
PostDown = ufw delete allow in on eth0 proto udp from 0.0.0.0/0 to 192.168.1.2 port 51820;
...

Test

Da Endpoint B (192.168.1.3) possiamo raggiungere il servizio sull'interfaccia virtuale dell'Endpoint A passando dalla porta dell'host B, quindi:

curl https://192.168.1.2

risponderà al browsing.

Tips

In questa rappresentazione, ogni host del sito B viene indirizzato solo verso la vpn wireguard. L'host B inoltra:

Se volessimo che solo determinati endpoint del sito B potessero inoltrare il loro traffico, si dovrebbero rendere le regole di inoltro più puntuali. Supponiamo che Endpoint A (10.0.0.2) esponga un servzio httpd sulla porta 10080 e vogliamo che solo gli host 192.168.1.9-192.168.1.14 del sito B lo possano raggiungere sulla canonica porta 80.

Port Forwarding di un solo endpoint

ufw route allow from 192.168.1.4 to 10.0.0.2 port 10080 proto tcp
iptables -t nat -A PREROUTING -s 192.168.1.4 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.2:10080

Port Forwarding di una subnet

ufw route allow from 192.168.1.8/29 to 10.0.0.2 port 10080 proto tcp
iptables -t nat -A PREROUTING -s 192.168.1.9-192.168.1.14 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.2:10080

Terzo scenario

Il terzo scenario può essere considerato un'unione dei primi due.

point-to-site-to-point Point-To-Site-To-Point

Obiettivo: L'host del sito A (192.168.10.10) espone un servizio di remote desktop (porta 3389), l'endpoint del sito B (192.168.1.3) espone un servizio web https sulla porta 443. Vogliamo che l'Endpoint A raggiunga il servizio web e che l'Endpoint B acceda al desktop remoto dell'Endpoint A.

point-to-site-gateway Point-To-Site: Gateway

Se viene configurato il routing sul gateway per indirizzare wireguard, il traffico potrà essere bidirezionale. Non ci sarà bisogno di manipolare i pacchetti perché:

  1. Da Endpoint A a Endpoint B, i pacchetti inoltrati da host B avranno come sorgente l'ip della rete wireguard di Endpoint A.
  2. Attraverso il gateway, Endpoint B potrà rispondere/contattare direttamente l'indirizzo sorgente wireguard di Endpoint A.

La configurazione di wireguard su Endpoint A e Endpoint B è assimilabile allo scenario 2 con l'impostazione del PersistentKeepAlive su Endpoint A

Configurazione routing/filtering Host B

Al solito va configurato l'inoltro su Host B da Endpoint A a B e viceversa

...
PreUp = ufw route allow in on wg0 out on eth0 to 192.168.1.3 port 443 proto tcp
PostDown = ufw route delete allow in on wg0 out on eth0 to 192.168.1.3 port 443 proto tcp
PreUp = ufw route allow in on eth0 out on wg0 from 192.168.1.3 port 3389 proto tcp
PostDown = ufw route delete allow in on eth0 out on wg0 from 192.168.1.3 port 3389 proto tcp
...

Configurazione gateway host del Sito B

A parte attivare il solito inoltro su host B, bisognerà avere la possibilità di configurare il routing anche sul nostro router (192.168.1.1), in maniera che Endpoint B, che presumbilmente ha proprio 192.168.1.1 come gateway di default, indirizzando Endpoint A, venga rediretto verso Host B. Ad es.

ip route add 10.0.0.2/32 via 192.168.1.2 dev eth0

In alternativa, se non si ha la possibilità/voglia di configurare il router, bisognerà configurare Endpoint B con la rotta statica indicata prima.

Test

Da Endpoint A (192.168.10.10) possiamo raggiungere il servizio come se fossimo nella lan del sito B, quindi:

curl https://192.168.1.3

Da Endpoint B (192.168.1.3) raggiungiamo il desktop remoto dell'Endpoint A attraverso la sua interfaccia virtuale (10.0.0.2)

rdesktop 10.0.0.2

Tips

Si potrebbe pensare di aggregare le potenziali rotte statiche considerando l'intera subnet wireguard per avere un'unica rotta statica su tutti gli host del sito B configurando sul nostro router (oppure su tutti gli host del sito B):

ip route add 10.0.0.0/24 via 192.168.1.2 dev eth0

In questo modo, ogni host del sito B può indirizzare vicendevolmente il suo traffico verso endpoint esterni purché le regole wireguard lo consentano (AllowedIPs)

Differenze fra i 3 approcci

Primo scenario: l'Endpoint A contatta l'Endpoint B sul suo indirizzo reale ma l'Endpoint B non ha idea di chi siano gli endpoint, lato “punto”, che lo contattano (per effetto del masquerading, le connessioni agli Endpoint B partono da host B).

Secondo scenario: l'Endpoint B non può contattare direttamente gli endpoint lato “punto” ma contatterà l'host B e sarà il DNAT a veicolare i pacchetti agli endpoint lato “punto”.

Terzo scenario: potendo configurare il router (o gli host del sito B), è l'approccio più semplice e garantisce bidirezionalità senza manipolazione dei pacchetti.

Riferimenti:

#vpn #openvpn #wireguard #CryptokeyRouting