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/health pour 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
1Le besoin : ouvrir un accès externe à un lab que personne ne peut joindre
2Ce qu’est Pangolin : trois composants, quatre notions
3L’architecture retenue, et pourquoi le serveur est chez moi
4Prérequis à vérifier avant de commencer
5Installer le serveur et créer le compte administrateur
6Le DNS : un wildcard pour ne plus y toucher
7Nginx Proxy Manager devant Pangolin : les WebSockets, le certificat joker et l’entrée joker
8Les redirections de port : le tunnel UDP 51820
9Installer le connecteur (le « site ») dans le lab
10Publier 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
11Corriger le « domaine de base »
12 Sécurité : ce que le tunnel donne, et ce qu’il ne faut pas publier
13Diagnostiquer : la méthode qui a servi, et les pièges
14Checklist

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

ContrainteConséquence
Budget nulaucun service payant
Partageable avec des personnes extérieuresTailscale écarté : le tailnet donne l’accès à tous les VLAN (dette T‑6)
Accès par ressource, pas par réseauun invité doit voir une machine, pas tout le lab
Aucun port à ouvrir côté lable 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)

ComposantRôle
Pangolininterface d’administration, comptes, règles d’accès, API
Gerbiltermine les tunnels WireGuard (UDP) venant des sites
Traefikle 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

NotionCe que c’est
Organisationle 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 publiqueun service web publié sous un nom de domaine, protégé par l’authentification de Pangolin
Ciblel’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│
                                                                                       └──────────────────┘
FluxSensProtocole
Visiteur → serveur Pangolinentrant, chez moiTCP 443 (et 80 pour les certificats)
Lab → serveur Pangolin (le tunnel)sortant depuis le labUDP 51820
Serveur → application du labà travers le tunnelHTTP, 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érequisComment le vérifierRésultat obtenu au TP
Un nom de domaine que l’on gèreaccè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 DNSle nom suit-il la nouvelle adresse ? un script met à jour l’enregistrement racine
Pouvoir rediriger TCP 80, 443 et UDP 51820 sur la boxinterface d’administration de la box après correction, voir §8
UDP sortant depuis le lab vers l’extérieurtest de capture, §13.3 le tunnel s’est établi depuis l’école
Un Docker sur une machine du labdocker --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 certificate qui 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 :

ChampValeur
TypeCNAME
Nom*.pangolin
Destination<domaine> (la racine)
État du proxyDNS uniquement (nuage gris)
TTLAutomatique

« 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 :

ChampValeur
Domain Namespangolin.<domaine> (puis un par ressource : termix.pangolin.<domaine>…)
Schemehttps
Forward Hostname / IP<IP-CT>
Forward Port443 (le port de Traefik)
Websockets Support activé
Onglet SSLnouveau 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 » :

ChampValeur
Nomun nom qui dit à quoi il sert (par exemple npm-certificat-joker-pangolin)
PermissionZone → DNS → Modifier
Ressources de la zonela seule zone du domaine, pas toutes les zones du compte
Filtrage d’adresse IPvide (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.

57-cloudflare-jeton-dns

7.3.2 Le certificat joker

NPM → Certificates → Add Certificate → Let’s Encrypt via DNS :

ChampValeur
Domain Names*.pangolin.<domaine>
Key TypeECDSA 256 (le défaut)
DNS ProviderCloudflare
Credentials File Contentdns_cloudflare_api_token=<jeton> (sans espace autour du =)
Propagation Secondsvide ; 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.

58-npm-certificat-joker-formulaire
59-npm-certificat-joker-cree

7.3.3 L’entrée joker

NPM → Hosts → Proxy Hosts → Add Proxy Host :

ChampValeur
Domain Names*.pangolin.<domaine>
Schemehttps
Forward Hostname / IP<IP-CT>
Forward Port443
Websockets Support activé
Onglet SSL → SSL Certificatele certificat *.pangolin.<domaine>
Force SSL activé

7.3.4 Le test qui prouve l’automatisation

  1. 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 .
  2. 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 :

ÉquipementRègle
Box de l’opérateurUDP 51820 → le routeur personnel
Routeur personnelUDP 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) :

ChampValeur
TypeHTTP (ne peut pas être modifié ensuite)
NomTermix-MDS
Sous-domainetermix
Domaine de basepangolin.<domaine>
Cible : nœudle site créé au §9
Cible : adresse10.0.30.12, port 443, protocole http

L’URL affichée doit être https://termix.pangolin.<domaine>, avec un seul https://.

56-pangolin-ressource-termix-saine

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

  1. Ouvrir l’URL : la page de l’application s’affiche (le circuit complet est prouvé).
  2. 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

ChampValeur
TypeHTTP
NompfSense-Lab
Sous-domainepfsense
Domaine de basepangolin.<domaine>
Ciblenœ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.

