logoScott

VPN sur mesure avec Xray VLESS + Reality

Comment j’ai construit un VPN furtif, géré par Terraform, qui résiste au DPI et protège les secrets.

SScott Santinhole 3 janvier 2026
architecture diagram cover

Pourquoi ce projet existe

Dans un futur pas si lointain, je prévois de voyager dans un pays où l’accès à internet peut devenir... orienté. Les outils sur lesquels je compte normalement (outils de travail, services Google, même des applications basiques) peuvent soudainement devenir lents, peu fiables, voire carrément bloqués.

Au début, j’ai fait comme tout le monde : essayer quelques applications VPN populaires en espérant que ça marche. Et oui… ça marchait parfois. D’autres fois, c’était ralenti, ça coupait aléatoirement, ou c’était comme jouer à la roulette avec « est-ce que ça va se connecter aujourd’hui ? »

Ce projet a donc commencé avec un objectif très pratique : je voulais quelque chose de fiable, sous mon contrôle, et facile à reconstruire si (quand) c’est ciblé ou que ça casse (parce que ça finit toujours par casser). Mais je voulais aussi que ce soit une pièce de portfolio propre, quelque chose qui montre que je peux livrer un système de bout en bout avec une automatisation correcte, de bonnes pratiques de sécurité, et de la documentation, pas juste un script qui « marche sur mon laptop ».

En bref : il ne s’agissait pas d’inventer un nouveau VPN. Il s’agissait de construire une configuration solide et reproductible que je peux posséder, comprendre et faire évoluer.

Architecture en un coup d’œil

Toute la stack est dans le code :

Terraform (GCP) construit le plan de contrôle : comptes de service, Secret Manager, triggers CI, sinks de logs, etc.

Cloud Build agit comme mon « opérateur » : lint/scan, prévisualisation de plan, application des changements, puis déclenchement du déploiement.

Fleet reconcile se connecte en SSH à chaque VPS et fait converger les serveurs vers la configuration souhaitée (idempotent : sûr à exécuter plusieurs fois).

Reality gère le handshake TLS furtif pour que le trafic ressemble à Chrome accédant à un site web légitime.

(Diagramme d’architecture ici.)

Reality check : le problème de la poule et de l’œuf

Avant que Terraform puisse « tout gérer », certaines choses doivent exister d’abord. On ne peut pas utiliser l’automatisation pour créer l’automatisation.

J’amorce donc quelques pièces manuellement, puis je les importe dans l’état Terraform (peut-être un peu trop pour être honnête 😅, mais parfois je privilégie la vitesse et l’itération plutôt qu’un one-shot parfait, et tout finit terraformé en fin de journée) :

  1. créer le projet GCP
  2. créer le compte de service Terraform executor (im-executor)
  3. configurer la connexion GitHub Cloud Build (2e génération)
  4. assigner quelques rôles IAM que le provider Terraform ne peut pas assigner en raison de limitations de l’API
  5. initialiser les premières valeurs de secrets (clés SSH, identifiants Xray, etc.)

Confession : j’ai fait plus de « clic d’abord, import ensuite » que ce que je recommanderais dans un monde parfait. Quand on debug l’IAM, la Console est plus rapide que des cycles plan/apply sans fin. Une fois que ça marche, c’est importé et ça redevient ennuyeux.

Intentions d’infrastructure

Mon objectif était de garder le pipeline ennuyeux-dans-le-bon-sens : privilèges minimaux, passages de relais propres, et zéro fuite de secrets dans les logs.

Plutôt qu’une seule identité CI ultra-puissante, chaque étape du pipeline a son propre compte de service :

TriggerCompte de ServicePérimètre
ci-lintcb-ci-lintScans de sécurité uniquement
im-previewcb-im-previewPlans Terraform
im-applycb-im-applyApplication des changements
fleet-reconcilecb-fleet-reconcileDéploiement sur VPS

Quelques choix de sécurité dont je suis assez fier :

  • IAM au niveau du secret, pas au niveau du projet : chaque SA n’obtient secretAccessor que sur les secrets spécifiques dont il a besoin.
  • Périmètres d’impersonation : le Terraform executor a des permissions larges, mais seuls les triggers de déploiement peuvent l’impersonner. Le lint ne peut pas toucher à l’infra du tout.
  • Pas de secrets dans les logs : les secrets atterrissent dans des fichiers temporaires avec chmod 600, sont nettoyés par des traps, et sont effacés sur le VPS après utilisation.
  • Pinning de clé d’hôte SSH : je ne fais explicitement pas de StrictHostKeyChecking=no. Je scanne les clés d’hôte lors de la configuration, les stocke dans Secret Manager, et fais échouer les déploiements si l’identité du serveur change de manière inattendue.
  • Runtime Xray à privilèges minimaux : Xray tourne en tant qu’utilisateur dédié xray, les configs sont verrouillées, et il ne conserve que CAP_NET_BIND_SERVICE pour le port 443.
  • Honnêteté sur la supply chain : vérification des checksums, versions figées, et éviter les mises à jour surprise des providers.

