Une méthode alternative pour configurer Wireguard sous FreeBSD

 x0r   0
freebsd wireguard

J’ai eu l’occasion d’expérimenter récemment avec Wireguard sous FreeBSD. Globalement, je suis plutôt satisfait du mode de fonctionnement, mais aussi de la façon dont cela a été packagé de manière générale sous FreeBSD.

Pour mon usage, j’avais besoin de créer un VPN point à point entre deux machines, une derrière une Livebox et l’autre chez un hébergeur. Je voulais que la seconde ait accès à des services hébergés sur la première, comme Postfix et nginx. Ces services sont configurés sur la première machine pour écouter sur une liste explicite d’adresses IP.

Mais au redémarrage de la machine chez l’hébergeur, Postfix et nginx refusaient de démarrer, car l’interface wg0 créée par Wireguard n’existait pas encore. Ce problème est dû au lancement trop tardif du script /etc/rc.d/wireguard à l’initialisation du système.

Comment faire, alors, pour que les VPN Wireguard soient montés plus tôt ? La solution consiste à les démarrer en même temps que les autres interfaces réseau du système, via le script /etc/rc.d/netif et des éléments de configuration dans /etc/rc.conf. La méthode que je vais vous montrer est en production chez moi depuis quelques mois, et je n’ai pas rencontré de problème particulier jusqu’à maintenant.

Prérequis

Pour commencer, installez Wireguard sur les deux côtés :

# pkg install wireguard-kmod wireguard-tools

Il faudra ensuite choisir un plan d’adressage pour le VPN et générer des clefs et un secret partagé.

Adressage

Pour mon VPN, j’ai choisi d’adresser les deux extrémités en IPv6, en utilisant les adresses locales uniques (Unique Local Addresses, ULA), dans la plage fc00::/7. Pour avoir la quasi-garantie d’une plage unique, il faut utiliser un générateur pour obtenir un /48 privé. J’obtiens par exemple fdd9:835d:6e41::/48.

Puisqu’il n’y a que deux machines dans ce réseau, alors d’après la RFC 6164, il est tout à fait possible (et souhaitable) de se restreindre à un /127 pour adresser les deux côtés. Mais alors, quelles valeurs choisir pour les 64 bits les plus à droite de l’adresse ?

Au début, j’avais choisi fdd9:835d:6e41::1 et fdd9:835d:6e41::2. Mais ce choix est mauvais. Sauriez-vous pourquoi ? Rassurez-vous, il m’a fallu plusieurs semaines pour comprendre.

On pourrait alors choisir fdd9:835d:6e41:: et fdd9:835d:6e41::1 à la place. Là aussi, le choix est mauvais, car la première des deux adresses a ses 64 bits de poids faible à zéro et leur usage en tant qu’adresse unicast est déconseillé. Pour la même raison, l’intervalle fdd9:835d:6e41::ffff:ffff:ffff:ff7f à fdd9:835d:6e41::ffff:ffff:ffff:ffff est déconseillé également.

J’ai donc fini par choisir fdd9:835d:6e41::a et fdd9:835d:6e41::b. Ainsi, pour la suite, j’appellerai les deux côtés « A » et « B », d’après l’identifiant d’interface que je leur donnerai dans le /127 susmentionné.

Dans mon cas, le côté B est celui qui initie la connexion avec le côté A. Les deux côtés peuvent initier la connexion, tout comme avec IPSec, mais dans mon cas, le côté B se trouve derrière un NAT et son adresse IP publique peut changer, alors que le côté A a une adresse IPv4 publique fixe.

Génération des clefs

Ensuite, il faut créer les clefs publiques et privées de chaque côté. La commande ci-dessous génère deux fichiers contenant des clefs représentés en base64.

# umask 077
# wg genkey | tee clef_privee | wg pubkey > clef_publique

Puis, sur l’un ou l’autre des côtés, générez un secret partagé entre les deux stations, qui se trouvera également représenté en base64 dans un fichier :

root@A:~# umask 077
root@A:~# wg genpsk > secret_partage

Nous pouvons ensuite passer à la configuration proprement dite.

Configuration

La page man de rc.conf(5) explique que, pour chaque interface à démarrer, netif cherche un script nommé /etc/start_if.<interface>. S’il existe, alors il est exécuté avant de configurer les adresses. Nous allons donc créer de chaque côté un script /etc/start_if.wg0, destiné à l’interface créée pour le VPN Wireguard. Cette interface est configurée en passant par l’utilitaire wg(8).

Côté B (initiateur)

Ainsi, sur le côté B, qui initie la connexion, le script /etc/start_if.wg0 est le suivant :

