TP 33 : Chiffrer le state avec OpenTofu
TP 33 : Chiffrer le state avec OpenTofu
Section intitulée « TP 33 : Chiffrer le state avec OpenTofu »Objectifs
Section intitulée « Objectifs »À l’issue de ce TP, vous serez capable de :
- expliquer la différence entre chiffrement côté serveur et chiffrement côté client du state ;
- configurer le bloc
encryptiond’OpenTofu avec un key provider PBKDF2 ; - migrer un state non chiffré vers un state chiffré via une méthode
unencryptedde repli ; - vérifier qu’un state exfiltré est illisible sans la passphrase ;
- rendre le chiffrement obligatoire avec
enforced.
Prérequis
Section intitulée « Prérequis »- OpenTofu
>= 1.7(le chiffrement de state est disponible depuis la 1.7). - La CLI
tofuinstallée.
Le chiffrement de state est une fonctionnalité d’OpenTofu, absente de Terraform. Ce TP utilise donc tofu.
Contexte
Section intitulée « Contexte »Le TP 32 a montré que le state contient des secrets en clair. Le chiffrement côté serveur (SSE-S3, SSE-KMS) protège au niveau du stockage, mais un lecteur du bucket obtient le contenu déchiffré. Le chiffrement côté client d’OpenTofu chiffre le state avant qu’il quitte la machine : un backend compromis ne livre qu’un blob illisible.
Étape 1 : Un state non chiffré au départ
Section intitulée « Étape 1 : Un state non chiffré au départ »terraform { required_version = ">= 1.7" required_providers { random = { source = "hashicorp/random" version = "~> 3.6" } }}resource "random_password" "db" { length = 24 special = true}tofu inittofu apply -auto-approvegrep result terraform.tfstate # le secret est lisible en clairÉtape 2 : Déclarer la passphrase
Section intitulée « Étape 2 : Déclarer la passphrase »variable "state_passphrase" { type = string sensitive = true description = "Passphrase de chiffrement du state (>= 16 caractères)."
validation { condition = length(var.state_passphrase) >= 16 error_message = "La passphrase doit faire au moins 16 caractères." }}La passphrase se fournit par variable d’environnement, jamais en dur :
export TF_VAR_state_passphrase="InfraBank-State-2026!"Étape 3 : Configurer le chiffrement avec repli de migration
Section intitulée « Étape 3 : Configurer le chiffrement avec repli de migration »Comme le state existant est en clair, OpenTofu refuse de le lire une fois le chiffrement activé, sauf si on déclare une méthode unencrypted de repli le temps de la migration.
terraform { encryption { key_provider "pbkdf2" "main" { passphrase = var.state_passphrase }
method "aes_gcm" "main" { keys = key_provider.pbkdf2.main }
# Repli temporaire pour lire le state encore en clair. method "unencrypted" "migrate" {}
state { method = method.aes_gcm.main fallback { method = method.unencrypted.migrate } } }}key_provider "pbkdf2"dérive une clé à partir de la passphrase (PBKDF2). D’autres key providers existent :aws_kms,gcp_kms, OpenBao.method "aes_gcm"est la méthode de chiffrement (AES-GCM, intègre l’authentification des données).- Le bloc
fallbackautorise la lecture de l’ancien state non chiffré pendant la migration.
Étape 4 : Migrer
Section intitulée « Étape 4 : Migrer »tofu apply -auto-approvecat terraform.tfstateLe state est désormais un enveloppe chiffrée : vous voyez encrypted_data et encryption_version, plus le JSON en clair. Vérifiez que le secret a disparu de la forme lisible :
grep -c result terraform.tfstate || echo "plus de champ result lisible"Étape 5 : Retirer le repli et durcir
Section intitulée « Étape 5 : Retirer le repli et durcir »La migration faite, supprimez la méthode unencrypted et le fallback, puis activez enforced pour refuser tout state non chiffré :
# encryption.tf (version durcie)terraform { encryption { key_provider "pbkdf2" "main" { passphrase = var.state_passphrase }
method "aes_gcm" "main" { keys = key_provider.pbkdf2.main }
state { method = method.aes_gcm.main enforced = true } }}tofu plan # fonctionne avec la passphrase fournieÉtape 6 : Simuler un backend compromis
Section intitulée « Étape 6 : Simuler un backend compromis »Un attaquant qui récupère le fichier de state (ou l’objet S3) sans la passphrase ne peut rien en tirer.
# Copie « exfiltrée » du statecp terraform.tfstate /tmp/state-vole.jsonfile /tmp/state-vole.json # data / non-JSON exploitablegrep -c "password" /tmp/state-vole.json || echo "aucun secret lisible"
# Sans la passphrase, la lecture échoueunset TF_VAR_state_passphrasetofu show 2>&1 | head -n 3 # erreur : impossible de déchiffrerRétablissez la passphrase pour continuer à travailler :
export TF_VAR_state_passphrase="InfraBank-State-2026!"tofu show | head -n 5Vérification
Section intitulée « Vérification »- Le state contient
encrypted_dataet non plus les secrets en clair. - Sans la bonne passphrase,
tofu showéchoue à déchiffrer. - Avec
enforced = true, un state non chiffré est refusé.
Nettoyage
Section intitulée « Nettoyage »tofu destroy -auto-approverm -f /tmp/state-vole.json