Zero-Trust CI/CD : Construire des runners GitHub Actions auto-hébergés sécurisés via tunnels inversés

Les équipes d’ingénierie modernes sont confrontées à un dilemme familier : les runners CI/CD hébergés dans le cloud sont coûteux et sous-dimensionnés pour des charges exigeantes, mais faire passer une plateforme publique comme GitHub Actions dans un réseau d’entreprise protégé par un pare-feu ressemble à un cauchemar IT. Il existe une troisième voie — celle qui utilise des tunnels inversés en sortie uniquement, des conteneurs éphémères, et une authentification basée sur l’identité pour offrir des performances bare-metal sans ouvrir un seul port entrant. Cet article est un plan détaillé pour construire cette architecture.
Le problème avec “Il suffit d’ouvrir un port”
L’approche conventionnelle pour des runners auto-hébergés suppose une connectivité entrante. Un runner sur votre réseau local s’enregistre auprès de GitHub puis attend que des jobs lui soient dispatchés. Pour que cela fonctionne, GitHub doit pouvoir atteindre votre machine — ce qui implique généralement de percer un trou dans votre pare-feu.
Cette approche entre en conflit immédiat avec la politique de sécurité de l’entreprise. Les ports entrants nécessitent des modifications de règles de pare-feu, des revues d’architecture réseau, et une maintenance continue des listes d’IP autorisées. GitHub opère à partir de plages d’adresses IP dynamiques réparties dans plusieurs centres de données mondiaux — maintenir et mettre à jour ces blocs CIDR représente une charge opérationnelle qui ne scale pas.
Il existe un modèle plus propre : inverser complètement la direction de la connexion.
Un runner auto-hébergé communique avec GitHub via un long polling HTTP(S). Il ouvre une connexion à GitHub pendant jusqu’à 50 secondes en attendant des assignations de jobs. Si rien n’arrive, il se déconnecte et se reconnecte. Le trafic est entièrement sortant sur le port 443 — le même port que votre navigateur utilise pour HTTPS. Les pare-feux d’entreprise qui autorisent la navigation web permettent déjà ce trafic. Aucune exception n’est nécessaire.
L’architecture du tunnel inversé
L’idée clé est que le runner n’a pas besoin de recevoir des connexions entrantes de GitHub — il va chercher du travail en sortant. Un agent de tunnel inversé renforce cet avantage en créant un canal sortant persistant, authentifié, qui peut aussi transporter tout webhook ou trafic de contrôle qui doit atteindre votre machine.
[ Matériel Bare-Metal Local ]
|
| (TLS/QUIC en sortie sur port 443)
▼
[ Proxy de tunnel avec authentification d'identité ] ◄─── Authentification mutuelle TLS / Vérification par token
▲
| (Trafic Webhook / Polling)
|
[ Plan de contrôle GitHub Actions ]
Parce que la connexion est initiée depuis l’intérieur de votre réseau, le pare-feu la voit comme une simple requête HTTPS. Le proxy de tunnel authentifie les deux extrémités avant que tout trafic ne circule, et toute la dispatch de jobs se fait via ce canal vérifié.
Sécurité basée sur l’identité : Plus qu’un simple tunnel
Faire transiter du trafic via un tunnel sortant n’est sécurisé que si l’authentification le protège. Les déploiements en production combinent plusieurs couches :
Mutual TLS (mTLS) : Le client runner et le proxy de tunnel présentent des certificats cryptographiquement signés. Aucun des deux côtés n’accepte une connexion d’un pair non authentifié.
Liens OIDC / OAuth2 : La session du tunnel peut être liée à des fournisseurs d’identité vérifiés — Okta, Google Workspace, ou tokens OIDC d’organisation GitHub — pour que seuls les runners appartenant à votre organisation puissent établir des sessions.
Tokens éphémères à courte durée : Le token d’enregistrement utilisé pour connecter un runner à GitHub est récupéré dynamiquement via l’API GitHub au début de chaque exécution de pipeline et invalidé immédiatement à la fin du job. Il n’y a pas de credential longue durée stocké sur disque.
Ces contrôles signifient que même si votre matériel local est accessible via le tunnel, il n’est pas découvrable, scannable, ou accessible par quelque chose qui ne peut pas présenter la bonne identité cryptographique.
Le vrai modèle de menace (mis à jour pour 2025–2026)
Avant de construire cette infrastructure, il est utile d’être lucide sur ce qui peut mal tourner.
En novembre 2025, le ver Shai-Hulud a démontré à grande échelle que des runners auto-hébergés peuvent être instrumentalisés comme des portes dérobées persistantes communiquant entièrement via les canaux de confiance de GitHub. Parce que tout le trafic passe par github.com, les défenses réseau traditionnelles sont largement aveugles face à cette menace. La documentation de sécurité de GitHub est explicite : “Les runners auto-hébergés pour GitHub ne garantissent pas qu’ils s’exécutent dans des machines virtuelles éphemeres et propres, et peuvent être compromis de façon persistante par du code non fiable dans un workflow.”
Des attaques plus anciennes sur la chaîne d’approvisionnement, y compris le poisoning de l’action tj-actions/changed-files et la brèche Codecov, ont montré que les attaquants ciblent de plus en plus les pipelines CI/CD car ces pipelines détiennent souvent un accès privilégié à l’infrastructure de production, aux identifiants cloud, et aux registres de packages.
L’architecture décrite dans cet article est conçue pour contenir ces menaces — pas pour les ignorer.
Plan étape par étape
Composant 1 : Le conteneur de runner éphémère
La première règle de sécurité pour un runner auto-hébergé est qu’il ne doit jamais exécuter de code non fiable directement sur le système hôte. Un environnement conteneurisé à usage unique limite la portée en cas de problème.
e Note de version : À partir du 16 mars 2026, GitHub exige que les runners auto-hébergés soient au minimum en version v2.329.0 (publiée le 15 octobre 2025). Les runners plus anciens sont bloqués lors de l’enregistrement. La version stable actuelle au moment de la rédaction est v2.334.0. Toujours récupérer la dernière version sur la page des releases de actions/runner.
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update 66 apt-get install -y \
curl \
sudo \
git \
jq \
build-essential \
ca-certificates \
66 rm -rf /var/lib/apt/lists/*
# Utilisateur runner non-root
RUN useradd -m -s /bin/bash runner 66 \
usermod -aG sudo runner 66 \
echo "runner ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers
USER runner
WORKDIR /home/runner
# Fixer à une version spécifique e0>= v2.329.0 ; vérifier la page des releases pour la dernière
ARG RUNNER_VERSION="2.334.0"
RUN curl -o github-runner.tar.gz -L \
https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
66 tar xzf ./github-runner.tar.gz \
66 rm github-runner.tar.gz
COPY --chown=runner:runner entrypoint.sh /home/runner/entrypoint.sh
RUN chmod +x /home/runner/entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
L’option --privileged ne doit jamais apparaître dans la commande docker run pour ce conteneur. Un conteneur privilégié a presque le même accès à l’hôte qu’un processus root en direct sur le système.
Composant 2 : Le script d’entrée
Le entrypoint.sh orchestre l’enregistrement, l’exécution, et le nettoyage. Il récupère un token d’enregistrement à courte durée de l’API GitHub à chaque invocation — aucun token persistant n’est stocké dans l’image.
#!/bin/bash
set -e
if [ -z "$GH_OWNER" ] || [ -z "$GH_REPOSITORY" ] || [ -z "$GH_PAT" ]; then
echo "ERREUR : Variables d'environnement manquantes."
exit 1
fi
echo "Récupération du token d'enregistrement à courte durée..."
REG_TOKEN=$(curl -s -X POST \
-H "Authorization: token ${GH_PAT}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
| jq -r '.token')
if [ "$REG_TOKEN" == "null" ] || [ -z "$REG_TOKEN" ]; then
echo "ERREUR : Échec de la récupération du token. Vérifiez les permissions PAT."
exit 1
fi
./config.sh \
--url https://github.com/${GH_OWNER}/${GH_REPOSITORY} \
--token "${REG_TOKEN}" \
--name "éphémère-$(hostname)" \
--labels "local-highperf,éphémère" \
--unattended \
--replace
cleanup() {
echo "Job terminé. Désenregistrement du runner..."
REM_TOKEN=$(curl -s -X POST \
-H "Authorization: token ${GH_PAT}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
| jq -r '.token')
./config.sh remove --token "${REM_TOKEN}"
}
trap 'cleanup' EXIT SIGINT SIGTERM
# --once : accepter un seul job, l'exécuter, puis sortir
./run.sh --once
L’option --once est essentielle. Elle indique à l’agent du runner d’accepter exactement un job, de l’exécuter jusqu’à la fin, puis de se terminer. Combiné à un service systemd ou une tâche cron qui détruit le conteneur après sortie et en lance un nouveau à partir de l’image de base propre, cela élimine tout état persistant entre les exécutions.
Composant 3 : L’agent tunnel
Une fois le conteneur du runner prêt, un agent de tunnel relie votre réseau local au plan de contrôle de GitHub. Cloudflare Tunnel (cloudflared) est une option open-source largement utilisée qui établit une connexion sortante authentifiée avec votre compte Cloudflare Zero Trust. Une configuration équivalente pour frp ou d’autres agents suit la même logique.
Un fichier minimal tunnel.yaml pour un déploiement Cloudflare Tunnel :
tunnel: tunnel-runners-local
credentials-file: /home/runner/.cloudflared/tunnel-auth.json
ingress:
- hostname: ci-proxy.votresociete.com
service: http://localhost:8080
- service: http_status:404
Le hostname ci-proxy.votresociete.com doit être protégé par des politiques Cloudflare Access pour que seul le trafic webhook authentifié provenant des plages IP documentées de GitHub puisse passer par le tunnel. Tout le reste reçoit une réponse 404.
Le token du tunnel doit être passé en variable d’environnement au lancement du conteneur, jamais intégré dans une image :
docker run --detach \
--restart always \
--network runner-net \
--name cloudflared \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token "${TUNNEL_TOKEN}"
Renforcement de la sécurité : Les contraintes incontournables
Les tunnels inversés éliminent le problème d’exposition entrante, mais ils ne protègent pas contre l’exécution de code malveillant à l’intérieur de votre périmètre une fois qu’un job commence. Les contrôles de durcissement suivants sont obligatoires dans toute déploiement en production.
1. Limites d’isolation strictes
Le runner doit s’exécuter dans un conteneur, et ce conteneur doit fonctionner dans une machine virtuelle dédiée autant que possible. Les microVMs Firecracker (utilisées par Actuated et autres services) offrent une isolation assistée par matériel : si une brèche dans le kernel permet de sortir du conteneur, l’attaquant reste contenu dans une VM jetable sans accès à l’OS ou au réseau hôte.
Exigences minimales :
- Pas de flag --privileged sur les conteneurs du runner
- Utilisateur non-root dans le conteneur (comme dans le Dockerfile ci-dessus)
- Pas de montages de volumes exposant le système de fichiers hôte à l’environnement de build
- Image du conteneur reconstruite à partir d’une base propre pour chaque job ; pas de réutilisation avec état en cache
2. Segmentation réseau (sandbox VLAN)
La machine physique hébergeant le runner doit être dans un VLAN dédié et isolé — un sandbox de développement ou un segment DMZ — avec des règles de pare-feu bloquant explicitement :
- Tout trafic du VLAN du runner vers les réseaux internes, partages de fichiers, et clusters de bases de données
- Tout trafic Internet sortant sauf vers les domaines spécifiques nécessaires :
*.github.com,*.githubusercontent.com, et vos endpoints de fournisseur de tunnel
Cela empêche un runner compromis d’être utilisé pour une propagation latérale dans votre réseau même si les couches d’isolation du conteneur et de la VM échouent.
3. Gérer les pull requests non fiables
La documentation de GitHub recommande explicitement de ne pas faire tourner de runners auto-hébergés sur des dépôts publics. Tout contributeur pouvant forker le dépôt et ouvrir une pull request peut potentiellement déclencher votre runner auto-hébergé. Pour les dépôts privés, appliquez ces contrôles :
- Exiger l’approbation d’un mainteneur avant d’exécuter des workflows pour des pull requests provenant de contributeurs externes ou de forks inédits
- Utiliser des règles de protection d’environnement et de branche pour que le label
local-highperfne soit invoqué que depuis des branches protégées (main,release/*) - Considérer le runner comme une infrastructure totalement non fiable qui pourrait être compromise à tout moment — l’architecture doit être conçue pour être sûre quoi qu’il arrive
4. Les runners éphémères sont non négociables
Les runners persistants accumulent des états. Un job qui met en cache des binaires malveillants, laisse un processus rogue en marche, ou modifie des variables d’environnement peut empoisonner les jobs suivants même issus de pull requests non liés. L’option --once et la boucle d’orchestration destroy/recrée sont l’application technique du principe d’éphémérité.
Notez que GitHub reconnaît que cette règle “peut ne pas être aussi efficace qu’espéré, car il n’y a aucun moyen de garantir qu’un runner auto-hébergé ne fasse qu’un seul job.” Ce n’est pas une raison pour la ignorer — cela relève quand même la barre.
La couche d’autoscaling (mise à jour 2026)
GitHub a introduit en début 2026 le Runner Scale Set Client en aperçu public — un module autonome en Go permettant aux équipes de construire des solutions d’autoscaling personnalisées pour les runners GitHub Actions sans Kubernetes. Cela diffère de l’Actions Runner Controller (ARC), qui reste la voie recommandée pour l’autoscaling basé sur Kubernetes.
Pour les environnements bare-metal sans Kubernetes, le Scale Set Client permet de construire une orchestration événementielle qui : - Écoute les événements de mise en file d’attente via les API de scale set de GitHub - Lance un nouveau conteneur de runner sur du matériel disponible - Détruit le conteneur après la fin du job
Cela offre une capacité élastique réelle sur une infrastructure fixe — le matériel inactif ne coûte rien en minutes de runner, et vous ne consommez de capacité que lorsque des builds tournent.
Vérification du coût (2026)
L’économie des runners auto-hébergés a changé début 2026. GitHub a introduit une facturation de 0,002 $ par minute pour l’utilisation des runners auto-hébergés dans les dépôts privés, à partir du 1er mars 2026, couvrant le plan de contrôle Actions (orchestration, planification, automatisation). Cette charge s’applique peu importe où vos runners sont hébergés — votre data center, AWS, ou bare metal sous votre bureau.
Les dépôts publics restent gratuits. GitHub Enterprise Server n’est pas affecté.
Concrètement : les runners auto-hébergés ne sont plus gratuits à 100 % du côté de GitHub même si votre coût de calcul est nul. Pour une équipe utilisant 3 000 minutes par mois au-delà du quota gratuit, la charge plateforme ajoute 2 $ par mois — négligeable pour la majorité des équipes. Pour des environnements CI à volume élevé, le calcul favorise toujours le bare metal pour les charges intensives, mais il faut intégrer cette charge dans votre modèle de coût.
Les prix des runners hébergés par GitHub ont aussi chuté d’environ 40 % au 1er janvier 2026, ce qui modifie certaines comparaisons avec des alternatives managées.
| Dimension | Runners GitHub hébergés | Bare-Metal via Tunnel inversé |
|---|---|---|
| Coût de calcul | Par minute (réduit d’environ 40 % en janv. 2026) | Capex matériel fixe ; coût marginal nul |
| Coût plateforme | Inclus dans le prix du runner | 0,002 $/min pour repos privés (depuis mars 2026) |
| Exposition entrante | Sur l’infrastructure GitHub | Aucun port entrant requis |
| Contrôle matériel | Tailles T-shirt standard | Contrôle total : Apple Silicon, clusters GPU, FPGAs |
| Isolation | VM éphémères gérées | Votre responsabilité ; nécessite un durcissement explicite |
| Coût pour repo public | Gratuit | Gratuit |
Performance réelle : Cas de build macOS
Prenons l’exemple classique : une équipe de développement mobile compilant des applications iOS. Les runners macOS alimentés par M2 de GitHub (disponibles sur macos-latest-xlarge et autres étiquettes depuis fin 2025) sont nettement plus rapides que les anciennes options cloud — mais ils fonctionnent toujours dans un environnement virtualisé avec ressources partagées.
Un Mac Studio idle avec une puce M2 Ultra dans un bureau d’entreprise, configuré comme runner auto-hébergé via un Tunnel Cloudflare, offre une exécution native bare-metal. Les temps de build pour une application iOS représentative passent de 30–45 minutes sur des runners cloud à moins de 4 minutes sur du matériel local. Sur une année de développement actif, cette différence représente des milliers d’heures de temps d’ingénierie récupéré.
L’architecture décrite dans cet article rend ce matériel accessible à GitHub Actions sans modifier votre pare-feu d’entreprise.
Résumé
La combinaison de tunnels inversés en sortie uniquement, d’environnements de runner conteneurisés éphémères, et d’une authentification basée sur l’identité résout la tension centrale entre sécurité et performance dans le CI/CD auto-hébergé :
- Aucun port entrant. Le runner initie toutes les connexions en sortie sur le port 443.
- Aucun credential persistant sur disque. Les tokens d’enregistrement sont récupérés dynamiquement et expirent immédiatement.
- Aucun état persistant. L’option
--onceet la destruction/reconstruction du conteneur garantissent que chaque job s’exécute dans un environnement propre. - Aucune propagation latérale. La segmentation VLAN et les règles de pare-feu explicites limitent le runner même s’il est compromis.
- Aucun coût surprise. La charge plateforme de 2026 est faible pour la majorité, mais doit être intégrée dans votre modélisation.
e Maintenez votre version de runner à partir de v2.329.0 — GitHub bloque les versions plus anciennes lors de l’enregistrement depuis mars 2026. Vérifiez la page des releases de actions/runner et mettez à jour votre ARG RUNNER_VERSION dans le Dockerfile.
L’architecture demande une configuration intentionnelle et une maintenance continue. Mais pour les équipes avec des charges de travail intensives, du matériel spécialisé, ou des exigences de sécurité nécessitant une exécution locale, c’est une voie bien comprise et éprouvée en production.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.