Aller au contenu
Sylvain Joffraud l4w
Retour

ACL par IP du VPN : le split-tunnel sort par la box

Une ACL par IP du VPN derrière un split-tunnel ne voit pas l’IP du VPN : le nom public du service résout vers l’IP publique du serveur, et comme seul le /24 du mesh passe dans le tunnel, la requête sort par la box. Apache reçoit l’IP publique de la maison, l’ACL répond 403, alors que le tunnel est bien monté. Le correctif est côté DNS : faire résoudre ces noms vers l’IP mesh du serveur, et seulement eux, quand le tunnel est actif.

Le contexte : des Studios Supabase réservés au mesh

Depuis le 25 septembre 2026, les Studios Supabase auto-hébergés sur srv-a ne sont plus derrière une Basic Auth : ils sont réservés aux postes admin du mesh WireGuard. Le pourquoi (le Studio n’a pas d’authentification propre, et Kong l’expose aussi sur l’hôte API) est détaillé dans l’article sur le Studio Supabase exposé via Kong.

La règle Apache tient dans un snippet inclus par chaque vhost de Studio :

# /etc/apache2/supabase-acl-studio.conf
Require ip 10.8.0.2 10.8.0.3

10.8.0.2 est mon PC en split-tunnel (wg-srv-s), 10.8.0.3 le même PC en full-tunnel (wg-srv-f). Depuis internet : 403, c’est voulu.

Pourquoi l’ACL par IP du VPN renvoie 403 en split-tunnel ?

Un split-tunnel est un VPN qui ne route que certains sous-réseaux, ici AllowedIPs = 10.8.0.0/24. Tout le reste, y compris l’IP publique de srv-a, part par la route par défaut de la machine, donc par la box.

Le nom supabase.dev.example.com est public : il résout vers 203.0.113.10. Le trajet est alors :

  1. le navigateur résout le nom → 203.0.113.10 ;
  2. 203.0.113.10 n’est pas dans 10.8.0.0/24 → pas de tunnel ;
  3. la requête sort par la box, Apache voit 198.51.100.20 ;
  4. 198.51.100.20 n’est pas dans la liste → 403.

Une ACL par IP de VPN ne contrôle pas « qui a le tunnel monté », mais « par où est arrivé le paquet » : en split-tunnel, c’est le DNS qui décide.

Le full-tunnel n’a pas le problème : tout passe dans le tunnel, y compris vers l’IP publique, et Apache voit 10.8.0.3. C’est le profil du quotidien, le split, qui coince.

Le correctif : rewrite DNS et routing domains

Deux pièces, une côté serveur, une côté poste.

Côté serveur, un AdGuard Home écoute sur l’IP mesh de srv-a (10.8.0.1, interface web sur :8080). Dans Filters → DNS rewrites, chaque nom de Studio est réécrit vers 10.8.0.1. Interrogé par le mesh, AdGuard répond l’IP mesh, et la requête HTTP passe dans le tunnel.

Côté poste, il faut que seuls ces noms soient résolus par AdGuard quand le tunnel est monté, sans faire passer toute la résolution du PC par le VPN. C’est le rôle des routing domains de systemd-resolved : un domaine préfixé par ~ envoie ses requêtes vers le DNS d’une interface donnée, sans devenir un domaine de recherche.

nmcli connection modify wg-srv-s ipv4.dns 10.8.0.1 \
  ipv4.dns-search "~supabase.dev.example.com,~supabase-dev.dev.example.com,~supabase.app.example.net"
nmcli connection up wg-srv-s
resolvectl domain wg-srv-s   # vérifier

Ajouter un Studio, c’est donc trois endroits : le vhost Apache avec le snippet, le rewrite AdGuard, et le routing domain de wg-srv-s.

Vérifier avant d’accuser Apache

Le premier contrôle n’est pas dans les logs Apache, il est dans la résolution :

resolvectl query supabase.dev.example.com   # doit répondre 10.8.0.1
curl -so/dev/null -w '%{http_code}' https://supabase.dev.example.com/

Depuis internet, curl renvoie 403. Tunnel monté et nom résolu en 10.8.0.1, il renvoie 307 : la requête a passé l’ACL. Si resolvectl query répond l’IP publique, le 403 est normal : le routing domain manque ou le tunnel est descendu.

Ce piège est cousin de celui des deux VPN sur le même /24 : dans les deux cas, la bonne question est « par quelle interface sort ce paquet ? », pas « le VPN est-il connecté ? ».

Lessons learned

Questions fréquentes

Pourquoi j’ai un 403 sur un site restreint aux IP du VPN alors que mon VPN est connecté ?
Avec un VPN en split-tunnel, seul le sous-réseau du VPN passe dans le tunnel. Si le nom du site résout vers son IP publique, la requête part par la connexion normale et le serveur voit l’IP publique de la box, pas l’IP du VPN. L’ACL Require ip refuse donc la requête avec un 403, alors que le tunnel est bien monté.
Comment faire passer un seul domaine par le DNS d’un VPN WireGuard avec NetworkManager ?
On déclare des routing domains sur la connexion VPN : nmcli connection modify wg-srv-s ipv4.dns 10.8.0.1 ipv4.dns-search "~supabase.dev.example.com". Le préfixe ~ indique à systemd-resolved d’envoyer uniquement les requêtes de ce domaine au DNS du tunnel, sans en faire un domaine de recherche. Le reste de la résolution continue d’utiliser le DNS habituel.
Comment vérifier quelle IP un nom résout quand le VPN est monté ?
La commande resolvectl query supabase.dev.example.com affiche l’adresse obtenue et l’interface utilisée pour la résoudre. Pour un service restreint au mesh, elle doit répondre l’IP mesh du serveur, ici 10.8.0.1. resolvectl domain wg-srv-s liste les routing domains effectivement associés à l’interface du tunnel.
Un full-tunnel a-t-il le même problème d’IP source qu’un split-tunnel ?
Non. En full-tunnel, tout le trafic passe dans le tunnel, y compris celui destiné à l’IP publique du serveur. Le serveur voit donc l’IP du client dans le VPN et l’ACL par IP fonctionne sans réglage DNS. Le problème est propre au split-tunnel, qui ne route que le sous-réseau du VPN.

Partager cet article :

Article précédent
RLS Postgres : le `exists` filtré par une autre policy
Article suivant
z-index 10000 : menus MUI vides dans un Drawer