infra / terraform
CI/CD avec Terraform : plan et apply automatisés
Explication
Ce que vous allez apprendre
- Construire un pipeline GitHub Actions complet pour Terraform
- Comprendre pourquoi le
planseul doit s'exécuter sur une Pull Request, jamais l'apply - Sauvegarder un plan avec
-outpour 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ènement | Action CI/CD | Pourquoi |
|---|---|---|
| Pull Request ouverte | plan uniquement | Permet la revue avant fusion, sans risque |
Fusion sur main | apply (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
# .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 }}# 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# 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.jsonRé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
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.