#!/bin/sh

/usr/local/bin/wg setconf wg0 /dev/stdin <<EOF
[Interface]
ListenPort = 51820
PrivateKey = clef privée de B

[Peer]
AllowedIPs = fdd9:835d:6e41::a/128
PublicKey = clef publique de A
PreSharedKey = secret partagé
Endpoint = adresse ou nom d’hôte de A:51820
PersistentKeepAlive = 30
EOF

Ici, j’ai choisi d’ajouter l’option PersistentKeepAlive, car il s’agit du côté qui initie la connexion et que je dois traverser des NAT IPv4. Et dans ce script, il faut utiliser le chemin complet vers l’utilitaire wg, car /usr/local/bin ne se trouve pas dans le PATH du shell qui l’exécute.

Étant donné que le fichier contient des secrets, celui-ci doit impérativement être chmod 700 ou la confidentialité de votre VPN sera compromise.

On peut ensuite ajouter les éléments de configuration suivants au rc.conf du côté B :

cloned_interfaces="wg0"  # si cette variable existe déjà, ajoutez "wg0"
ifconfig_wg0_ipv6="inet6 fdd9:835d:6e41::b prefixlen 127"

C’est tout !

Côté A

Pour le côté A, l’idée est la même, mais le contenu du script /etc/start_if.wg0 est légèrement différent :

#!/bin/sh

/usr/local/bin/wg setconf wg0 /dev/stdin <<EOF
[Interface]
ListenPort = 51820
PrivateKey = clef privée de B

[Peer]
AllowedIPs = fdd9:835d:6e41::b/128
PreSharedKey = secret partagé
PublicKey = clef publique de A

Là encore, pensez à faire un chmod 700 sur ce fichier pour éviter de compromettre votre VPN.

Les éléments à ajouter dans rc.conf sont similaires :

cloned_interfaces="wg0"  # si cette variable existe déjà, ajoutez "wg0"
ifconfig_wg0_ipv6="inet6 fdd9:835d:6e41::a prefixlen 127"

Démarrage du VPN

Enfin, de chaque côté, il ne reste plus qu’à démarrer l’interface wg0…

# service netif start wg0

…et de tester en « pingant » l’autre côté (ping6 sous FreeBSD jusqu’à 12, ping tout court à partir de FreeBSD 13) :

root@A:~# ping6 fdd9:835d:6e41::b
PING6(56=40+8+8 bytes) fdd9:835d:6e41::a --> fdd9:835d:6e41::b
16 bytes from fdd9:835d:6e41::b, icmp_seq=0 hlim=64 time=60.610 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=1 hlim=64 time=79.763 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=2 hlim=64 time=59.891 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=3 hlim=64 time=59.442 ms
^C
--- fdd9:835d:6e41::b ping6 statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 59.442/64.926/79.763/8.576 ms

Conclusion

Je vous ai donc présenté une méthode pour lancer un VPN Wireguard le plus tôt possible au démarrage de FreeBSD.

Cette méthode ne se limite pas qu’aux VPN point à point, mais il se trouve que dans mon cas d’usage, un lien entre deux machines suffisait. Si nous avions eu plusieurs machines initiatrices de connexions à un seul et même point central, les configurations de chaque initiateur serait identique (à la clef privée près) et le point central aurait une déclaration par initiateur autorisé.

Il y a aussi quelques opportunités de mettre les choses un peu plus au propre dans ce système. Tout d’abord, le script /etc/start_if.wg0 contient toute la configuration pour l’interface wg0, dans un seul fichier. Je peux comprendre que ce choix puisse déranger certains. Mais adapter ce script pour qu’il lise des secrets ou un fichier de configuration complet dans /usr/local/etc/wireguard est tout à fait possible.

De même, le nom wg0 est codé en dur dans le fichier ; mais là encore, il est possible de modifier le script en examinant le nom sous lequel il est appelé (variable $0) et d’en dériver le nom de l’interface à démarrer (par la transformation ${0##*.}). Ainsi, s’il faut démarrer un autre VPN Wireguard, en imaginant que l’interface s’appelle wg1, on pourrait finir par juste pouvoir s’en sortir en faisant de /etc/start_if.wg1 un lien symbolique vers /etc/start_if.wg0.

Dans tous les cas, je vous ai montré qu’il est tout à fait possible de passer par netif, pour démarrer les VPN Wireguard en même temps que les autres interfaces réseau, dès l’initialisation du système. Ainsi, les interfaces wg se comportent beaucoup plus comme des interfaces natives du système, ce qui est élégant et appréciable.

Commentaires

Poster un commentaire

Poster un commentaire