Chaque PR passe par des vérifications automatisées : formatage/validation Terraform, linting, scanners de sécurité, linting de scripts shell et détection de secrets. Si une vérification échoue, la PR n’est pas mergée.

Choix de protocole & VPS

J’ai écarté WireGuard et IKEv2 parce que leurs handshakes sont plus faciles à détecter pour le DPI.

  • WireGuard est rapide, mais le DPI peut repérer le pattern de son handshake UDP.
  • IKEv2/IPSec a des signatures bien connues.
  • OpenVPN-over-TLS commence comme du HTTPS, mais diverge suffisamment pour que le DPI avancé le détecte.

Reality adopte une approche différente : il n’encapsule pas le trafic VPN dans du TLS de manière suspecte. Il usurpe une vraie session TLS :

  1. prétend se connecter à un domaine légitime via le SNI
  2. imite le fingerprinting TLS de Chrome (cipher suites, extensions, etc.)
  3. si quelqu’un sonde le serveur sans les bons paramètres, il est redirigé vers le vrai site leurre
  4. VLESS n’ajoute pas de surcoût de chiffrement supplémentaire, TLS s’en charge

Un détail pratique : les shortIds. Chaque VPS a ses propres shortIds, et j’en garde 2 à 3 par serveur pour pouvoir faire tourner les clients si l’un est repéré, sans toucher à la config serveur.

Si vous devinez mon IP serveur sans un shortId valide, vous atterrissez simplement sur le site leurre. Le VPN ne se révèle jamais.

Le choix du fournisseur compte aussi. Avant de louer quoi que ce soit, je teste l’accessibilité depuis le pays de destination (j’ai utilisé des vérifications looking-glass via globalping) car la réputation et le routage peuvent faire toute la différence.

Globalping reachability test results

Configuration côté client

Une fois que Terraform applique et que le fleet reconciler s’exécute, chaque VPS écrit l’URL de connexion client sur le disque. Je me connecte en SSH et la récupère :

  • cat /etc/xray/client_url.txt

Puis je la colle dans mes clients :

  • macOS : Happ (gratuit)
  • iOS : Shadowrocket (payant, mais fiable)

Mon test de vérification est simple : connecter le client, puis vérifier l’IP de sortie :

  • curl -s ifconfig.me devrait renvoyer l’IP du VPS, pas la mienne.
Happ macOS configuration showing the Singapore VPS IP
Shadowrocket iOS profiles for Tokyo and Singapore

Voir une IP de Singapour dans le navigateur pendant que le client Happ est actif est mon test de vérification que le trafic passe bien par le VPS.

Plus important encore, le flux est invisible. Le handshake TLS ressemble à Chrome communiquant avec un site légitime, donc le DPI a peu de raisons de l’examiner. Si j’ai besoin de faire tourner le shortId ou de regénérer des clés, le pipeline fait le gros du travail : mes clients n’ont qu’à coller un nouveau lien VLESS.

Monitoring & logging (pour ne pas naviguer à l’aveugle)

Je voulais suffisamment d’observabilité pour débugger les problèmes, sans construire accidentellement un système de surveillance pour moi-même.

  • Les logs VPS vont vers Cloud Logging, puis sont sinkés dans BigQuery
  • Je ne capture que les logs Xray de niveau erreur (pas de logs d’accès bruyants)
  • Les tables BigQuery expirent après 7 jours pour contrôler les coûts
  • Les métriques hôte (CPU/mémoire) sont collectées périodiquement

Le coût estimé de logging est pratiquement nul (moins de ~0,10 €/mois).

Lire le détail technique

Si vous voulez voir le layout Terraform, les triggers Cloud Build, l’inventaire de la flotte et les templates/scripts Xray, tout est documenté dans le repo, incluant les étapes de bootstrap pour que je puisse ré-importer les ressources proprement la prochaine fois que j’en ai besoin.

https://github.com/scottsantinho/vless-reality-gcp