Tailscale Access Controls

J'ai souvent parlé de Zerotier que j'utilise au quotidien, mais qui dans sa version gratuite comporte quelques limitations en terme de contrôle d'accès dans un environnement mixant du site-to-site et des clients isolés. J'ai donc voulut voir ce que proposait Tailscale, qui est un VPN managé relativement similaire reposant sur Wireguard. Tous deux offrent des fonctionnalités gratuites et payantes. Si payer ne doit pas être un problème dans un environnement de production, c'est un peu différent en mode home lab.... d'autant plus que certaines fonctionnalités de la version gratuite ne se retrouvent que dans la version Premium à $18 /mois/utilisateur.

La version gratuite des Tailscale est plutôt généreuse, elle propose 100 nodes et 3 utilisateurs (à condition de disposer d'un "custom domain" (je vais utiliser des comptes Microsoft 365, mais plusieurs autres possibilités sont disponibles : SSO Identity Providers). On peut donc disposer de 3 comptes sur un même domaine. Je parle de comptes car nativement (et normalement) le contrôle d'accès se fait sur l'utilisateur. Manque de chance j'ai besoin de gérer 4 utilisateurs, et je ne suis pas prêt à payer 4 x $18, ce qui me ferait plus de $ 800 par an !

J'ai donc cherché à contourner et je vais finalement gérer mes autorisations à partir des machines Tailscale que j'ai approuvées (mode NFS pour ceux qui se souviennent). Dans cet usage je ne cherche pas faire communiquer les clients Tailscale entre eux mais à leur permettre d'accéder à des ressources situées sur différents subnets sur lesquels je souhaite restreindre les possibilités.

Pour la suite on considère que le réseaux Tailscale est configuré avec les nodes qui servent à joindre les subnets.

Je vais créer deux utilisateurs :

[email protected]
[email protected]

Le premier compte qui sera également le mien va également me permettre de configurer et approuver les machines clientes.

Le second servira à connecter mes utilisateurs et je pourrais approuver leurs machines. Ce compte étant protégé en MFA, il nécessitera une validation de ma part. Une fois la machine connectée, je désactive l'expiration de la clé et on passe au contrôle s'accès :

{
  "acls": [
    {"action": "accept", "src": ["[email protected]"], "dst": ["*:*"]}, // Ici l'admin dispose de tous les droits
    
    // Autorisation par machines
    {
      "action": "accept",
      "src": ["azura", "lydia"],      // Ici deux clients Tailscale
      "dst": [
        "pipa:25,53",                 // On autorise le host qui supporte le DNS si on veut utiliser les noms courts...
        "mixa:80,443,445,3389",
        "nona:80,443,445,1443,3389",  // Ports autorisés

        "lab-1:*",

        "192.168.2.12:80,443,9100,5001,3389", // Par IP + ports
        "192.168.6.0/24:*",                   // Par subnet
      ],
    },
  ],
  "hosts": {
    "azura": "100.1.2.3",        // Client Tailscale
    "lydia": "100.5.2.3",        // Client Tailscale

    "mixa":  "192.168.1.4",      // Host on subnet
    "nona":  "192.168.2.8",      // Host on subnet
    "pipa":  "192.168.2.1",      // Host on subnet

    "lab-1": "192.168.6.0/24", // subnet
  },

}

Dans l'ordre :

  1. J'autorise l'admin à tout voir.
  2. Ensuite j'autorise les machines azura et lydia à accéder uniquement aux hosts que j'aurais défini avec une restriction sur certains ports. C'est possible en définissant les hosts plus loin ou en se servant directement des adresses IP. L'ICMP (ping) est ouvert automatiquement vers les destinations autorisées.
  3. Je défini les clients et serveurs.

Voilà. Je n'ai pas creusé toutes les possibilité offertes par le contrôle d'accès proposé par Tailscale, la liste est longue et le documentation plutôt bien faire. Idées bienvenues !

Redirection de port sous Linux : 2/2

Dans la partie précédente, en bon béotien, j'ai exploré pas mal de techniques de redirection de port, mais j'aurais du commencer par l'exploitation de ce qui est depuis un certain temps de base sans Linux, à savoir IPTables.

Pour cette manipulation on va utiliser un routeur Ubiquiti ER-X avec la dernière version de EdgeOS (2.0.9-hotfix.2). Et sur ce routeur on va commencer par y installer Wireguard (pour changer un peu, mais Zerotier ou les VPN disponibles de base auraient pu faire l'affaire).

On va se rendre compte que la philosophie de WireGuard est bien différente de Zerotier (ou même de TailScale). Ici il faut tout borner, c'est la version barbus et les ACL ne se gèrent pas sur une console d'admin, il faudra pour ça utiliser le firewall du point d'entrée, ce qui à mon gout est bien moins souple et rend impossible certaines restrictions plus granulaires au niveau de l'utilisateur client...

Je ne suis pas expert, mais contrairement à Zerotier ou le client génère un ID propre à la machine, sous WireGuard un simple copié collé suffit à exporter et utiliser la configuration ailleurs... On peut certes utiliser une clé supplémentaire (PresharedKey), mais ça ne changera rien. Un mot de passe qui se transmet vocalement aurait été préférable. Cela ne sera pas trop gênant dans une configuration fermée (la mienne, un serveur verrouillé vers un routeur qui sera tout autant), par contre dans le cadre de l'utilisation pour un utilisateur lambda itinérant et souvent inconscient cela pose question, tout comme le non support apparent d'une clé physique...

WireGuard coté serveur :

On se connecter en SSH et on installer la dernière version que l'on va trouver ici avec les commandes suivantes :

curl -OL https://github.com/WireGuard/wireguard-vyatta-ubnt/releases/download/1.0.20210606-1/e50-v2-v1.0.20210606-v1.0.20210424.deb
sudo dpkg -i e50-v2-v1.0.20210606-v1.0.20210424.deb

Attention à bien remplacer par la dernière version et faire attention car il existe des versions spécifiques à chaque modèle de routeur et pour chaque version de EdgeOS (v1 et v2).

Si on manque de place (df) on fait un show system image et on efface celle qui ne sert à rien après une mise à jour avec un delete system image. Et oui il y a du vécu.

Quand WireGuard est installé on va générer nos clés :

sudo wg genkey | tee /dev/tty | wg pubkey

Ce qui va nous donner deux clés, la première est la clé privée, la seconde la clé publique.

0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=
KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=

On sauvegarde ces clés au chaud et on passe à la configuration de l'interface CLI propre à EdgeOS (configure, commit, save et exit)

configure

On y associe un subnet privé, un port UDP et la clé privée :

set interfaces wireguard wg0 address 192.168.33.1/24 # Ici on choisit l'IP de notre serveur WireGuard
set interfaces wireguard wg0 listen-port 51833       # Ici le port UDP qu'il conviendra de redirigier si on est pas en Bridge ou DMZ...
set interfaces wireguard wg0 route-allowed-ips true  # Pour sortir de ce subnet....
set interfaces wireguard wg0 private-key 0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=

On va ensuite déclarer les pairs (peers) autorisés en associant notre interface à leur clé publique et une IP autorisée. 

set interfaces wireguard wg0 peer N4cuA0WPkMt+3hfidlemsI/VGcupv96NtrkwA/esf2E= allowed-ips 192.168.33.2/32
set interfaces wireguard wg0 peer u2w/+ZNI2RwbYTdft9yggPGnff8QexY9UjjvdvVf0gM= allowed-ips 192.168.33.3/32

On ajoute une règle sur le firewall (Il est également possible aussi de faire ça depuis l'interface du routeur)

set firewall name WAN_LOCAL rule 20 action accept
set firewall name WAN_LOCAL rule 20 protocol udp
set firewall name WAN_LOCAL rule 20 description 'WireGuard'
set firewall name WAN_LOCAL rule 20 destination port 51833

Et on termine par :

commit
save
exit

Il est bien sur possible de faire ça en plusieurs fois, mais dans ce cas là il ne faudra pas oublier de rentrer dans le CLI propre à EdgeOS (configure, commit, save et exit).

Si je fais un scan des ports coté WAN, je ne dois rien voir le seul port ouvert étant le 51833 en UDP.

WireGuard coté client

Le client peut être sous n'importe quel OS, dans mon cas ce sera Windows et on va faire la configuration manuellement (il y a moyen de préparer des fichiers de configuration à importer pour un déploiement conséquent). Plus haut on a ajouté la clé publique fournie par le client dans les pairs autorisés. Cette clé est propre à chaque tunnel défini coté client. Idem pour la clé privée du client que l'on reporte ci dessous, elle est crée lors de l'ajout d'un tunnel sur le client. Ensuite on renseigne le pair du client, c-a-d le serveur que l'on a créé plus haut, sa clé publique, les IP autorisées pour ce tunnel ainsi que l'IP ou le TLD du serveur.

[Interface]
PrivateKey = IIczTA5sdrdcg4+VQNnudslgnveoR5ZDD3ZyL0ZXonU=
ListenPort = 15092
Address = 192.168.33.2/32

[Peer]
PublicKey = KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=
AllowedIPs = 192.168.33.0/24
Endpoint = 69.69.69.69:51833
PersistentKeepalive = 25

Ensuite on active le tunnel et normalement à ce stade on doit pouvoir faire un ping sur l'IP LAN du routeur distant (notre serveur WireGuard) ainsi que les IP de son subnet pour peu que les routes inverses soient configurées.

Si sous Linux l'activation / désactivation d'un tunnel en CLI coule de source, il m'a fallut un peu chercher pour Windows. Mon but étant de permettre à une application de supervision d'éventuellement réactiver un tunnel cas de défaillance.

Donc pour activer un tunnel,  CMD en mode admin... (-h pour en savoir plus..) :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice "C:\Program Files\WireGuard\Data\Configurations\Nom du Tunnel.conf.dpapi"

Et pour le désactiver :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /uninstalltunnelservice "Nom du Tunnel"

Pour information et pour les afficionados, on peut très bien installer un serveur WireGuard sous Windows, les explications sont ici, ce n'est pas officiellement supporté et ça a l'air bien plus compliqué.

Sauf que dans le cas qui me préoccupe je ne peux justement pas disposer des routes inverses pour une question de sécurité chez mon client. La seule IP autorisée sera l'IP LAN du routeur. Il faut donc que les services que je doit joindre me reconnaissent avec cette IP. Et c'ets ici qu'interviennent les IPTables.

IPTables

Pour utiliser les IPTables il n'y a rien à installer car cela fait partie de l'O/S. Ca tombe bien car sur ce routeur la place est limitée. Par contre il faut que l'IP Forwarding soit activé, on peut vérifier avec sysctl net.ipv4.ip_forward qui va nous répondre net.ipv4.ip_forward = 0 ou 1 si c'est activé.

Ensuite, toujours en SSH : 

sudo iptables -F
sudo iptables -F -t nat
sudo echo 1 >| /proc/sys/net/ipv4/ip_forward  # En cas de besoin....
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
sudo iptables -t nat  -A POSTROUTING -j MASQUERADE

Depuis le client on va faire pointer nos requetés sur la première IP de WireGuard à laquelle j'ai associé un serveur web sur 192.168.150.20 et un serveur SMTP sur 192.168.169.22. A noter que si le port de destination est bien sur le port sur lequel répond le service, le port source peut lui être identique ou défini différemment (surtout qu'en 80 on a l'interface du routeur...).

Maintenant, depuis mon client, le serveur web de destination répondra sur http://192.168.33.1:8080 et si je fais un telnet 192.168.33.1 25 j'obtiendrait la mire de mon serveur SMTP. Et ces deux derniers ne verront en IP source que l'IP LAN de mon routeur, donc une IP autorisée.

Mais on ne peut pas tout à fait aller dîner... En effet ces IPTables vont disparaitre  au premier redémarrage !

Persistance

Mon premier réflexe était d'utiliser le paquet iptables-persistent. Sauf que j'ai remarqué, que pour une raison que je n'explique pas, mais qui a certainement sa logique, il est impossible d'ajouter une règle au firewall (tout au moins depuis l'interface du routeur) dès lors que ce paquet est activé. Je vais donc créer un script qui se lancera au démarrage du routeur et que je pourrais désactiver au besoin... (si un barbu passe par la merci de me dire sil y a mieux à faire).

Sous EdgeOS on a un emplacement spécifique pour les scripts devant s'exécuter après le démarrage du routeur et de ses services : /config/scripts/post-config.d

sudo vi /config/scripts/post-config.d/myfwd

Et dans ce fichier on va insérer nos commande  :

#!/bin/bash
iptables -F
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
iptables -t nat -A POSTROUTING -j MASQUERADE

Ensuite on va rendre ce script exécutable :

chmod +x /config/scripts/post-config.d/myfwd

Mais, car il y a toujours un mais. Il se peut que la résolution DNS ne se fasse pas au lancement. Donc si vous devez obtenir l’adresse IP à partir d’un nom de domaine, vous devez utiliser dig que vous obtiendrez avec :

sudo apt-get install dnsutils

Ensuite il faudra modifier légèrement le script...

sudo IP_ADDR=$(dig +short smtp.mondomaine.com| awk 'NR==1') 
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to $IP_ADDR::25

Firewall

Attention : dès lors que l'on ajoute ces IPTables il ne sera plus possible de modifier les règles du firewall depuis l'interface, et comme l'interface qui ne fait que lancer des lignes de CLI, idem en CLI. Afin de pouvoir créer ou modifier des règles il faudra temporairement désactiver ce script et redémarrer. En ce qui me concerne je l'ai fait sauvagement en renommant le répertoire qui le contient :

cd /config/scripts
sudo mv post-config.d post-config.d.off

Pour le reste on laisse par défaut, mais on va tout de même éviter de laisser des ports ouverts. On commence par désactiver le service Ubiquiti Device Discovery qui expose en UDP/TCP le port 10001 :

configure
set service ubnt-discover-server disable
commit ; save 

On pourrait désactiver l'interface GUI et le SSH depuis le WAN. Pour ça il est possible de forcer sur l'IP LAN :

set service gui listen-address 192.168.0.1
set service ssh listen-address 192.168.0.1

Mais en fait ça ne m'intéresse pas car je veux que ce soit accessible également via WireGuad, et comme après avoir installé IPTables on ne peu plus utiliser le firewall intégré on va continuer à se servir d'IPTables pour blinder le port WAN, ici eth4 :

# On commence par tout fermer sur le port WAN
sudo iptables -A INPUT -i eth4 -j DROP
# On accepte le port UDP de Wireguard (à noter qu'il est possible de le restreindre à une IP ou un subnet avec -s)
sudo iptables -I INPUT -i eth4 -p udp --dport 51833 -j ACCEPT
# On une IP spécifique sur les ports 22/80/443
sudo iptables -I INPUT -i eth4 -p tcp -m tcp -s 82.65.19.160 --dports 22,80,443 -j ACCEPT

On ajoute au fichier de configuration cité plus haut et on fait un scan (avec ça par exemple) sur l'IP WAN afin de constater la fermeture effective. Il ne reste que l'ICMP et WireGuard en UDP. Si on souhaite encore renforcer la sécurité on peut aussi restreindre l'ICMP et le port UDP de Wireguard avec des IP sources... Le même scan depuis un client WireGuard continuera à présenter les mêmes ports que l'interface LAN.

Dans le fichier ci dessous j'ai donc mes règles DNAT, ensuite je ferme tout sur sur port WAN (eth4) et j'autorise depuis mon IP publique WG en UDP sur le port 51833, en TCP 22, 80, 443 et enfin l'ICMP pour pouvoir monitorer.

#!/bin/bash
iptables -F
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.69.24:25
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 1430 -j DNAT --to 192.168.69.24:143
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.69.16:8000
iptables -t nat -A POSTROUTING -j MASQUERADE   

iptables -A INPUT -i eth4 -j DROP

iptables -I INPUT -i eth4 -p udp -s MyPublicIP --dport 51833 -j ACCEPT

iptables -I INPUT -i eth4 -p tcp -m multiport -s MyPublicIP --dports 22,80,443 -j ACCEPT

iptables -I INPUT -i eth4 -p icmp -s MyPublicIP -j ACCEPT

Et vu que je ne suis pas un grand fan de VI, le plus simple est de préparer le fichier en local, de l'uploader dans le répertoire utilisateur avec un SFTP graphique, et ensuite de le déplacer dans le répertoire idoine :

sudo mv myfwd /config/scripts/post-config.d/

Et de le rendre exécutable :

chmod +x /config/scripts/post-config.d/myfwd

Et pour vérifier les règles :

sudo iptables -L --line-numbers

Pour en savoir plus sur ces règles c'est ici, et vous allez trouver une petite bible dédiée aux EdgeRouter, et notamment pour tout ce qui concerne leur sécurité.

Voilà !

EDIT 25/09/2021: La bonne nouvelle c'es que Free a ajouté WireGuard dans ses Freebox et que ça fait tout le travail... Quand on a une Freebox bien sûr !

EDIT 04/11/2021: Ajustement du script pour EdgeOS.

EDIT 05/10/2022 : Ajustement IPTables pour le Firewall

Sources