Aller au contenu

TP 33 : Chiffrer le state avec OpenTofu

À 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 encryption d’OpenTofu avec un key provider PBKDF2 ;
  • migrer un state non chiffré vers un state chiffré via une méthode unencrypted de repli ;
  • vérifier qu’un state exfiltré est illisible sans la passphrase ;
  • rendre le chiffrement obligatoire avec enforced.
  • OpenTofu >= 1.7 (le chiffrement de state est disponible depuis la 1.7).
  • La CLI tofu installée.

Le chiffrement de state est une fonctionnalité d’OpenTofu, absente de Terraform. Ce TP utilise donc tofu.

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.

versions.tf
terraform {
required_version = ">= 1.7"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
main.tf
resource "random_password" "db" {
length = 24
special = true
}
Fenêtre de terminal
tofu init
tofu apply -auto-approve
grep result terraform.tfstate # le secret est lisible en clair
variables.tf
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 :

Fenêtre de terminal
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.

encryption.tf
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 fallback autorise la lecture de l’ancien state non chiffré pendant la migration.
Fenêtre de terminal
tofu apply -auto-approve
cat terraform.tfstate

Le 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 :

Fenêtre de terminal
grep -c result terraform.tfstate || echo "plus de champ result lisible"

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
}
}
}
Fenêtre de terminal
tofu plan # fonctionne avec la passphrase fournie

Un attaquant qui récupère le fichier de state (ou l’objet S3) sans la passphrase ne peut rien en tirer.

Fenêtre de terminal
# Copie « exfiltrée » du state
cp terraform.tfstate /tmp/state-vole.json
file /tmp/state-vole.json # data / non-JSON exploitable
grep -c "password" /tmp/state-vole.json || echo "aucun secret lisible"
# Sans la passphrase, la lecture échoue
unset TF_VAR_state_passphrase
tofu show 2>&1 | head -n 3 # erreur : impossible de déchiffrer

Rétablissez la passphrase pour continuer à travailler :

Fenêtre de terminal
export TF_VAR_state_passphrase="InfraBank-State-2026!"
tofu show | head -n 5
  • Le state contient encrypted_data et non plus les secrets en clair.
  • Sans la bonne passphrase, tofu show échoue à déchiffrer.
  • Avec enforced = true, un state non chiffré est refusé.
Fenêtre de terminal
tofu destroy -auto-approve
rm -f /tmp/state-vole.json