Retour au cours

infra / github-actions

Sécurité des pipelines (niveau expert)

Leçon 131 exercice

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_target mal 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éfautUn job avec bien plus de droits que nécessaire
Épinglage par SHA completUne action tierce modifiée après coup via un tag mutable
OIDC plutôt qu'une clé cloud statiqueUne clé d'accès longue durée qui pourrait fuiter
Éviter pull_request_target + checkout du forkL'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

yaml
# 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
yaml
# 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
yaml
# 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
yaml
# 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
yaml
# 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
bash
# Dependabot : garde les actions tierces et dépendances à jour automatiquement
# .github/dependabot.yml
yaml
version: 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

1 disponible
1

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.

Résoudre l’exercice →