Déployer Wireguard un peu partout🔗
Introduction
Cela fait quelques temps que j'ai vu passer le logiciel Wireguard. Étant donné qu'il est créé et maintenu par zx2c4, dont j'utilise l'excellent cgit, je me suis dit que ça valait le coup d'y passer un temps certain.
Qu'est-ce-que Wireguard ? Pour faire simple, c'est un VPN. Il est écrit en C avec quelques lignes (5000) de code et semble prometteur pour l'avenir, tant du point de vue de la sécurité que des performances.
Dans cet article, nous allons simplement installer et configurer Wireguard sur une série de machines qui formeront notre VPN.
Installer Wireguard sur une machine Debian Buster
Wireguard est composé de deux éléments:
- un module pour le noyau Linux.
- un ensemble d'outils en espace utilisateur.
Sous une machine Debian classique, on peut utiliser la dernière version de Wireguard en installant:
# apt install wireguard-dkms wireguard-tools
Normalement, ces actions devraient vous conduire à installer de quoi compiler le module noyau en live (DKMS), c'est-à-dire le compilateur gcc en version 7 ainsi que les fichiers d'en-tête du noyau Linux. Sur une station de travail, ce n'est pas si lourd.
Installer WireGuard sur un Scaleway C1
J'ai un serveur Scaleway C1. Comme pour n'importe quel fournisseur de serveur dédié, vous ne pouvez pas forcément faire ce que vous voulez. Hé oui, ce n'est pas votre matériel. Forcément, ça s'en ressent aussi dans la partie logicielle.
Je me suis inspiré de ce document. Mais j'ai dû fortement améliorer mes compétences en gestion de noyau pour faire fonctionner réellement Wireguard sur cette machine. Car, il faut bien l'avouer, ma dernière compilation de kernel Linux doit dater d'il y a au moins 10 ans.
Si vous suivez la documentation sus-hyperliée, vous n'obtiendrez rien de satisfaisant. En effet, il existe des petites subtilités qui font que ça ne fonctionnera pas directement.
Le premier problème auquel j'ai dû me confronter est le fait que ma compilation de noyau ne donnait rien d'utilisable en termes de modules. J'ai même réussi à bloater une partie du C1 pendant quelques jours avec des modules qui ne se chargeaient plus. J'ai finalement pris le parti d'essayer de comprendre quelques options, notamment celle de CONFIG_MODULE_SRCVERSION
Ensuite, après avoir réglé ce premier problème et réussi à charger mes modules compilés avec modprobe
, j'ai eu beaucoup de mal pour faire fonctionner le module Wireguard qui ne se chargeait tout simplement pas. Après quelques jours de repos (quand un truc ne marche pas, il faut le laisser se reposer, l'éviter pendant un temps. Quand on y revient, c'est souvent la voie du succès alors), je me suis rendu compte que le CPU/SOC du Scaleway C1 est un Marvell Armada 370 et que ce dernier ne gère pas les extensions NEON.
Malheureusement, Wireguard impose d'utiliser les extensions NEON dans le noyau si vous avez un CPU sous ARMv7, ce qui est le cas du CPU du Scaleway C1. J'ai donc dû modifier le fichier Kconfig de Wireguard pour modifier la ligne:
select KERNEL_MODE_NEON if CPU_V7
en
select KERNEL_MODE_NEON if (CPU_V7 && !MACH_ARMADA_370)
et le tour était joué. Je ne pensais pas devoir aller si loin mais j'avoue que quand j'ai réussi à faire un modprobe wireguard
sans aucun message d'erreur, j'ai jubilé !
J'en remets le contenu à ma sauce:
# On récupère les éléments de version du Kernel de la machine arch="$(uname -m)" release="$(uname -r)" upstream="${release%%-*}" local="${release#*-}" # On récupère les sources mkdir -p /usr/src wget -O "/usr/src/linux-${upstream}.tar.xz" "https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-${upstream}.tar.xz" tar xf "/usr/src/linux-${upstream}.tar.xz" -C /usr/src/ ln -fns "/usr/src/linux-${upstream}" /usr/src/linux ln -fns "/usr/src/linux-${upstream}" "/lib/modules/${release}/build" # On récupère la configuration du kernel utilisé zcat /proc/config.gz > /usr/src/linux/.config printf 'CONFIG_LOCALVERSION="%s"\nCONFIG_CROSS_COMPILE=""\n' "${local:+-$local}" >> /usr/src/linux/.config echo "CONFIG_MODULE_SRCVERSION=y" >> /usr/src/linux/.config wget -O /usr/src/linux/Module.symvers "http://mirror.scaleway.com/kernel/${arch}/${release}/Module.symvers" # On prépare la configuration make oldconfig # On patche Wireguard in-tree /home/medspx/WireGuard/contrib/kernel-tree/create-patch.sh | patch -p1 sed make menuconfig make prepare modules_prepare make CFLAGS_MODULE=-fno-pic -j9 make CFLAGS_MODULE=-fno-pic -j9 modules make modules_install
Configurer le VPN
Ok, nous avons maintenant deux bécanes avec Wireguard. Construisons donc un VPN avec. Je pars du principe que vous n'avez pas de firewall entre les deux, pour simplifier les commandes.
Avec Wireguard, ce qui est bien, c'est qu'on peut construire le VPN en live puis balancer la configuration finale dans un fichier de configuration avec une seule commande. Tout se passe avec les commandes classiques ip
et wg
.
-- Génération des clefs privées/publiques # wg genkey > /etc/wireguard/private -- Ajout de l'interface wireguard (wg0) # ip link add dev wg0 type wireguard -- Affectation d'une IP virtuelle sur l'interface wireguard # ip addr add 192.168.3.4/24 dev wg0 -- On ajoute notre clef privée # wg set wg0 private-key /etc/wireguard/private -- On peut maintenant voir la configuration en dynamique # wg showconf wg0 -- On va maintenant ajouter la définition d'un autre client -- Vous avez besoin de sa clef publique que vous pouvez -- obtenir avec wg show wg0 # wg set wg0 peer LHqF7DAsJoRftEWmj+b2HnBKonZqLwJwdO9eLQYx+lI= -- allowed-ips 192.168.3.1/32 endpoint medspx.fr:54003 -- Ensuite, il suffit d'activer l'interface réseau ip link set wg0 up -- Sur l'autre machine, il faut également ajouter ce nouveau peer # wg set wg0 peer clef_publique allowed-ips 192.168.3.4/32 -- et maintenant, la connexion devrait être effective # wg show wg0 -- On peut maintenant stocker la configuration en dur # wg showconf wg0 > /etc/wireguard/wg0.conf -- Un peu plus tard, vous pouvez recharger cette configuration # wg setconf wg0 /etc/wireguard/wg0.conf
Configuration avec systemd-networkd
Une fois la configuration dynamique passée, il peut être intéressant d'utiliser systemd pour garder cette configuration. Pour ma part, je n'utilise que systemd pour la configuration de mes interfaces réseaux. Wireguard est géré par systemd-networkd depuis systemd 237. Cela signifie que c'est présent dans Debian Buster mais également dans Debian Stretch si vous avez activé les dépôts backports.
Vous devez créer un fichier /etc/systemd/networkd/30-wg0.netdev
:
[NetDev] Name = wg0 Kind = wireguard Description = Wireguard [WireGuard] ListenPort = auto PrivateKey = [CLIENT PRIVATE KEY] [WireGuardPeer] PublicKey = [SERVER PUBLIC KEY] PresharedKey = [PRE SHARED KEY] AllowedIPs = 192.168.3.1/32 Endpoint = medspx.fr:54003 PersistentKeepalive = 60
Le ListenPort à auto
indique que le port change chaque fois que vous lancez l'interface. C'est une configuration plutôt intéressante pour des stations en mode client sans configuration spécifique de pare-feu.
Si vous avez un serveur public avec une IP fixe, je vous conseille de déclarer un port spécifique et de l'ouvrir dans votre pare-feu.
Le PersistentKeepalive est également intéressant si vous êtes derrière un NAT (donc plutôt réservé aux stations de type client). Avec cette configuration, Wireguard envoi un petit paquet réseau de temps en temps (selon l'intervalle indiqué) pour ré-ouvrir l'interface vers l'extérieur depuis le réseau interne. Cela permet de laisser la connexion ouverte au niveau du routeur NAT et donc de laisser votre client disponible pour le reste du VPN.
Si vous configurez une sorte de serveur VPN, vous devez indiquer tous les clients enregistrés en ajoutant autant de directives [WireGuardPeer]
que de clients, afin de partager les clefs publiques.
Puis, vous aurez besoin d'un fichier pour configurer votre interface dans /etc/systemd/networkd/30-wg0.network
:
[Match] Name = wg0 [Link] RequiredForOnline = no [Network] Address = 192.168.3.4/24 [Route] Gateway = 192.168.3.1 Destination = 192.168.3.0/24
J'ai indiqué la directive RequiredForOnline à no
car, pour certaines machines (typiquement un portable avec une connexion erratique), cela permet de démarrer le système sans attendre que Wireguard soit activé.
Aller un peu plus loin que le test du ping
Nous allons mettre en place ssh derrière une machine du VPN. L'astuce consiste simplement à mettre la ligne suivante dans /etc/ssh/sshd_config
:
# L'adresse publique ListenAddress 10.0.0.1:2222 # On écoute sur l'adresse Wireguard ListenAddress 192.168.3.1:22
À partir de cet instant, vous pouvez utiliser un client ssh depuis une des machines du VPN pour attaquer directement le serveur via Wireguard.
On le voit, la grande force de Wireguard est d'utiliser une interface réseau virtuelle dédiée. De fait, n'importe quelle configuration classique d'un logiciel serveur peut être utilisée sans commandes spéciales.
Si vous avez un firewall
Pour ma part, mon C1 a un pare-feu configuré avec nftables. J'ai choisi de laisser passer tout ce qui vient ou va vers wg0.
Globalement ma configuration est la suivante:
#!/usr/sbin/nft -f # Configuration NFT de debianplug # On vide toutes les règles flush ruleset # Table pour un serveur public un peu fermé table inet public_server { # Chaîne de gestion de tout ce qui rentre chain entree { # Chaîne de type filtre, en entrée type filter hook input priority 0; # Accepter tout ce qui vient de l'interface loopback meta iif lo accept # Accepter tout ce qui vient de Wireguard meta iif wg0 accept # Accepte le trafic qui vient de la machine ct state established,related accept # Réponse au ping IPv4 ip protocol icmp accept # Ouverture des ports publics # Le port 54003 est celui de Wireguard tcp dport { 25,53,80,143,443,1376,4416,4444,5222,5269,9714,54003 } accept udp dport { 53, 9714, 54003 } accept # Ouverture du port nbd tcp sport { 4416 } accept # Compter et journaliser ce qui est rejeté counter log prefix "Connexion entrante rejetée: " level warn drop } # Chaîne de gestion de tout ce qui sort chain sortie { type filter hook output priority 0; # On accepte les sorties vers l'interface loopback meta oif lo accept # On accepte les sorties vers Wireguard meta oif wg0 accept # Réponse au ping IPv4 ip protocol icmp accept # Ouverture des ports publics tcp sport { 25,53,80,143,443,1376,4444,5222,5269,9714,54003 } accept udp sport { 53,9714,54003 } accept # Ouverture des ports externes classiques utilisés par le système tcp dport { 25,53,80,143,443,995,4416,8443 } accept udp dport { 53,67,68,123 } accept # count and drop any other traffic counter log flags skuid prefix "Connexion sortante rejetée: " level warn drop } }
Dans cette configuration, j'ouvre le port 54003 de Wireguard sur cette machine et j'autorise tout ce qui passe par l'interface wg0. De cette manière, tout marche !
Conclusions
Ce truc est vraiment extra ! D'abord, ça marche et ça passe pas mal de caca. Le code est super light. J'ai eu l'occasion de le parser rapidement pour trouver mes erreurs de symboles de Kernel et j'ai été étonné du nombre ultra limité de fichiers et de lignes (5000 seulement, sachant que mon programme de base pgMitm en fait près de 1000 à lui tout seul pour pas grand-chose). L'implémentation est vraiment élégante et fine. Je ne parle pas de la configuration en live qui témoigne d'un vrai réalisme dans la mise en place de réseaux qui se solde généralement par un nombre impressionnant de systemctl restart systemd-networkd
.
Comme d'habitude, Jason Donenfield et zx2c4 sortent un truc de qualité et je suis fier de leur rendre un hommage appuyé.