Retour au cours

infra / terraform

CI/CD avec Terraform : plan et apply automatisés

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Construire un pipeline GitHub Actions complet pour Terraform
  • Comprendre pourquoi le plan seul doit s'exécuter sur une Pull Request, jamais l'apply
  • Sauvegarder un plan avec -out pour garantir que l'apply exécute exactement ce qui a été revu
  • Utiliser des environnements GitHub avec approbation manuelle avant un apply de production
  • Authentifier la CI via OIDC plutôt que via des clés AWS statiques

Dans quel contexte ?

Jusqu'ici, chaque développeur de l'équipe lance terraform apply depuis sa propre machine — un mode de fonctionnement qui a permis d'apprendre les bases, mais qui devient rapidement risqué : rien ne garantit qu'un plan a bien été relu avant application, et les identifiants cloud personnels de chacun doivent avoir des droits d'écriture étendus sur l'infrastructure.

D'abord, il faut poser une règle de sécurité fondamentale

Sur une Pull Request non fusionnée, un pipeline CI/CD ne doit JAMAIS exécuter d'apply automatique. Il se contente de lancer terraform plan, une opération strictement en lecture qui ne modifie rien dans le cloud, et qui permet à l'équipe de relire le changement proposé avant même de fusionner le code.

Ce n'est qu'une fois la Pull Request fusionnée sur la branche principale que le pipeline peut envisager un apply réel — et encore, généralement après une approbation humaine supplémentaire.

ÉvènementAction CI/CDPourquoi
Pull Request ouverteplan uniquementPermet la revue avant fusion, sans risque
Fusion sur mainapply (souvent après approbation)Le changement a déjà été revu et accepté

Prérequis

Cette leçon suppose une bonne maîtrise du cycle init/plan/apply et du fonctionnement d'un backend distant (leçons précédentes), puisque le pipeline reproduit exactement ces étapes de façon automatisée.

Une fois cette règle posée, il reste un problème de cohérence : le plan revu est-il bien celui appliqué ?

Sans précaution, terraform apply recalcule son propre plan au moment de s'exécuter — qui pourrait légèrement différer de celui qui a été relu sur la Pull Request, si l'état du cloud a changé entre-temps. terraform plan -out=tfplan puis terraform apply tfplan élimine ce risque : l'apply rejoue EXACTEMENT le plan sauvegardé, sans le recalculer.

Piège fréquent

Une erreur fréquente en configuration CI/CD consiste à faire tourner terraform apply sans passer le fichier de plan sauvegardé (tfplan), pensant que le comportement serait identique. Sans ce fichier, Terraform recalcule un nouveau plan à cet instant précis, ce qui casse la garantie "ce qui a été revu est ce qui est appliqué".

Maintenant, une couche de sécurité supplémentaire pour la production

Bonne pratique

Les "environments" GitHub avec approbation manuelle obligatoire ajoutent un point de contrôle humain juste avant l'apply de production : même si le code est déjà fusionné sur main, une personne désignée doit cliquer explicitement "approve" avant que le déploiement réel ne parte. C'est un filet de sécurité supplémentaire, indépendant de la revue de code elle-même.

Le dernier point, souvent négligé : comment la CI s'authentifie-t-elle auprès du cloud ?

Stocker des clés AWS statiques dans des secrets GitHub fonctionne, mais représente un risque en cas de fuite (la clé reste valide indéfiniment). L'authentification OIDC (OpenID Connect) élimine ce risque : GitHub Actions prouve son identité directement au cloud via un jeton temporaire, sans qu'aucune clé secrète durable n'ait besoin d'être stockée nulle part.

Le pipeline désormais fiable et sécurisé, la prochaine leçon aborde un sujet directement lié : comment gérer les secrets eux-mêmes (mots de passe, clés API) sans jamais les exposer, ni dans le code, ni dans le state.

Commandes & code

CI/CD avec Terraform : plan et apply automatisés

yaml
# .github/workflows/terraform.yml : pipeline GitHub Actions standard
name: Terraform

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  terraform:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write             # nécessaire pour commenter le plan sur la PR
    defaults:
      run:
        working-directory: environments/production

    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.9.0"

      - name: Terraform Format Check
        run: terraform fmt -check -recursive

      - name: Terraform Init
        run: terraform init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Terraform Validate
        run: terraform validate

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color -out=tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      # Sur une Pull Request : uniquement "plan", jamais d'apply automatique
      - name: Commenter le plan sur la PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "Plan Terraform généré, voir les logs du job."
            })

      # Sur main uniquement, ET après revue humaine du plan (approbation d'environnement GitHub)
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
bash
# Bonnes pratiques CI/CD niveau expert :
# - JAMAIS d'apply automatique sur une Pull Request non fusionnée (uniquement "plan", en lecture seule)
# - Utiliser des "environments" GitHub avec approbation manuelle obligatoire avant apply en production
# - Le plan sauvegardé (-out=tfplan) garantit que l'apply exécute EXACTEMENT ce qui a été revu, pas un plan recalculé
# - Rôles IAM dédiés à la CI (OIDC, pas de clés statiques en secrets si possible) avec permissions minimales
# - Scanner le plan avec un outil de policy-as-code (ex: OPA/Conftest, Sentinel) avant apply
bash
# Authentification CI moderne sans clés statiques : OIDC (OpenID Connect) GitHub Actions <-> AWS IAM
# Configure un rôle IAM qui fait confiance au fournisseur OIDC GitHub, sans secret à stocker
aws iam create-role --role-name github-actions-terraform \
  --assume-role-policy-document file://trust-policy-oidc.json

Résumé

  • Le plan doit toujours être exécuté (et éventuellement revu) sur les Pull Requests, jamais l'apply.
  • Sauvegarder le plan (-out=tfplan) puis l'appliquer tel quel évite tout écart entre ce qui a été revu et ce qui est appliqué.
  • L'authentification OIDC (sans clés AWS statiques stockées en secret) est la pratique de sécurité recommandée pour la CI.

Exercices pratiques

1 disponible
1

Mission : verrouiller un pipeline trop permissif

Objectif : Corriger un pipeline CI/CD qui applique automatiquement sur les Pull Requests et garantir la cohérence entre plan revu et plan appliqué.

Contexte

Le pipeline GitHub Actions actuel exécute terraform apply -auto-approve directement sur chaque Pull Request ouverte, avant toute fusion sur main. Une Pull Request est revue et approuvée à 9h avec un terraform plan qui ne montre aucune suppression ; le déploiement effectif n'a lieu qu'à 17h, après qu'une ressource a été modifiée manuellement dans la console cloud entre-temps.

Résoudre l’exercice →