Retour au cours

infra / terraform

Sécurité : secrets et state chiffré

Leçon 151 exercice

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 .gitignore complet 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.

ProtectionCe qu'elle couvreCe qu'elle NE couvre PAS
sensitive = trueMasque l'affichage CLI (plan, apply)Le contenu du fichier state lui-même
.gitignore sur *.tfstateEmpêche le commit accidentel dans GitUn state déjà présent sur un backend non chiffré
Chiffrement du backend S3Protège le contenu au repos sur S3Un 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é

bash
# 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.
bash
# 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é
hcl
# --- 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
}
bash
# 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
hcl
# --- 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
}
bash
# --- 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
bash
# .gitignore recommandé pour tout projet Terraform
cat >> .gitignore << 'EOF'
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl.bak
*.tfvars
!example.tfvars
crash.log
EOF
hcl
# 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

1 disponible
1

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.

Résoudre l’exercice →