Mode opératoire — Pangolin : donner accès à une infrastructure sans rien ouvrir chez elle
Document autonome. Partie théorique, puis mise en œuvre telle qu’elle a été réalisée et vérifiée dans le TP Lab pfSense (phase 12 du suivi). La méthode vaut pour toute infrastructure dont on ne maîtrise pas le réseau d’entrée. Le bastion publié en exemple est celui du MO-7 Termix ; l’alternative écartée est le MO-8 Tailscale.
Valeurs masquées.
<domaine>= un nom de domaine que l’on possède ;<IP-publique>= l’adresse publique de la connexion Internet où se trouve le serveur ;<IP-CT>= l’adresse locale du conteneur Pangolin ;<ID>et<SECRET>= les identifiants du connecteur. Aucun secret n’est écrit ici.Dernière mise à jour : 28/10/2026 — ajout du §10.9 (laisser passer
/api/healthpour la supervision, régler les contrôles de santé) État au 12/11/2026 : la phase 12 (accès externe) est arrêtée depuis le 05/11 : l’accès Termix du formateur suffit ; tunnel et ressources d’essai laissés tels quels (dette T-12). Depuis le 10/11, le tunnel publie aussi le site public de GSB (gsb.pangolin.<domaine>, phase 18, voir MO-24). Une ressource (Wazuh) a été ajoutée le 03/11.
Sommaire
| § | Contenu |
|---|---|
| 1 | Le besoin : ouvrir un accès externe à un lab que personne ne peut joindre |
| 2 | Ce qu’est Pangolin : trois composants, quatre notions |
| 3 | L’architecture retenue, et pourquoi le serveur est chez moi |
| 4 | Prérequis à vérifier avant de commencer |
| 5 | Installer le serveur et créer le compte administrateur |
| 6 | Le DNS : un wildcard pour ne plus y toucher |
| 7 | Nginx Proxy Manager devant Pangolin : les WebSockets, le certificat joker et l’entrée joker |
| 8 | Les redirections de port : le tunnel UDP 51820 |
| 9 | Installer le connecteur (le « site ») dans le lab |
| 10 | Publier une ressource, vérifier sa protection, les exemples pfSense (noms alternatifs) et statistiques HAProxy (règle étroite, contrôle de santé), les utilisateurs et rôles, la liste des ressources, et le chemin /api/health laissé passer pour la supervision |
| 11 | Corriger le « domaine de base » |
| 12 | Sécurité : ce que le tunnel donne, et ce qu’il ne faut pas publier |
| 13 | Diagnostiquer : la méthode qui a servi, et les pièges |
| 14 | Checklist |
1 · Le besoin : ouvrir un accès externe à un lab que personne ne peut joindre
1.1 La situation de départ
Le laboratoire tourne sur l’hyperviseur de l’école. Il est derrière un réseau que l’on ne maîtrise pas : aucune redirection de port possible, donc aucun service du lab n’est joignable depuis Internet. Des personnes extérieures (le formateur, puis les examinateurs) peuvent avoir besoin de le voir fonctionner, voire d’y agir : le formateur a indiqué qu’un accès avec possibilité de modifier est attendu, quoi exactement reste à confirmer par écrit.
1.2 Les contraintes qui ont décidé du choix
| Contrainte | Conséquence |
|---|---|
| Budget nul | aucun service payant |
| Partageable avec des personnes extérieures | Tailscale écarté : le tailnet donne l’accès à tous les VLAN (dette T‑6) |
| Accès par ressource, pas par réseau | un invité doit voir une machine, pas tout le lab |
| Aucun port à ouvrir côté lab | le lab ne peut pas recevoir : il doit sortir |
Même renversement que pour Tailscale : un réseau laisse toujours sortir. Plutôt que d’attendre une connexion entrante, la machine du lab ouvre un tunnel sortant vers un point de rendez-vous, et les visiteurs arrivent par ce point de rendez-vous.
2 · Ce qu’est Pangolin : trois composants, quatre notions
Pangolin est un reverse proxy à tunnel : il publie sur Internet des applications qui se trouvent derrière un réseau fermé, sans qu’on ouvre quoi que ce soit côté application. Il est open source (AGPL-3 pour l’édition communautaire), et existe aussi en offre hébergée.
2.1 Les trois composants du serveur (installés ensemble)
| Composant | Rôle |
|---|---|
| Pangolin | interface d’administration, comptes, règles d’accès, API |
| Gerbil | termine les tunnels WireGuard (UDP) venant des sites |
| Traefik | le reverse proxy HTTPS qui reçoit les visiteurs et les oriente vers la bonne ressource |
Versions installées lors du TP : Pangolin 1.24.0, Gerbil 1.5.2, Traefik 3.7.14.
2.2 Les quatre notions de l’interface
| Notion | Ce que c’est |
|---|---|
| Organisation | le conteneur de tout le reste (utilisateurs, sites, ressources) |
| Site (ou « nœud ») | un réseau distant relié au serveur. Matériellement, un connecteur installé dedans qui ouvre le tunnel |
| Ressource publique | un service web publié sous un nom de domaine, protégé par l’authentification de Pangolin |
| Cible | l’adresse réelle (IP, port, protocole) vers laquelle la ressource renvoie, vue depuis le site |
Trois types de site sont proposés à la création : Site Newt (recommandé : un petit programme fait le tunnel), WireGuard basique (n’importe quel client WireGuard, avec une configuration NAT manuelle), Locale (aucun tunnel, ressources locales seulement).
3 · L’architecture retenue, et pourquoi le serveur est chez moi
L’auto-hébergement du serveur exige une adresse publique joignable. Le lab n’en a pas (réseau de l’école). Le serveur Pangolin est donc installé sur l’hyperviseur personnel, derrière une connexion Internet qui, elle, peut recevoir des connexions. Le lab, de son côté, ne fait que sortir vers lui.
Visiteur CHEZ MOI LE LAB (école)
(externe)
│ https://termix.pangolin.<domaine>
▼
DNS Cloudflare ──► <IP-publique>
│ TCP 80 / 443
▼
box → routeur personnel → Nginx Proxy Manager
│ https → <IP-CT>:443
▼
┌──────────────────────────┐ UDP 51820 ┌──────────────────┐
│ CT Pangolin │◄══ tunnel WireGuard ═══ │ srv-docker │
│ Pangolin + Gerbil + │ ouvert DEPUIS le lab │ conteneur │
│ Traefik │ │ « pangolin-site » │
└──────────────────────────┘ │ │ │
│ ▼ │
│ Termix 10.0.30.12│
└──────────────────┘
| Flux | Sens | Protocole |
|---|---|---|
| Visiteur → serveur Pangolin | entrant, chez moi | TCP 443 (et 80 pour les certificats) |
| Lab → serveur Pangolin (le tunnel) | sortant depuis le lab | UDP 51820 |
| Serveur → application du lab | à travers le tunnel | HTTP, vers l’IP du lab |
Rien n’est ouvert côté lab. Seul le serveur, chez moi, reçoit des connexions entrantes. Le pare-feu du lab garde donc « zéro règle entrante » sur le WAN.
Pourquoi un Nginx Proxy Manager devant Pangolin ? Parce que, chez moi, les ports 80 et 443 de la connexion étaient déjà utilisés par ce reverse proxy pour d’autres services. C’est un contournement propre à ma situation, expliqué au §7.
4 · Prérequis à vérifier avant de commencer
| Prérequis | Comment le vérifier | Résultat obtenu au TP |
|---|---|---|
| Un nom de domaine que l’on gère | accès à la zone DNS | domaine géré chez Cloudflare |
| Une adresse publique (pas de CGNAT) | des services sont-ils déjà joignables de l’extérieur par redirection de port ? | oui |
| Une adresse qui change : un script de mise à jour DNS | le nom suit-il la nouvelle adresse ? | un script met à jour l’enregistrement racine |
| Pouvoir rediriger TCP 80, 443 et UDP 51820 sur la box | interface d’administration de la box | après correction, voir §8 |
| UDP sortant depuis le lab vers l’extérieur | test de capture, §13.3 | le tunnel s’est établi depuis l’école |
| Un Docker sur une machine du lab | docker --version && docker compose version | Docker 29.x / Compose 5.x |
Ce qui n’a pas été vérifié : le règlement de l’école sur un tunnel sortant depuis son hyperviseur. La question au formateur a été abandonnée sur décision d’Axel le 14/10 : elle n’a été ni posée ni tranchée (voir le suivi, phase 12). Le point reste donc ouvert, pas autorisé.
5 · Installer le serveur et créer le compte administrateur
5.1 Le serveur
Sur l’hyperviseur personnel, le serveur a été installé avec un script communautaire (Proxmox VE Helper-Scripts) qui crée un conteneur LXC et y déploie Pangolin, Gerbil et Traefik.
Ce script n’est pas celui de l’éditeur de Pangolin et s’exécute en administrateur sur l’hyperviseur : à choisir en connaissance de cause. L’éditeur documente une installation officielle par Docker, que je n’ai pas utilisée.
À une étape, le script demande :
Enter your Pangolin URL (ex: https://pangolin.example.com):
C’est l’adresse web du tableau de bord, sous un nom de domaine que l’on possède (par exemple
https://pangolin.<domaine>). Je l’ai saisie avec https://, et le script l’a recopiée à un endroit où
elle gênera plus tard : voir §11.
5.2 Le jeton de configuration
À la première ouverture du tableau de bord, Pangolin demande un jeton de configuration : un code à usage unique affiché dans la console du serveur. Il prouve que celui qui crée le compte administre bien la machine.
Dans le shell de l’hyperviseur, entrer dans le conteneur (le numéro est celui de ton conteneur) :
pct enter 106
Puis lire le journal du service :
journalctl -u pangolin --no-pager | grep -i -B2 -A5 token
Une ligne Token: … apparaît. Saisir ce jeton dans la page, avec une adresse mail et un mot de passe
fort distinct de ceux du lab. Ce compte est le seul administrateur du serveur.
Les trois lignes
Failed to fetch server IP … self-signed certificatequi suivent dans le journal concernent la recherche de l’adresse publique du serveur : elles n’ont pas empêché la création du compte ni le tunnel.
5.3 Organisation et site
L’assistant demande ensuite, dans l’ordre : une organisation (un nom d’affichage et un identifiant technique sans espace), puis un nœud (le site), puis des ressources. Les deux dernières étapes sont détaillées aux §9 et §10.
Validation : après connexion, l’écran « Nouvelle organisation » (ou le tableau de bord) s’affiche.
6 · Le DNS : un wildcard pour ne plus y toucher
Chaque ressource aura son propre nom (termix.pangolin.<domaine>, pfsense.pangolin.<domaine>…). Plutôt
que de créer un enregistrement à chaque fois, un seul enregistrement joker suffit.
6.1 Ce que j’avais déjà
Dans la zone, un seul enregistrement A (la racine, mis à jour automatiquement quand l’adresse change) et
des CNAME vers cette racine pour chaque service. Le script d’adresse dynamique n’a donc jamais à
connaître les nouveaux noms.
6.2 L’enregistrement à créer
Dans le DNS (Cloudflare) → Ajouter un enregistrement :
| Champ | Valeur |
|---|---|
| Type | CNAME |
| Nom | *.pangolin |
| Destination | <domaine> (la racine) |
| État du proxy | DNS uniquement (nuage gris) |
| TTL | Automatique |
« DNS uniquement », pas « Proxied » pour les noms de Pangolin : le proxy de Cloudflare ne transporte que du trafic web. Le tunnel UDP de Pangolin n’y passerait pas. (Connaissance générale ; ce que j’ai constaté, c’est que le nom du serveur se résolvait bien en mon adresse réelle.)
6.3 Le tableau de bord a son propre nom
Le wildcard *.pangolin ne couvre pas pangolin.<domaine> lui-même : il lui faut son enregistrement
propre (pangolin → <domaine>, DNS uniquement).
Validation, sur un poste :
Resolve-DnsName termix.pangolin.<domaine> -Type A | Select-Object Name,Type,IPAddress
Elle renvoie <IP-publique>, pas une adresse du fournisseur DNS.
7 · Nginx Proxy Manager devant Pangolin : WebSockets, certificat joker, entrée joker
7.1 Pourquoi cet étage
Chez moi, un Nginx Proxy Manager (NPM) tient déjà les ports 80 et 443 de la connexion. Le Traefik de Pangolin en a besoin aussi. Je n’ai pas voulu toucher à NPM (ses services actuels devaient continuer à fonctionner), donc :
visiteur → NPM (80/443 de la box) → Traefik de Pangolin → tunnel → application
NPM ne fait ici que passer : il présente le certificat au navigateur, puis renvoie vers le conteneur.
7.2 L’entrée à créer, une par nom (la variante sans entrée par nom est au §7.3)
Dans NPM → Proxy Hosts → Add Proxy Host :
| Champ | Valeur |
|---|---|
| Domain Names | pangolin.<domaine> (puis un par ressource : termix.pangolin.<domaine>…) |
| Scheme | https |
| Forward Hostname / IP | <IP-CT> |
| Forward Port | 443 (le port de Traefik) |
| Websockets Support | activé |
| Onglet SSL | nouveau certificat Let’s Encrypt, Force SSL activé |
Ne pas viser le port 3002. C’est l’interface web de Pangolin seulement. Les appels à l’API
(/api/…) vont sur un autre port. Seul Traefik (443) sait les répartir. Viser 3002 donne l’écran de
connexion… puis l’erreur Request failed with status code 404 à la création du compte.
Websockets Support doit être activé. Le connecteur communique avec le serveur par un WebSocket. Sans
cette option, NPM ne transmet pas la « mise à niveau » du protocole et le connecteur boucle sur
websocket: bad handshake (voir §13).
Le certificat de Pangolin affiche « Pending » pour ces noms : normal. C’est NPM qui présente le certificat au navigateur, Let’s Encrypt ne pouvant pas valider le Traefik qui est derrière lui.
7.3 Ne plus créer d’entrée par nom : le certificat joker et l’entrée joker
Avec une entrée NPM par nom (§7.2), il faut en ajouter une à chaque ressource publiée. Pangolin, lui,
route déjà tout seul dès qu’une ressource est créée. On peut donc remplacer toutes les entrées par une
seule, valable pour tout nom qui finit par .pangolin.<domaine>. Il faut pour cela un certificat joker.
Le DNS est déjà « joker » (§6 :
*.pangolin→ racine). Il reste à rendre NPM joker aussi : un certificat qui couvre tous les noms, puis une entrée qui les accepte tous.
7.3.1 Un jeton d’API Cloudflare dédié
Un certificat joker ne peut s’obtenir que par validation DNS : Let’s Encrypt vérifie que l’on contrôle la zone en y faisant écrire un enregistrement temporaire. NPM a donc besoin d’un droit d’écriture sur le DNS.
Dans Cloudflare → Mon profil → Jetons d’API → Créer un jeton, modèle « Modifier le DNS de la zone » :
| Champ | Valeur |
|---|---|
| Nom | un nom qui dit à quoi il sert (par exemple npm-certificat-joker-pangolin) |
| Permission | Zone → DNS → Modifier |
| Ressources de la zone | la seule zone du domaine, pas toutes les zones du compte |
| Filtrage d’adresse IP | vide (un filtre casserait le renouvellement si l’adresse change) |
| TTL (durée de vie du jeton) | vide (un jeton qui expire ferait échouer un renouvellement sans prévenir) |
Le jeton est affiché une seule fois : le copier directement dans un gestionnaire de mots de passe. Il ne va ni dans une conversation, ni dans une capture, ni dans Git. Un jeton dédié : ne pas réutiliser celui d’un autre script.
NPM le stocke en clair dans sa base et dans un fichier (l’écran l’indique en rouge). C’est la raison du périmètre minimal : s’il fuit, il ne peut modifier que le DNS d’un seul domaine.
7.3.2 Le certificat joker
NPM → Certificates → Add Certificate → Let’s Encrypt via DNS :
| Champ | Valeur |
|---|---|
| Domain Names | *.pangolin.<domaine> |
| Key Type | ECDSA 256 (le défaut) |
| DNS Provider | Cloudflare |
| Credentials File Content | dns_cloudflare_api_token=<jeton> (sans espace autour du =) |
| Propagation Seconds | vide ; mettre 60 si la validation échoue |
Piège de saisie : le champ affiche un texte d’exemple (
dns_cloudflare_api_token=0123456789abcdef…). Il faut le remplacer, pas le laisser. Copier le jeton avec le bouton de copie plutôt que le retaper.
Un joker ne couvre qu’UN seul niveau. *.<domaine> couvre grafana.<domaine> mais pas
termix.pangolin.<domaine> (deux niveaux). Il faut donc bien un certificat pour *.pangolin.<domaine>,
même si un certificat *.<domaine> existe déjà.
Ce joker ne couvre pas non plus
pangolin.<domaine>lui-même (le tableau de bord) : celui-ci garde son entrée et son certificat propres.
Validation : le certificat apparaît dans la liste, fournisseur « Let’s Encrypt – cloudflare », avec une date d’expiration (trois mois plus tard). Son statut est « Not Used » tant qu’aucune entrée ne l’emploie : c’est normal.
7.3.3 L’entrée joker
NPM → Hosts → Proxy Hosts → Add Proxy Host :
| Champ | Valeur |
|---|---|
| Domain Names | *.pangolin.<domaine> |
| Scheme | https |
| Forward Hostname / IP | <IP-CT> |
| Forward Port | 443 |
| Websockets Support | activé |
| Onglet SSL → SSL Certificate | le certificat *.pangolin.<domaine> |
| Force SSL | activé |
7.3.4 Le test qui prouve l’automatisation
- Désactiver (et non supprimer) l’entrée du nom précis (
termix.pangolin.<domaine>) : c’est le joker qui doit répondre à sa place. L’URL affiche toujours l’écran d’authentification de Pangolin . - Le vrai test : créer dans Pangolin une ressource avec un sous-domaine jamais utilisé, puis ouvrir son URL sans avoir créé d’entrée NPM. La page doit répondre.
Quand plusieurs entrées correspondent, nginx retient le nom exact avant le joker. Les entrées par nom restent donc prioritaires tant qu’elles existent : voilà pourquoi on les désactive pour tester. Une fois le joker éprouvé, elles peuvent être supprimées.
Validation : une ressource créée uniquement dans Pangolin, sans toucher à NPM, répond. C’est le cas sur le lab avec l’interface de pfSense (§10.5).
8 · Les redirections de port : le tunnel UDP 51820
8.1 Ce qui doit arriver jusqu’au conteneur
Le tunnel WireGuard du connecteur aboutit en UDP 51820 sur le conteneur Pangolin (Gerbil). Il faut donc, sur chaque équipement de la chaîne entre Internet et le conteneur, une redirection :
| Équipement | Règle |
|---|---|
| Box de l’opérateur | UDP 51820 → le routeur personnel |
| Routeur personnel | UDP 51820 → <IP-CT> port 51820 |
Vérifier que le conteneur écoute :
ss -ulnp | grep 51820
Deux lignes (IPv4 et IPv6) en UNCONN, sur le port 51820.
8.2 L’erreur qui a coûté la soirée
J’avais deux équipements en chaîne et une zone DMZ sur la box. Je pensais que la DMZ envoyait tout le trafic au routeur personnel. En réalité, elle visait un autre équipement (un routeur pas encore déployé), donc les paquets n’atteignaient jamais le routeur de l’infrastructure.
Une règle de redirection explicite vers le bon équipement vaut mieux qu’une DMZ supposée. Et on vérifie quel équipement répond réellement à l’adresse visée (page de statut, liste des appareils), on ne se fie pas à ce que l’on se rappelle de sa propre configuration.
9 · Installer le connecteur (le « site ») dans le lab
9.1 Créer le nœud
Dans Pangolin → Créer un nœud : type Site Newt, un nom (lab-ecole dans mon cas). L’écran affiche
alors trois valeurs : le point de terminaison, l’ID et le secret, puis une commande
d’installation avec le choix du système (Docker, Linux, Windows…).
Le secret ne se partage pas : ni dans une conversation, ni dans une capture du dossier de preuves, ni dans Git. S’il a été exposé, supprimer et recréer le nœud.
9.2 Installer avec Docker Compose, sans le secret dans le fichier
Sur la machine du lab (ici le bastion), dans un dossier dédié :
mkdir -p ~/newt && cd ~/newt
Le fichier compose.yml ne contient aucun secret :
services:
pangolin-site:
image: fosrl/pangolin-cli
container_name: pangolin-site
restart: unless-stopped
env_file: newt.env
Le fichier newt.env porte les trois variables, avec les valeurs copiées depuis l’écran :
PANGOLIN_ENDPOINT=https://pangolin.<domaine>
SITE_ID=<ID>
SITE_SECRET=<SECRET>
Droits restreints, puis vérification sans afficher le secret :
chmod 600 newt.env && ls -l newt.env
sed 's/=.*/=…/' newt.env
-rw-------, et trois lignes avec les trois noms de variables.
Piège de collage : copier chaque valeur avec le bouton « copier » de l’écran évite les fautes de frappe sur le secret. Un espace ou un retour à la ligne parasite fait échouer l’authentification.
9.3 Lancer et lire les journaux
docker compose up -d && docker logs -f pangolin-site
(Ctrl+C ferme l’affichage ; le conteneur continue.)
Validation : dans les journaux, la ligne
Tunnel connection to server established successfully!
Sans Ping attempt … failed ensuite. « En ligne » dans le tableau de bord seul ne prouve pas le
tunnel : cet état ne reflète que le WebSocket (§13.2).
10 · Publier une ressource, vérifier sa protection, exemples (pfSense, HAProxy), utilisateurs et rôles
10.1 Connaître le protocole de l’application, avant de la déclarer
Depuis la machine du lab (ici Termix, publié sur le port 443 de l’hôte) :
echo "--- HTTP ---"; curl -sI --max-time 5 http://10.0.30.12:443 | head -1
echo "--- HTTPS ---"; curl -skI --max-time 5 https://10.0.30.12:443 | head -1
Résultat chez moi : une réponse HTTP/1.1 200 OK pour http, rien pour https. La cible doit donc
être déclarée en http. Se tromper donne une erreur 502 dont la cause est longue à trouver.
10.2 Créer la ressource
Ressources → Ajouter une ressource (publique) :
| Champ | Valeur |
|---|---|
| Type | HTTP (ne peut pas être modifié ensuite) |
| Nom | Termix-MDS |
| Sous-domaine | termix |
| Domaine de base | pangolin.<domaine> |
| Cible : nœud | le site créé au §9 |
| Cible : adresse | 10.0.30.12, port 443, protocole http |
L’URL affichée doit être https://termix.pangolin.<domaine>, avec un seul https://.
10.3 Ajouter l’entrée NPM de ce nom
Voir §7.2 (même modèle que pour le tableau de bord).
10.4 Tester, et surtout tester la protection
- Ouvrir l’URL : la page de l’application s’affiche (le circuit complet est prouvé).
- Dans une fenêtre de navigation privée (ou un téléphone en 4G) : la page doit être « Authentification requise », celle de Pangolin, pas celle de l’application.
Le premier test ne prouve rien sur la sécurité : le navigateur de l’administrateur est déjà connecté à Pangolin et passe sans demande. Seul le second test montre qu’un visiteur anonyme est arrêté.
10.5 Exemple : publier l’interface de pfSense — et les noms alternatifs
Deuxième ressource du lab, créée sans aucune entrée NPM (grâce au joker du §7.3).
Le protocole de la cible
Depuis la machine du lab qui porte le connecteur (une commande par bloc) :
echo "--- HTTPS ---"; curl -skI --max-time 5 https://10.0.30.1 | head -1
echo "--- HTTP ---"; curl -sI --max-time 5 http://10.0.30.1 | head -1
HTTP/2 200 pour l’HTTPS, rien pour l’HTTP : la cible est en https. (-k ignore la vérification du
certificat auto-signé de pfSense : ce test ne dit pas si Pangolin l’acceptera.)
La ressource
| Champ | Valeur |
|---|---|
| Type | HTTP |
| Nom | pfSense-Lab |
| Sous-domaine | pfsense |
| Domaine de base | pangolin.<domaine> |
| Cible | nœud du lab, https, 10.0.30.1, port 443 |
Aucune règle de pare-feu à ajouter : le connecteur est dans le VLAN d’administration, dont la règle n°1 autorise déjà l’accès à l’interface de pfSense (443 et 22).
Le message rouge : « Potential DNS Rebind attack detected »
Ouvrir https://pfsense.pangolin.<domaine> affiche d’abord, sur fond rouge :
Potential DNS Rebind attack detected, see https://en.wikipedia.org/wiki/DNS_rebinding
Try accessing the router by IP address instead of by hostname.
Ce message est une bonne nouvelle. Il est émis par pfSense lui-même : le certificat joker, l’entrée joker, Pangolin, le tunnel et la cible ont donc tous fonctionné. Ce qui refuse, c’est l’application visée.
Cause : pfSense ne sert son interface qu’aux noms qu’il connaît. Un nom inconnu est traité comme une possible attaque par DNS rebinding : un site hostile ferait pointer son propre nom vers l’adresse privée du pare-feu pour joindre son interface depuis le navigateur d’un administrateur.
Correction : déclarer ce nom à pfSense. Se connecter à pfSense par son adresse IP habituelle, puis :
| Où | Valeur |
|---|---|
| System → Advanced → onglet Admin Access | |
| Champ Alternate Hostnames | pfsense.pangolin.<domaine> |
| Bouton | Save |
Ne pas cocher « Disable DNS Rebinding Checks » : cela supprime la protection pour tous les noms.
Validation : recharger l’URL : l’écran de connexion de pfSense s’affiche. En navigation privée, l’écran d’authentification de Pangolin vient d’abord.
Non vérifié : seul l’écran de connexion a été vu. Le comportement après connexion (vérification de l’en-tête
Referer) reste à éprouver.
On publie l’administration du pare-feu. À traiter comme un essai : aucun compte externe sur cette ressource ; deux barrières (authentification de Pangolin, puis mot de passe de pfSense) ; à retirer à la fin (ressource et nom alternatif). Voir la documentation du lab, étape 41 et l’incident H-17.
10.6 Exemple : la page de statistiques de HAProxy — une règle étroite, et le contrôle de santé
Troisième ressource du lab. Elle illustre deux pièges que les deux premières n’ont pas montrés.
La cible n’est pas joignable par le connecteur
Depuis la machine du connecteur, le test reste sans réponse :
curl -sI --max-time 5 http://10.0.10.5:8404/ | head -1
Le pare-feu du lab n’autorise pas ce flux (le connecteur n’a le droit d’atteindre les autres VLAN que sur SSH et RDP). Il faut une règle, et on la fait la plus étroite possible : l’hôte source, l’hôte cible, un seul port. Un alias nomme le port (convention « un alias = un usage ») :
| Dans pfSense | Valeur |
|---|---|
| Firewall → Aliases → Ports | PORTS_HA_STATS = 8404 ; Save, Apply Changes |
| Firewall → Rules → onglet du VLAN du connecteur | Pass, TCP, source = machine du connecteur, destination = la machine HAProxy, port = PORTS_HA_STATS, au-dessus de la règle de refus |
Ne pas élargir la règle existante du bastion à tous les réseaux du lab pour « faire passer » la page : le tunnel hériterait de tout. Voir §12.
Validation : le même curl répond HTTP/1.1 401 Unauthorized. Le 401 est la bonne nouvelle : la page
est atteinte et demande son propre identifiant.
La ressource, et « no available server »
Ressource HTTP, cible http vers l’adresse et le port de la page. Elle peut afficher « no available
server » : voir l’incident H-18 de la documentation du lab. Le contrôle de santé de Pangolin reçoit le
401, juge la cible malade, et le proxy ne lui envoie plus personne.
| Remède | Effet |
|---|---|
| Retirer le contrôle de santé de cette cible (essayé, a fonctionné) | la page s’affiche ; Pangolin ne détecte plus une vraie panne de cette cible |
Régler le contrôle pour qu’il accepte 401 | non essayé : dépend des options de l’interface |
La cible est protégée deux fois : par Pangolin, puis par son propre mot de passe. Ne pas copier ce mot de passe dans la configuration de Pangolin (en-tête d’autorisation) sans raison : il y serait stocké en clair pour tout administrateur du serveur.
10.7 Utilisateurs, rôles et « Valide pour » — ce qui est établi, et ce qui ne l’est pas
Pour donner un accès externe nominatif, on crée un utilisateur dans Pangolin (menu des utilisateurs → Créer un utilisateur). L’écran demande trois choses :
| Champ | Sens |
|---|---|
| Adresse mail | l’identité du futur utilisateur |
| Valide pour (3 jours par défaut, plusieurs durées proposées) | la validité de l’invitation, c’est-à-dire du lien envoyé : le délai pour l’accepter et créer le compte |
| Rôles | « Cette édition ne prend en charge qu’un rôle par utilisateur » |
Établi (écran, documentation officielle de l’édition gratuite) :
- l’accès à une ressource se règle par rôle : sur chaque ressource, on définit quels rôles y accèdent ; un rôle par utilisateur suffit donc pour « un externe = une ressource » ;
- plusieurs rôles par utilisateur sont réservés à l’édition Enterprise ;
- la durée de session (« une session inutilisée expire après 30 jours ») ne concerne que la reconnexion ; son réglage est une fonction Enterprise.
Non établi :
- la documentation ne dit pas si un compte expire ni combien dure exactement une invitation : « Valide pour » est très probablement le délai d’acceptation de l’invitation, pas la durée de vie du compte. À vérifier en créant un compte de test et en regardant la liste des utilisateurs ;
- des utilisateurs signalent des invitations qui s’affichent « expirées » dès le premier clic, quelle que soit la durée choisie : à tester sur sa version.
Ce qu’apporte Enterprise, pour ce besoin : surtout la traçabilité (journaux d’authentification, de session et d’actions d’administration, conservation plus longue). La limitation d’un externe à une ressource n’en dépend pas. La licence personnelle est gratuite mais demande une clé ; les établissements d’enseignement ne sont pas assimilés à un usage personnel : lire les conditions avant de la demander. L’activation décrite par l’éditeur passe par une image Docker
ee: la marche à suivre pour une installation par script n’est pas établie.
Une ressource sans la protection de Pangolin est ouverte à tout Internet. Termix est resté un temps dans ce cas : les compensations retenues étaient la 2FA sur le compte et les inscriptions désactivées (l’écran de connexion n’a plus d’onglet de création de compte, vu le 14/10). Il a depuis été remis en « Protégé » (relevé de la liste des ressources, 14/10). Cette combinaison reste le minimum à avoir si la protection est un jour retirée ; elle ne doit pas être appliquée à l’administration du pare-feu.
10.8 Les ressources publiées sur le lab, au 14/10/2026
Relevé de la liste des ressources de Pangolin (toutes derrière l’authentification de la plateforme, sauf mention contraire).
| Ressource | Cible (vue du connecteur) | État relevé | À savoir |
|---|---|---|---|
| Termix | http, machine du bastion, port 443 | Sain | bastion d’accès : contient les identifiants des machines ; 2FA du compte et inscriptions désactivées |
| pfSense-Lab | https, 10.0.30.1:443 | Sain | administration du pare-feu ; nom alternatif déclaré dans pfSense (§10.5) |
| HA proxy | http, 10.0.10.5:8404 | « Inconnu » (contrôle de santé retiré) | règle de pare-feu étroite (§10.6) ; mot de passe propre à la page |
| Portainer | machine du bastion | Sain | Portainer pilote Docker de la machine du bastion (il en monte le socket) : sa page de connexion devient accessible depuis Internet, avec les droits qu’elle donne sur l’hôte |
| Wazuh (03/11) | https, 10.0.30.16:443 (certificat autosigné, comme pfSense) | Sain, contrôle de santé TCP | tableau de la détection d’intrusion (phase 16) : il montre les failles et alertes de tout le lab ; ouvert au seul rôle administrateur ; protection vérifiée en navigation privée (« Authentification requise ») |
| Proxmox 01 | hyperviseur de l’école | Sain | hyperviseur partagé : ne dépend pas que du lab ; page de connexion affichée par le nom de domaine et connexion réussie le 14/10 |
« Sain » ne veut dire que ceci : Pangolin atteint la cible à travers le tunnel. Cela ne prouve ni que la page s’affiche correctement, ni que l’application accepte d’être appelée par ce nom (voir pfSense, §10.5).
10.9 Laisser passer un seul chemin, et régler les contrôles de santé (28/10)
Contexte : la supervision veut vérifier toute la chaîne (internet → serveur → tunnel → lab) ; elle a besoin d’une adresse qui réponde sans l’authentification de Pangolin. Et les contrôles de santé de Pangolin interrogent les cibles toutes les 5 secondes : mal réglés, ils remplissent les journaux (H-53).
1. La règle d’accès — choisir son organisation en haut à gauche (pas l’espace d’administration du serveur) → Ressources → la ressource Grafana → onglet Règles d’accès → activer les règles, puis :
| Champ | Valeur |
|---|---|
| Action | Toujours autoriser (Always Allow / Bypass Auth) |
| Type de correspondance | Chemin |
| Valeur | /api/health |
Validation (depuis n’importe quel poste) :
curl.exe -s https://grafana.pangolin.<domaine>/api/health
{"database": "ok", "version": …}. Et la protection : /, /login, /api/healthx, /api/health/../search
doivent répondre 401. Contrepartie : la version de Grafana devient publique (dette T-12).
2. Les contrôles de santé — onglet HTTP de la ressource → bouton de la colonne Vérification de l’état de santé :
| Ressource | Réglage | Pourquoi |
|---|---|---|
| Grafana | Stratégie HTTP, GET, 10.0.30.15:3000, chemin /api/health | répond 200 (Grafana ne journalise pas les succès) et teste aussi sa base |
| pfSense | Stratégie TCP | l’interface de pfSense (nginx) journalise toutes les requêtes : en TCP, aucune page n’est demandée |
la cible repasse Sain ; dans la base syslog, plus aucune ligne 10.0.30.12 … GET / (mesuré : 17 000 lignes par
jour en moins pour chacune des deux ressources).
Pendant un redémarrage de la cible, l’état passe brièvement à « En mauvaise santé » : c’est attendu.
11 · Corriger le « domaine de base »
11.1 Le symptôme
La première ressource affichait une URL malformée :
https://termix.mds.https://pangolin.<domaine>
11.2 La cause
Le fichier de configuration du serveur contenait, sous domains:, la ligne
base_domain: "https://pangolin.<domaine>" : avec le préfixe https://, recopié depuis l’adresse saisie
à l’invite du script (§5.1). Ce champ attend un nom de domaine seul. Chaque ressource héritait donc d’un
suffixe invalide, même recréée.
11.3 La correction
Dans le conteneur du serveur. Lire le bloc concerné (le fichier actif est
/opt/pangolin/config/config.yml, les autres sont des modèles) :
grep -n -B1 -A6 "domains" /opt/pangolin/config/config.yml
Ne pas afficher ni envoyer le fichier entier : il contient la clé du serveur et des mots de passe.
Sauvegarder, puis corriger la ligne 6 seulement :
cp /opt/pangolin/config/config.yml /opt/pangolin/config/config.yml.bak-13-10
sed -i '6s#"https://pangolin.<domaine>"#"pangolin.<domaine>"#' /opt/pangolin/config/config.yml
sed -n '4,7p' /opt/pangolin/config/config.yml
On lit base_domain: "pangolin.<domaine>", sans https://.
Redémarrer, puis supprimer la ressource malformée et la recréer (le type et le domaine ne se modifient pas) :
systemctl restart pangolin && sleep 5 && systemctl is-active pangolin
active.
dashboard_urldoit, lui, garder sonhttps://. Seule la lignebase_domainest concernée.
Retour arrière si besoin :
cp /opt/pangolin/config/config.yml.bak-13-10 /opt/pangolin/config/config.yml && systemctl restart pangolin
12 · Sécurité : ce que le tunnel donne, et ce qu’il ne faut pas publier
| Risque | Pourquoi | Parade |
|---|---|---|
| Le connecteur atteint tout ce que sa machine atteint | il est installé sur le bastion, dans un VLAN d’administration | borner par des règles du pare-feu du lab ce que cette machine peut joindre ; ne publier que des ressources ciblées |
| Publier le bastion (Termix) | il contient les identifiants de toutes les machines : un compte compromis ouvre tout | ressource de test seulement ; pour un accès externe, publier les machines une à une, ou un bastion de démonstration |
| Inscriptions ouvertes dans l’application publiée | un inconnu pourrait créer un compte | désactiver l’inscription dans l’application |
| Secret du connecteur exposé | il donne un accès au tunnel | le régénérer (supprimer et recréer le nœud), chmod 600, jamais en capture |
| Un utilisateur externe qui modifie | il peut casser le lab | instantanés avant chaque ouverture, comptes dédiés à durée limitée |
| Publier l’administration du pare-feu (pfSense) | la page de connexion du pare-feu devient joignable depuis Internet, derrière deux barrières seulement | essai uniquement, aucun compte externe, à retirer ensuite (ressource et nom alternatif) |
| Ressource sans la protection de Pangolin (cas de Termix avant le 14/10, désormais protégé) | l’écran de l’application est ouvert à tout Internet | 2FA du compte, inscriptions désactivées, rien d’autre derrière |
| Comptes Pangolin des utilisateurs externes | l’offre hébergée gratuite n’a pas de rôles ; je n’ai pas vérifié si l’auto-hébergement permet de limiter un utilisateur à une seule ressource | à tester avec un compte de test avant tout accès réel |
La protection de Pangolin se teste depuis un visiteur anonyme, pas depuis son propre navigateur administrateur (§10.4).
13 · Diagnostiquer : la méthode qui a servi, et les pièges
13.1 Les pièges rencontrés
| Symptôme | Cause | Remède | Section |
|---|---|---|---|
Request failed with status code 404 à la création du compte | NPM renvoie tout vers le port 3002 (interface seule) | viser Traefik (443) | §7.2 |
Connecteur : websocket: bad handshake en boucle | Websockets Support désactivé dans NPM | l’activer, enregistrer | §7.2 |
URL de ressource …https://… | base_domain avec https:// | corriger la ligne, redémarrer, recréer | §11 |
Site « En ligne » mais Ping attempt … i/o timeout | le tunnel UDP n’arrive pas | chaîne de redirection : §8 et §13.3 | §8 |
Failed to fetch server IP … self-signed certificate dans le journal du serveur | une connexion HTTPS sortante du conteneur reçoit un certificat qui n’est pas public | sans effet sur le tunnel ; cause non établie | §5.2 |
| Certificat « Pending » côté Pangolin | Let’s Encrypt n’atteint pas Traefik derrière NPM | normal : NPM présente le certificat | §7.2 |
Un certificat *.<domaine> existe, mais termix.pangolin.<domaine> est non couvert | un joker ne vaut que pour un seul niveau | créer *.pangolin.<domaine> | §7.3.2 |
| Certificat joker « Not Used » | aucune entrée ne l’emploie encore | normal tant que l’entrée joker n’est pas créée | §7.3.2 |
no available server devant une page qui demande un mot de passe | le contrôle de santé reçoit un 401 et juge la cible hors service | retirer le contrôle de cette cible | §10.6 |
Fond rouge Potential DNS Rebind attack detected sur pfSense | pfSense refuse les noms qu’il ne connaît pas | Alternate Hostnames (Admin Access) | §10.5 |
13.2 Lire les journaux du connecteur
| Ligne | Sens |
|---|---|
Websocket connected | authentification et WebSocket acceptés |
Connecting to endpoint: … | tentative d’établir le tunnel |
Ping attempt N failed … i/o timeout | le tunnel ne monte pas |
Tunnel connection to server established successfully! | tunnel monté |
Started tcp proxy to <IP>:<port> | une cible est active (une ressource existe) |
Le statut « En ligne » du tableau de bord ne prouve pas le tunnel.
13.3 Trouver où se perd l’UDP : couper la chaîne en deux, à chaque fois
① Le paquet part-il de la machine du lab ? (outil à installer : sudo apt install -y tcpdump)
sudo tcpdump -ni any "udp and host <IP-publique>"
Des lignes … > <IP-publique>.51820: UDP, length 148, toutes les 5 secondes, avec l’adresse de la
machine en ens18 : le connecteur émet bien sur le bon port.
② Le pare-feu du lab le laisse-t-il sortir ? Dans pfSense → Diagnostics → States, filtrer sur
<IP-publique>.
Une ligne udp … → <IP-publique>:51820 côté LAN et côté WAN, en NO_TRAFFIC:SINGLE (émis, rien reçu en
retour) : le pare-feu laisse passer, la coupure est au-delà.
③ Où s’arrête le chemin ? Depuis la machine du lab (BusyBox n’a pas l’option -U, l’UDP est le
défaut) :
docker run --rm --net host nicolaka/netshoot traceroute -n -p 51820 -m 12 -w 2 <IP-publique>
Les sauts traversent l’école puis l’opérateur et atteignent <IP-publique> : le réseau de l’école ne
bloque pas.
④ Arrive-t-il chez moi ? Sur l’hyperviseur personnel puis dans le conteneur :
tcpdump -ni any -l --immediate-mode "port 51820"
Envoyer, depuis un autre réseau (par exemple le partage de connexion d’un téléphone), trois paquets :
1..3 | ForEach-Object { $u = New-Object Net.Sockets.UdpClient; $u.Send([Text.Encoding]::ASCII.GetBytes("test"),4,"<IP-publique>",51820) }
Des lignes … > <IP-CT>.51820: UDP, length 4 : le trafic arrive jusqu’au conteneur.
13.4 Les erreurs de mesure à éviter
| Piège | Ce qui s’est passé |
|---|---|
tcpdump : « N packets received by filter » avec 0 packets captured | j’y ai vu des paquets arrivés ; ce compteur n’est pas fiable, seules les lignes affichées comptent |
tcpdump sans -l --immediate-mode | l’affichage par lots peut manquer des paquets si l’on interrompt trop vite |
| Tester depuis un seul réseau | un « rien » peut venir du réseau d’émission : changer de réseau pour trancher |
| Deux affirmations qui se contredisent (« tel équipement est le WAN du routeur » vs perte d’accès après suppression d’une règle) | vérifier l’écran (statut, liste d’appareils) avant de continuer |
| Supprimer des règles « pour voir » | coupe l’accès à d’autres services : décocher plutôt que supprimer |
14 · Checklist
SERVEUR
Domaine possédé, adresse publique sans CGNAT, mise à jour automatique de l'adresse
Conteneur Pangolin installé, jeton lu dans le journal, compte administrateur créé
base_domain SANS "https://" (§11), dashboard_url AVEC
ss -ulnp | grep 51820 → le port écoute
DNS ET PROXY
pangolin → racine en DNS seul ; *.pangolin → racine en DNS seul ; Resolve-DnsName → l'adresse publique
NPM : https → <IP-CT>:443, Websockets activé, Force SSL (une entrée par nom, OU une entrée joker : §7.3)
Jeton Cloudflare dédié, limité à la zone, en permission DNS seulement, sans filtre IP ni durée
Certificat *.pangolin.<domaine> créé par validation DNS ; test avec une ressource sans entrée NPM
Redirection UDP 51820 sur CHAQUE équipement de la chaîne, vers le BON équipement
CONNECTEUR
Nœud Site Newt créé ; newt.env en 600, secret jamais en capture
« Tunnel connection to server established successfully! » sans « Ping attempt … failed »
RESSOURCE
Protocole de la cible testé au curl avant de la déclarer
URL à un seul https:// ; page de l'application visible
Test en navigation PRIVÉE : écran d'authentification Pangolin, pas celui de l'application
SÉCURITÉ
Rien de sensible publié (pas le bastion en accès externe) ; inscriptions désactivées
Ce que le connecteur peut atteindre est borné par le pare-feu du lab
Si une cible est dans un autre VLAN : une règle étroite (un hôte, un port, un alias), au-dessus du refus ; `curl` depuis le connecteur répond
Si pfSense est publié : nom alternatif déclaré (Admin Access), pas de « Disable DNS Rebinding Checks », à retirer après l'essai
Règlement du réseau d'accueil sur un tunnel sortant : vérifié (au TP : question abandonnée le 14/10, point ouvert)
La règle à retenir en une phrase
Un tunnel ne se prouve pas par un statut « en ligne » mais par du trafic qui passe : on coupe la chaîne en deux, on mesure de chaque côté, et on teste la protection depuis un visiteur qui n’est pas soi-même.
Captures
Fichier (dans ../captures/) | Ce qu’elle montre | État |
|---|---|---|
55-pangolin-ressource-malformee.png | la ressource avec l’URL …https://… avant correction (§11) | déposée |
56-pangolin-ressource-termix-saine.png | la ressource avec l’URL propre, la cible http, la santé « Sain » (§10.2) | déposée |
57-cloudflare-jeton-dns.png | la création du jeton : permission DNS, zone unique (§7.3.1) | déposée |
58-npm-certificat-joker-formulaire.png | le formulaire du certificat joker (§7.3.2) | déposée |
59-npm-certificat-joker-cree.png | le certificat dans la liste, « Not Used » (§7.3.2) | déposée |
60-pfsense-dns-rebind.png | le message rouge « Potential DNS Rebind attack detected » (§10.5) | déposée |
61-pangolin-tunnel-etabli.png | les journaux du connecteur avec « Tunnel connection … established » (§9.3) | à déposer |
62-pangolin-auth-navigation-privee.png | l’écran d’authentification Pangolin en navigation privée (§10.4) | à déposer |
63-pfsense-alternate-hostnames.png | le champ « Alternate Hostnames » renseigné (§10.5) | à déposer |
64-pfsense-connexion-par-pangolin.png | l’écran de connexion de pfSense atteint par le nom de domaine (§10.5) | à déposer |
Masquer l’ID et le secret avant tout dépôt dans le dossier de preuves.






