Wireguard - Généralités
Posté le 02. Avril 2022 • 12 minutes • 2395 mots • Autres langues: English
A - Introduction sur le fonctionnement général
Wireguard est un protocole VPN récent (2017) et sous licence libre. En 2018 il est intégré au kernel Linux.
Voici une citation1 intéressante de Linus Torvalds issue de la mailing-list concernant le kernel Linux :
I see that Jason actually made the pull request to have WireGuard included in the kernel. Can I just once again state my love for it and hope it gets merged soon? Maybe the code isn’t perfect, but I’ve skimmed it, and compared to the horrors that are OpenVPN and IPSec, it’s a work of art. Linus 1
Passons en revue quelques caractéristiques intéressantes de Wireguard :
- Wireguard a la particularité d’être simple : 4000 lignes de codes contre +400 000 pour OpenVPN par exemple et +600 000 pour IPsec.
- WireGuard a donc une surface d’attaque moins importante ainsi que des performances bien supérieures à OpenVPN ou IPsec. (Voir mes essais plus bas dans l’article).
- Wireguard ne fonctionne qu’en UDP, pas de TCP. Il est pleinement compatible IPv4 et IPv6.
- Wireguard a été créé pour être aussi facile à configurer et déployer que SSH . La connexion VPN se fait par un simple échange de clé publique comme SSH. WG gère le roaming d’IP et il n’y a plus besoin de manager les connexions ou de vérifier leur état, ou de gérer des démons.
- Wireguard au niveau cryptographique utilise l’état de l’art actuel en la matière : courbe elliptique et les meilleurs algo de hashage prêt pour l’informatique quantique. Il ne permet pas d’utiliser d’autres suites crypto à ma connaissance. Cela le rend incompatible avec des systèmes anciens, mais aussi beaucoup plus résistant aux attaques de type “downgrade” qui cherchent à négocier une connexion avec des suites crypto qui comporteraient des failles de sécurité par exemple.
- Enfin, les performances sont excellentes notamment car il est intégré au kernel, il ne fait que 4k lignes de code et utilise les suites crypto les plus rapides.
B - Interfaces réseaux
WG fonctionne en ajoutant une ou plusieurs interfaces réseaux nommées wg0, wg1… On peut configurer ces interfaces comme n’importe quelle autre : ajouter des route, nat, fixé une ip… Pour les aspects réseaux spécifiques à wireguard il faudra utiliser l’utilitaire wg. Le nom des interfaces réseaux est totalement libre, se basant sur le nom du fichier de conf.
C - Installation et déploiement
1 - Installation du paquet
Sous Debian 11 :
apt-get install wireguard
Si vous êtes sous Debian 10, il est nécessaire d’activer les backport de la distribution.
2 - La génération des clés publiques/privées.
Afin d’assurer une confidentialité maximale on passera le umask en 077 avant de passer ces commandes :
umask 077
Pour générer une clé privée la commande est :
wg genkey > privatekey
Pour générer la clé publique dérivée de la clé privée, la commande sera :
wg pubkey < privatekey > publickey
On peut combiner ces commandes en une seule ligne pour plus de praticité :
umask 077 && wg genkey | tee privatekey | wg pubkey > publickey
Généralement la clé privée ira dans le fichier /etc/wireguard/wg0.conf
. La clé publique peut être retrouvée facilement en l’affichant avec : wg show wg0
(ou juste wg
si une seule interface)
Le livre blanc stipule bien que les transferts de clés ne sont pas gérés pas Wireguard, comme pour SSH c’est à vous de transférer vos clés publiques selon vos méthodes/outils.
3 - Le lancement de wireguard
Il y a deux manière de lancer wireguard :
- à la main
- avec l’outil
wg-quick
3-1 - A la main
A la main, cela donnerait par exemple :
ip link add dev wg0 type wireguard
ip address add dev wg0 192.168.2.1/24
ip address add dev wg0 192.168.2.1 peer 192.168.2.2
A partir de là, l’interface est montée il restera à ajouter les clé des peers et les endpoints et pour cela toujours en mode manuel il y a deux options, soit on va lire le fichier un fichier de conf contenant les infos des peer et de l’interface avec :
wg setconf wg0 myconfig.conf
Ou alors on le fait à la main avec par exemple :
wg set wg0 listen-port 51820 private-key /path/to/private-key peer ABCDEF... allowed-ips 192.168.88.0/24 endpoint 209.202.254.14:8172
C’est tout… et oui WG ce n’est que du réseau :)
3-2 - Avec l’outil wg-quick
L’outil wg-quick permet en gros de passer les commandes ip
à notre place, c’est-à-dire :
- Création de l’interface réseau de type wireguard
- Ajout de l’IP à l’interface
- UP de l’interface
- Ajout des routes
- …
L’outil wg-quick va se baser sur ce qui est présent dans le fichier /etc/wireguard/wg0.conf ou wg0 est le nom de l’interface. wg-quick up wg1
lira par exemple le fichier /etc/wireguard/wg1.conf
3-3 Astuces et commandes plus complètes
Je me suis basé sur le man des commandes pour la suite.
L’option syncconf
de la commande wg
:
syncconf Like setconf, but reads back the existing configuration first and only makes changes that are explicitly different between the configuration file and the interface. This is much less efficient than setconf, but has the benefit of not disrupting current peer sessions. The contents of must be in the format described by CONFIGURATION FILE FORMAT below.
L’option strip
de la commande wg-quick
:
Use strip to out-put a configuration file with all wg-quick(8)-specific options removed, suitable for use with wg(8).
Du coup je combine ces deux commandes afin de modifier le fichier /etc/wireguard/wg0.conf “without disrupting current peer sessions”. C’est le cas typique pour un ajout de client sur un serveur par exemple.
La commande finale sera donc :
wg syncconf wg0 <(wg-quick strip wg0)
Attention, <(commande)
est une substitution de processus en bash et si on ajoute un espace entre < et ( il y a une erreur, j’ai bloqué là-dessus un moment :S.
4 - Automatiser le lancement de l’interface WG
Pour démarrer wireguard en tant que service, il suffit de rentrer :
systemctl start wg-quick@wg0
wg0 étant le nom du fichier wg0.conf présent dans /etc/wireguard
Pour péreniser à chaque démarrage de la machine :
systemctl enable wg-quick@wg0
Ce qui doit avoir pour réponse :
Created symlink /etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service -> /lib/systemd/system/wg-quick@.service.
5 - Exemple de fichier de conf
Fichier de conf /etc/wireguard/wg0.conf de ma VM1 :
[Interface]
Address = 192.168.2.1/32
PrivateKey = MBeJIvr0a0qahCiQv37X89ZllAYCd+2Ch5OaaQt8amQ=
ListenPort = 51820
[Peer]
PublicKey = bX8dih2FWl/P18RGyJ+JpOV4nwvFEtpdU4I70IV3PlY=
AllowedIPs = 10.0.0.1/32
Fichier de conf de ma VM2 :
[Interface]
Address = 10.0.0.1/32
#Address = 192.168.2.2/32
PrivateKey = OJKJKA2eHTYtSEbaT+u2u3/SnN4nIqBb1Ha64rrFDFc=
ListenPort = 51821
[Peer]
PublicKey = uwOY7H0Z2mFMAsJlXR/1p7V5p8yk3AZecjuYydmDzHs=
AllowedIPs = 192.168.2.1/32
Endpoint = 172.16.25.100:51820
Comme indiqué plus bas dans ma note (voir “Client/server ou server/server” ou encore la partie “roaming” de mes “Notes sur le livre blanc”) dans cette configuration seule la VM2 peut ping la VM1. En effet, la VM2 connaît le endpoint de la VM1 mais pas l’inverse.
En revanche, à partir du premier ping de la VM1 vers la VM2, la VM1 mettra dynamiquement à jour sa “cryptokey tab” en ajoutant le endpoint de la VM1 ce qui permettra à VM1 de répondre ou d’initier une connexion plus tard.
D - Benchmark : OpenVPN vs Wireguard
1 - Client/server ou server/server
A retenir dans tous les cas : Pour que deux hotes puissent communiquer chacun doit détenir la clé publique de l’autre.
Ensuite on peut vouloir un VPN poste à site ou site à site ce qui revient à faire du client/server ou du server/server.
Dans le cas d’une connexion client/server, c’est toujours le client qui souhaitera se connecter au serveur. Le client doit donc disposer du Endpoint:port
du serveur. En revanche le serveur n’a pas besoin de disposer du Enpoint:port
du client. Evidemment le serveur ne pourra pas initier une communication avec le client, car il ne sait pas où il se trouve. En revanche, il saura lui répondre car il mettra à jour dynamiquement le endpoint:port
à partir du moment ou le client le contact. En faisant mes tests c’est flagrant, tant que le client n’a pas ping le serveur, le serveur ne peut pas ping le client. A partir du moment où le client a ping une seule fois le serveur, le serveur peut ping le client. Cette technique permet un roaming parfait car le client peut changer de connexion en permanence. D’ailleurs, la commande wg est mise à jour automatiquement, on peut le constater avec watch -n 1 wg
, au moment du ping, la cryptokey table se met à jour avec le endpoint du client.
Dans le cas d’une communication server/server il faudra bien préciser le enpoint:port
sur les deux afin d’avoir une connexion qui pourra toujours être initiée par l’un ou l’autre des serveurs.
2 - Test de performance
Pour éviter toutes fluctuation de débit ou latence dû au réseau Internet, je n’ai travaillé qu’en local pour ces tests. Tous les tests sont effectués entre deux VMs sur mon PC portable, sur un réseau local gigabit. Les VMs disposent de : 512Mo de RAM et 2 coeurs de i7-8550U. Le stockage est sur SSD. Le fichier de test est un fichier de 1Go qui est téléchargeable sur proof.ovh.net OpenVPN est paramétré en UDP pour ce test.
2-1 - Rsync par SSH
Rsync par le réseau et donc par SSH d’un fichier de 1Go :
- Vitesse sans VPN : 92Mo/sec
- Vitesse avec Wireguard : 53Mo/sec
- Vitesse avec OpenVPN : 18Mo/sec
Sans VPN :
Wireguard :
OpenVPN :
2-2 - Rsync en local par partage Samba
Le test effectué est sur un Samba paramétré en plain (sans AES)
J’ai monté un samba sur la VM-1, j’ai monté le partage samba sur la VM-2 sur la point de montage /smbshare
J’ai lancé un rsync du fichier de 1Go. Le point de montage étant local, rsync ne passe pas par une connexion SSH avec la VM-1 et il y a donc un gain de performance notable :
- Vitesse sans VPN : 162Mo/sec
- Vitesse avec Wireguard : 71Mo/sec
- Vitesse avec OpenVPN : 21Mo/sec
Sans VPN :
Wireguard :
OpenVPN :
2-3 - Rsync en local par partage NFS
Le test effectué est sur un partage NFS paramétré en plain (sans AES)
- Vitesse sans VPN : 144Mo/sec
- Vitesse avec Wireguard : 66Mo/sec
- Vitesse avec OpenVPN : 18Mo/sec
Sans VPN :
Wireguard :
OpenVPN :
2-4 - Conclusion des tests de performance
Dans mes conditions de tests, Wireguard est en moyenne 3.3x plus rapide que OpenVPN.
Pour aller plus loin et mieux comprendre Wireguard, je conseille la lecture des pages 3 à 7 du livre blanc disponible sur le site Wireguard.
Voici mes notes de lecture.
Quelques notes de lecture du livre blanc (page 3-7)
WG est stateless, pas besoin de s’occuper de connexion/deconnexion/demon etc… WG comme SSH ne s’occupe pas de l’échange des clés. Les clés sont courtes en plus et sont donc simplement partageable. “Finally, WireGuard is cryptographically opinionated”, il n’utilise pas de vieux protocoles ou suite crypto, uniquement certaines les plus récentes et il n’y a aucune souplesse là-dessus. C’est ce qui le rend très résistant aussi. WG a une protection contre le DoS intégré en utilisant “a new crypto-cookie mechanism for IP address attributability”. Du coup WG ne fonctionne que sur la couche 3 et nulle part ailleurs et supporte aussi bien ipv6 que ipv4.
Le fonctionnement du routage WG : Le principe fondamental d’un VPN sécurisé est l’association entre des pairs (peers) et une adresse IP qui est autorisé comme IP source. Dans WG les pairs (peers) sont identifiés strictement par leur clé publique (32bits Curve25519). Ce qui veut dire que le fonctionnement est un simple mapping entre un ensemble d’adresses IP autorisé et des clés publiques. C’est aussi simple que ça.
Voici un exemple d’une table de routage de clé crypto (crypto key routing table) :
L’interface elle-même a une clé privée et un port UDP d’écoute, c’est la première ligne. S’en suite une liste de peers identifiés par une clé publique et chaque peer a une liste d’IP source autorisée.
Quand un paquet est transmis par une interface WG (ie wg0) la table est consultée pour déterminé quelle clé publique utilisé pour le chiffrement du paquet. Par exemple un paquet ayant pour destination l’IP 10.192.122.4 sera chiffré en utilisant la clé TrMv….WXX0. Inversement quand wg0 reçoit le paquet chiffré après déchiffrement ET authentification du paquet il ne sera accepté uniquement si l’IP source est résolu dans la table et à la ligne qui correspond à la clé publique utilisée pour chiffré le paquet. Par exemple si le paquet est déchiffré depuis xTIB…p8Dg, il ne sera accepté que si l’IP source est 10.192.122.3 ou si l’IP est dans le range compris entre 10.192.124.0 à 10.192.124.255. Sinon le paquet sera dropped.
Du coup les règles de firewall sont simples et tout paquet arrivant sur une interface WireGuard aura une IP source authentique et fiable (en plus, bien sûr, de la garantie du secret parfait du transport via le chiffrement). C’est possible uniquement parce que WG est strictement basé sur la couche 3. Dans le cas ou on souhaiterait routé tout le trafic au travers d’un peer WG vers un autre peer WG c’est possible en configurant simplement la cryptokey routing table ainsi :
“Here, the peer authorizes HIgo…f8yk to put packets onto wg0 with any source IP, and all packets that are outgoing on wg0 will be encrypted using the secure session associated with that public key and sent to that peer’s endpoint.”
Endpoint et roaming (itinérance) : Il est important que des peer puissent communiquer sur l’internet avec donc des adresses IP sources qui ne sont pas forcément spécifiées. C’est pourquoi pré-spécifié une IP externe et un port UDP est optionnel. C’est optionnel car si WG recoit un paquet correctement authentifié d’un peer, il utilisera l’IP source externe afin de déterminé le endpoint. Comme une clé publique identifie de façon unique un peer, l’ip source du paquet WG chiffré est utilisé en tant que “remote endpoint”.
IMPORTANT : il faut noter que le port d’écoute et le port d’envoi des paquets est toujours le même ! Cela ajoute de la simplicité et une meilleure reliability notamment si on traverse du NAT. Ainsi WG sait que si le paquet provient de x.x.x.x:21841 il faudra répondre sur ce même port. Du coup il n’y a pas besoin de conserver ouverte une session NAT par exemple car le port ne changera pas, toutefois : “the interface can be optionally configured to periodically send persistent authenticated keepalives” , c’est l’option : PersistentKeepalive = XX