infra / terraform
Sécurité : secrets et state chiffré
Explication
Ce que vous allez apprendre
- Comprendre pourquoi le state Terraform contient TOUJOURS les secrets en clair
- Injecter un secret sans jamais l'écrire dans un fichier versionné
- Lire un secret directement depuis un gestionnaire dédié via une data source
- Protéger le state lui-même comme un actif aussi sensible que les secrets qu'il contient
- Écrire un
.gitignorecomplet pour un projet Terraform
Dans quel contexte ?
Un audit de sécurité interne demande à l'équipe de prouver qu'aucun mot de passe de base de données ne fuit dans le code source. L'équipe est confiante : le code utilise bien sensitive = true sur la variable du mot de passe (vue dans la leçon sur les variables). L'audit révèle pourtant que ce mot de passe est parfaitement lisible dans le fichier terraform.tfstate.
D'abord, il faut affronter un fait déstabilisant sur le state
Le state Terraform stocke TOUS les attributs de toutes les ressources qu'il gère, y compris ceux marqués sensitive, EN CLAIR dans son JSON interne. sensitive = true ne masque que l'AFFICHAGE dans le terminal pendant un plan ou un apply — il ne chiffre absolument rien dans le fichier lui-même.
Cette réalité change complètement la façon d'aborder la sécurité : protéger le CODE ne suffit pas, il faut aussi protéger le STATE avec le même niveau de rigueur.
| Protection | Ce qu'elle couvre | Ce qu'elle NE couvre PAS |
|---|---|---|
sensitive = true | Masque l'affichage CLI (plan, apply) | Le contenu du fichier state lui-même |
.gitignore sur *.tfstate | Empêche le commit accidentel dans Git | Un state déjà présent sur un backend non chiffré |
| Chiffrement du backend S3 | Protège le contenu au repos sur S3 | Un accès direct via des permissions IAM trop larges |
Prérequis
Cette leçon suppose une bonne compréhension du state et des backends distants (leçons précédentes) : la sécurité des secrets en dépend directement, puisque c'est précisément là que les secrets finissent stockés.
Une fois ce constat posé, la première règle devient évidente
Ne jamais écrire un mot de passe en dur dans un fichier .tf versionné dans Git : une fois committé, ce secret reste récupérable indéfiniment dans l'historique Git, même après suppression apparente du fichier. La variable doit rester sans default, forçant à fournir le secret explicitement à chaque exécution.
Piège fréquent
Fournir un secret via -var="db_password=..." en ligne de commande laisse une trace dans l'historique bash local ET potentiellement dans les logs d'exécution de la CI. Préférer une variable d'environnement TF_VAR_ reste plus sûr, mais la meilleure option reste de ne jamais faire transiter le secret par Terraform lui-même.
Voici comment on résout ce problème le plus proprement possible
Une data source pointant directement vers un gestionnaire de secrets (AWS Secrets Manager, dans l'exemple de cette leçon) permet à Terraform de lire le secret au moment de l'apply, sans qu'il ne transite jamais par un fichier .tfvars, une variable d'environnement visible dans les logs, ou une commande CLI enregistrée dans l'historique.
Il reste un dernier réflexe indispensable, souvent oublié
Bonne pratique
Traite le bucket de state avec la même rigueur qu'une base de données de production : chiffrement au repos, blocage explicite de l'accès public, permissions IAM strictes limitées aux rôles CI et administrateurs, et versioning activé pour pouvoir revenir en arrière en cas de state corrompu ou de clé compromise à révoquer.
Un .gitignore complet (*.tfstate, .terraform/, *.tfvars sauf exemples) reste la toute première ligne de défense, à mettre en place dès le premier commit d'un projet Terraform, jamais après coup.
La sécurité des secrets maîtrisée, la toute dernière leçon de ce cours rassemble tous ces principes dans une structuration de projet complète, digne d'une organisation avec plusieurs équipes et environnements.
Commandes & code
Sécurité : secrets et state chiffré
# LE PROBLÈME FONDAMENTAL : le state Terraform contient TOUS les attributs des ressources,
# y compris les valeurs marquées "sensitive" (mots de passe, clés API...), EN CLAIR dans le JSON.# Vérifier concrètement : un secret apparaît en clair dans le state, même s'il est masqué à l'écran
terraform show -json | grep -i password # peut révéler des secrets si le state n'est pas protégé# --- Règle n°1 : ne JAMAIS coder un secret en dur dans un fichier .tf versionné ---
# MAUVAIS :
resource "aws_db_instance" "mauvais" {
password = "MotDePasse123!" # committé dans Git = fuite garantie
}
# BON : injecter depuis une variable d'environnement ou un gestionnaire de secrets
variable "db_password" {
type = string
sensitive = true
# PAS de "default" pour un secret : force à le fournir explicitement à chaque apply
}
resource "aws_db_instance" "bon" {
password = var.db_password
}# Fournir le secret sans jamais l'écrire dans un fichier versionné
export TF_VAR_db_password=$(aws secretsmanager get-secret-value \
--secret-id prod/db/password --query SecretString --output text)
terraform apply# --- Encore mieux : lire le secret DIRECTEMENT depuis un gestionnaire, via une data source ---
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password"
}
resource "aws_db_instance" "app" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
# Le secret ne transite JAMAIS par un fichier .tfvars ou une variable d'env visible dans les logs CI
}# --- Protéger le STATE lui-même (là où les secrets finissent de toute façon) ---
# 1. Backend S3 avec chiffrement au repos (encrypt = true, SSE-KMS avec une clé dédiée)
# 2. Bucket JAMAIS public : bloquer explicitement l'accès public (aws_s3_bucket_public_access_block)
# 3. Permissions IAM strictes sur le bucket de state : seuls les rôles CI/admin autorisés
# 4. Versioning activé : permet un rollback si le state est corrompu ou une clé compromise à révoquer
# 5. .gitignore : ne JAMAIS committer terraform.tfstate, terraform.tfstate.backup, *.tfvars contenant des secrets# .gitignore recommandé pour tout projet Terraform
cat >> .gitignore << 'EOF'
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl.bak
*.tfvars
!example.tfvars
crash.log
EOF# Chiffrement du state EN PLUS du chiffrement S3 (défense en profondeur, Terraform 1.10+)
# state encryption natif (expérimental selon version) : chiffre le contenu même AVANT écriture sur le backend
terraform {
encryption {
key_provider "pbkdf2" "ma_cle" {
passphrase = var.state_encryption_passphrase
}
method "aes_gcm" "defaut" {
keys = key_provider.pbkdf2.ma_cle
}
state {
method = method.aes_gcm.defaut
}
}
}Résumé
- Le state contient TOUJOURS les secrets en clair, quel que soit
sensitive = true(qui ne masque que l'affichage CLI). - Ne jamais committer de secrets en dur ; les injecter via variables d'environnement ou, mieux, via un gestionnaire de secrets en data source.
- Protéger le state lui-même est aussi important que protéger les secrets : chiffrement au repos, IAM strict, bucket privé, versioning.
Exercices pratiques
Mission : colmater une fuite de mot de passe après un audit
Objectif : Diagnostiquer pourquoi un secret marqué sensitive fuit malgré tout, et corriger la chaîne complète du code jusqu'au state.
Contexte
L'auditeur de sécurité vient de confirmer ses soupçons : git log -p sur le dépôt de l'équipe révèle un ancien commit contenant password = "MotDePasse123!" en dur dans un aws_db_instance, et le fichier terraform.tfstate local traîne encore, non ignoré, dans le dernier commit. La variable db_password est pourtant bien déclarée avec sensitive = true depuis six mois. L'équipe doit comprendre pourquoi ce marquage n'a rien empêché et reconstruire une chaîne propre, du code jusqu'au backend.