infra / github-actions
Sécurité des pipelines (niveau expert)
Explication
Ce que vous allez apprendre
- Comprendre pourquoi un pipeline CI/CD est une cible de choix pour un attaquant
- Appliquer le principe du moindre privilège avec
permissions: {}par défaut - Épingler les actions tierces par SHA complet plutôt que par tag mutable
- Utiliser OIDC pour éliminer le besoin de stocker une clé cloud statique
- Identifier et éviter le piège de
pull_request_targetmal utilisé avec un checkout de fork
Dans quel contexte ?
Un dépôt open source utilise pull_request_target pour commenter automatiquement chaque pull request externe avec les résultats d'un linter, et checkoute le code du fork pour l'analyser. Un contributeur malveillant propose une pull request dont le code, une fois exécuté par ce workflow, lit et exfiltre discrètement tous les secrets du dépôt de base vers un serveur externe — parce que pull_request_target exécute le workflow avec les permissions du dépôt de base, pas celles, restreintes, du fork. Cette dernière leçon adopte le point de vue d'un attaquant pour identifier ce genre de piège avant qu'il ne soit exploité.
Un pipeline CI/CD est une cible
D'abord, après avoir appris à construire des pipelines fonctionnels dans les leçons précédentes, cette dernière leçon adopte un point de vue différent : celui d'un attaquant.
Pourquoi un pipeline est une cible de choix
Un pipeline CI/CD a souvent accès à des secrets puissants, déploiement, cloud, registres, et exécute du code automatiquement, ce qui en fait une cible de choix.
Ce que ça implique pour toi
Sécuriser un pipeline n'est pas une option "avancée", c'est une nécessité dès qu'il touche à la production.
| Pratique de sécurité | Ce qu'elle empêche |
|---|---|
permissions: {} par défaut | Un job avec bien plus de droits que nécessaire |
| Épinglage par SHA complet | Une action tierce modifiée après coup via un tag mutable |
| OIDC plutôt qu'une clé cloud statique | Une clé d'accès longue durée qui pourrait fuiter |
Éviter pull_request_target + checkout du fork | L'exécution de code externe avec les secrets du dépôt de base |
Le principe du moindre privilège, appliqué à chaque job
Par défaut, un workflow GitHub Actions dispose d'un ensemble de permissions relativement larges sur le dépôt.
La bonne pratique à adopter
Elle consiste à partir de zéro permission, permissions: {}, et à n'accorder, job par job, que le strict nécessaire, par exemple seulement lire le contenu du dépôt pour un simple build.
Le bénéfice de cette rigueur
Ainsi, même si un step est compromis, les dégâts possibles restent limités à ce que ce job précis pouvait faire.
Prérequis
Cette dernière leçon suppose l'ensemble du cours déjà parcouru, en particulier les secrets (leçon 7) et les environments (leçon 8) : elle en révèle les implications de sécurité les plus avancées.
Pourquoi épingler par SHA plutôt que par tag
Utiliser une action tierce via un tag comme @v4 semble pratique, mais un tag est mutable : rien n'empêche techniquement son propriétaire, ou un attaquant ayant compromis son compte, de le faire pointer vers un code différent après coup.
La solution : le SHA complet
Épingler par SHA complet, l'empreinte figée d'un commit précis, garantit que le code exécuté est exactement celui qui a été vérifié, une fois pour toutes.
OIDC : éliminer le besoin même de stocker un secret
Plutôt que de stocker une clé d'accès cloud statique et longue durée dans un secret GitHub, qui pourrait fuiter ou être mal révoquée, OIDC permet au job de demander un jeton temporaire à GitHub.
Comment ce jeton temporaire fonctionne
Le fournisseur cloud accepte ce jeton grâce à une relation de confiance préconfigurée. Aucune clé permanente ne transite jamais : c'est structurellement plus sûr qu'un secret, aussi bien protégé soit-il.
Le piège le plus dangereux : pull_request_target mal utilisé
Ce déclencheur exécute le workflow avec les secrets et permissions du dépôt de base, pas ceux, restreints, du fork.
Le scénario d'attaque précis
Si ce workflow va en plus récupérer, checkout, le code proposé par le fork et l'exécute, un contributeur externe malveillant peut faire exécuter son propre code avec accès à tes secrets.
Piège fréquent
Ne jamais combiner pull_request_target avec un checkout du code du fork ET un accès aux secrets dans le même job. C'est la combinaison précise qui transforme une simple pull request externe en exécution de code arbitraire avec accès à des secrets puissants — l'une des vulnérabilités les plus documentées et les plus réellement exploitées sur GitHub Actions.
La règle à retenir absolument
Ne jamais combiner ce déclencheur avec un checkout du code du fork et un accès aux secrets dans le même job.
Ce cours touche ici à sa fin : tu es maintenant capable de construire, automatiser et sécuriser des pipelines GitHub Actions, du premier workflow jusqu'à la production.
Commandes & code
Sécurité des pipelines CI/CD
# 1. Principe du moindre privilège : permissions minimales par défaut, élargies au cas par cas
name: Secure Pipeline
on: push
permissions: {} # aucune permission par défaut au niveau du workflow entier
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # juste ce qu'il faut pour checkout le code
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build# 2. Épingler les actions tierces par SHA complet, pas par tag mutable (protège d'un tag compromis)
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.1, SHA figé
# PLUTÔT QUE :
# - uses: actions/checkout@v4 <- un tag peut être déplacé/compromis a posteriori# 3. OIDC : authentification cloud SANS stocker de clé d'accès longue durée en secret
permissions:
id-token: write # requis pour qu'un job demande un jeton OIDC à GitHub
contents: read
jobs:
deploy-aws:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: eu-west-3
# Le rôle AWS fait confiance à GitHub via un "trust policy" scoping à ce repo/cette branche
# -> aucune clé d'accès AWS statique ne transite jamais par un secret GitHub# 4. Ne jamais faire confiance aveuglément à un "pull_request" venant d'un fork
on:
pull_request_target: # ATTENTION : s'exécute avec les secrets du dépôt de base !
types: [opened, synchronize]
jobs:
# Piège classique : checkout du code du FORK puis exécution avec des secrets -> fuite possible
# Règle : ne JAMAIS combiner "pull_request_target" + checkout du HEAD du fork + accès aux secrets
safe-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }} # code de la base, PAS celui du fork# 5. Scanner les dépendances et le pipeline lui-même
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Audit des dépendances npm
run: npm audit --audit-level=high
- name: Scan des workflows avec zizmor
run: |
pip install zizmor
zizmor .github/workflows/
- name: CodeQL (analyse statique de sécurité)
uses: github/codeql-action/analyze@v3# Dependabot : garde les actions tierces et dépendances à jour automatiquement
# .github/dependabot.ymlversion: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"Résumé
permissions: {}par défaut puis élargissement ciblé par job = moindre privilège systématique.- Épingler les actions tierces par SHA complet évite qu'un tag compromis compromette le pipeline.
- OIDC (
id-token: write+ rôle cloud) élimine le besoin de clés d'accès statiques en secret. pull_request_target+ checkout du fork + secrets = combinaison dangereuse à ne jamais assembler.- Dependabot + scan des workflows (zizmor) + CodeQL détectent les dérives avant qu'elles soient exploitées.
Exercices pratiques
Mission : une fuite de secrets via une pull request externe
Objectif : Identifier et corriger la combinaison dangereuse pull_request_target + checkout du fork + secrets dans un workflow public.
Contexte
Un dépôt open source utilise pull_request_target pour commenter automatiquement chaque pull request externe avec les résultats d'un linter. Le job checkoute github.event.pull_request.head.sha (le code proposé par le fork) et utilise secrets.NPM_TOKEN pour installer certains outils dans ce même job. Le workflow utilise aussi actions/checkout@v4, un tag mutable, sur l'ensemble de ses jobs, avec les permissions par défaut, relativement larges, du workflow.