60-pfsense-dns-rebind

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 Hostnamespfsense.pangolin.<domaine>
BoutonSave

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 pfSenseValeur
Firewall → Aliases → PortsPORTS_HA_STATS = 8404 ; Save, Apply Changes
Firewall → Rules → onglet du VLAN du connecteurPass, 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èdeEffet
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 401non 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 :

ChampSens
Adresse maill’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).

RessourceCible (vue du connecteur)État relevéÀ savoir
Termixhttp, machine du bastion, port 443Sainbastion d’accès : contient les identifiants des machines ; 2FA du compte et inscriptions désactivées
pfSense-Labhttps, 10.0.30.1:443Sainadministration du pare-feu ; nom alternatif déclaré dans pfSense (§10.5)
HA proxyhttp, 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
Portainermachine du bastionSain 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é TCPtableau 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 01hyperviseur de l’écoleSainhyperviseur 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 :

ChampValeur
ActionToujours autoriser (Always Allow / Bypass Auth)
Type de correspondanceChemin
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é :

RessourceRéglagePourquoi
GrafanaStratégie HTTP, GET, 10.0.30.15:3000, chemin /api/healthrépond 200 (Grafana ne journalise pas les succès) et teste aussi sa base
pfSenseStratégie TCPl’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>

55-pangolin-ressource-malformee

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_url doit, lui, garder son https://. Seule la ligne base_domain est 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

RisquePourquoiParade
Le connecteur atteint tout ce que sa machine atteintil est installé sur le bastion, dans un VLAN d’administrationborner 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 toutressource 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éeun inconnu pourrait créer un comptedésactiver l’inscription dans l’application
Secret du connecteur exposéil donne un accès au tunnelle régénérer (supprimer et recréer le nœud), chmod 600, jamais en capture
Un utilisateur externe qui modifieil peut casser le labinstantané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 seulementessai 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 Internet2FA du compte, inscriptions désactivées, rien d’autre derrière
Comptes Pangolin des utilisateurs externesl’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ômeCauseRemèdeSection
Request failed with status code 404 à la création du compteNPM renvoie tout vers le port 3002 (interface seule)viser Traefik (443)§7.2
Connecteur : websocket: bad handshake en boucleWebsockets Support désactivé dans NPMl’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 timeoutle tunnel UDP n’arrive paschaîne de redirection : §8 et §13.3§8
Failed to fetch server IP … self-signed certificate dans le journal du serveurune connexion HTTPS sortante du conteneur reçoit un certificat qui n’est pas publicsans effet sur le tunnel ; cause non établie§5.2
Certificat « Pending » côté PangolinLet’s Encrypt n’atteint pas Traefik derrière NPMnormal : NPM présente le certificat§7.2
Un certificat *.<domaine> existe, mais termix.pangolin.<domaine> est non couvertun joker ne vaut que pour un seul niveaucréer *.pangolin.<domaine>§7.3.2
Certificat joker « Not Used »aucune entrée ne l’emploie encorenormal 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 passele contrôle de santé reçoit un 401 et juge la cible hors serviceretirer le contrôle de cette cible§10.6
Fond rouge Potential DNS Rebind attack detected sur pfSensepfSense refuse les noms qu’il ne connaît pasAlternate Hostnames (Admin Access)§10.5

13.2 Lire les journaux du connecteur

LigneSens
Websocket connectedauthentification et WebSocket acceptés
Connecting to endpoint: …tentative d’établir le tunnel
Ping attempt N failed … i/o timeoutle 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ègeCe qui s’est passé
tcpdump : « N packets received by filter » avec 0 packets capturedj’y ai vu des paquets arrivés ; ce compteur n’est pas fiable, seules les lignes affichées comptent
tcpdump sans -l --immediate-model’affichage par lots peut manquer des paquets si l’on interrompt trop vite
Tester depuis un seul réseauun « 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.pngla ressource avec l’URL …https://… avant correction (§11) déposée
56-pangolin-ressource-termix-saine.pngla ressource avec l’URL propre, la cible http, la santé « Sain » (§10.2) déposée
57-cloudflare-jeton-dns.pngla création du jeton : permission DNS, zone unique (§7.3.1) déposée
58-npm-certificat-joker-formulaire.pngle formulaire du certificat joker (§7.3.2) déposée
59-npm-certificat-joker-cree.pngle certificat dans la liste, « Not Used » (§7.3.2) déposée
60-pfsense-dns-rebind.pngle message rouge « Potential DNS Rebind attack detected » (§10.5) déposée
61-pangolin-tunnel-etabli.pngles journaux du connecteur avec « Tunnel connection … established » (§9.3) à déposer
62-pangolin-auth-navigation-privee.pngl’écran d’authentification Pangolin en navigation privée (§10.4) à déposer
63-pfsense-alternate-hostnames.pngle champ « Alternate Hostnames » renseigné (§10.5) à déposer
64-pfsense-connexion-par-pangolin.pngl’é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.