Les secrets hors de l'état
Pourquoi
Le premier cours a posé le problème sans le résoudre entièrement : l'état de Terraform est un inventaire en clair de tout ce que les ressources ont comme attributs, et un mot de passe de base de données en fait partie. Marquer une variable sensitive masque la valeur dans les affichages, mais pas dans le fichier. Quiconque peut lire l'état (le bucket Object Storage, une sauvegarde, la sortie d'un terraform state pull, l'artefact d'un plan dans la CI) lit le secret. Dans une organisation, l'état circule bien plus que prévu : il est relu par la CI, copié pour un diagnostic, analysé par des outils.
Deux mécanismes récents ont changé la donne. Les valeurs éphémères (Terraform 1.10) n'existent que le temps d'une opération et ne sont écrites ni dans le plan ni dans l'état. Les arguments en écriture seule (Terraform 1.11) permettent à une ressource de recevoir un secret sans le garder. OpenTofu les a repris dans sa version 1.11, et propose en plus, depuis la 1.7, le chiffrement de l'état, qu'il faut connaître même si l'on reste sur Terraform. Cette leçon explique à quoi sert chaque outil, ce qu'il ne couvre pas, et comment les combiner pour que le dépôt signalements-iac ne contienne aucun secret nulle part : ni dans le code, ni dans l'état, ni dans le plan.
Avec une réserve d'emblée : tout n'est pas encore possible partout. Une ressource ne reçoit un secret en écriture seule que si son fournisseur a prévu l'argument. Pour le reste, la protection de l'état redevient la seule barrière, et c'est pourquoi on la traite aussi.
Les concepts
Quatre niveaux de protection
| Niveau | Ce qu'il protège | Ce qu'il ne protège pas |
|---|---|---|
sensitive | l'affichage dans les plans et les sorties | l'état, le plan enregistré, les sorties JSON |
| valeur éphémère | l'état et le plan (la valeur n'y est jamais écrite) | les endroits où le fournisseur la transmet ensuite (son API, un journal) |
| argument en écriture seule | l'état et le plan, pour cet argument | tout autre argument de la ressource |
| chiffrement de l'état (OpenTofu) | le fichier d'état et les plans au repos | quiconque dispose de la clé, et le contenu pendant l'exécution |
Le premier niveau est de l'hygiène. Les trois suivants sont des protections réelles, à utiliser en couches : on évite d'abord que le secret entre dans l'état, et pour ce qui y entre malgré tout, on chiffre.
Les valeurs éphémères
Une valeur éphémère est une valeur qui n'existe que pendant une phase de l'exécution. Terraform ne la persiste ni dans le plan ni dans l'état. Elle peut être :
- une variable d'entrée déclarée
ephemeral = true(Terraform 1.10, OpenTofu 1.11) ; - une sortie de module enfant déclarée
ephemeral = true; - le résultat d'une ressource éphémère, un bloc
ephemeralqu'on ouvre au début d'une opération et qu'on referme à la fin (Terraform 1.10, OpenTofu 1.11) ; - une valeur locale qui dérive d'une valeur éphémère (elle le devient à son tour : l'éphémérité se propage).
Les endroits où une valeur éphémère est acceptée sont limités, et c'est la garantie : une configuration de fournisseur, un bloc provisioner ou connection, une autre valeur éphémère, et un argument en écriture seule. Partout ailleurs, Terraform refuse, dès la validation, plutôt que de persister silencieusement. On verra le message d'erreur plus bas.
Une ressource éphémère est un bloc ephemeral "type" "nom" { ... }, distinct de resource et de data. Elle n'a pas de cycle de vie en plusieurs étapes : Terraform l'ouvre (« Opening »), l'utilise, la referme (« Closing »), à chaque phase d'évaluation qui en a besoin. Elle ne laisse aucune trace dans l'état. Le fournisseur random en offre deux (random_password, random_bytes) ; le fournisseur Scaleway en 2.84 en offre sept, dont scaleway_secret_version (lire un secret) et scaleway_key_manager_decrypt.
Les arguments en écriture seule
Un argument en écriture seule (write-only) est un argument de ressource que le fournisseur reçoit pendant l'opération et ne renvoie jamais : Terraform ne l'enregistre pas dans l'état (il y vaut null) ni dans le plan, qui l'affiche (write-only attribute). Il est apparu avec Terraform 1.11 ; OpenTofu l'a ajouté dans la 1.11. C'est un attribut marqué write_only dans le schéma du fournisseur.
Le prix est évident : sans trace de la valeur, Terraform ne peut pas détecter qu'elle a changé. Un argument en écriture seule n'entre pas dans la comparaison du plan. Les fournisseurs règlent le problème avec un second argument, un numéro de version : password_wo_version accompagne password_wo, data_wo_version accompagne data_wo. On incrémente le numéro pour dire « la valeur a changé, renvoyez-la ». C'est la mécanique de rotation, détaillée plus bas.
Dans le fournisseur Scaleway 2.84, les arguments en écriture seule couvrent les mots de passe de plusieurs produits (scaleway_rdb_instance.password_wo, scaleway_rdb_user.password_wo, scaleway_redis_cluster.password_wo, scaleway_mongodb_instance.password_wo, etc.) et le contenu d'un secret (scaleway_secret_version.data_wo). Il n'y a pas de règle générale : on consulte le schéma (terraform providers schema -json) ou la page de la ressource.
Source de données ou ressource éphémère ?
Pour lire un secret existant (le mot de passe de la base, stocké dans Secret Manager), Terraform offre deux voies :
- la source de données
data "scaleway_secret_version"lit le secret et l'écrit dans l'état, marqué sensible mais présent ; - la ressource éphémère
ephemeral "scaleway_secret_version"lit le secret à chaque opération et ne l'écrit nulle part.
Dès qu'elle est disponible, la seconde est la bonne, et la première un défaut à signaler en revue de code.
Chiffrer l'état avec OpenTofu
Quand un secret ne peut pas être gardé hors de l'état (le fournisseur n'offre aucun argument en écriture seule, ou la ressource renvoie des identifiants à la création), la défense est de chiffrer le fichier lui-même. OpenTofu le propose nativement depuis la version 1.7, dans un bloc terraform { encryption { ... } } : on y déclare un fournisseur de clé (d'où vient la clé), une méthode (comment on chiffre) et les cibles, state et plan. Terraform n'a pas d'équivalent : il compte sur le chiffrement du stockage (serveur) et sur le contrôle d'accès.
En pratique
Les démonstrations utilisent Terraform 1.16.1 et les fournisseurs random et terraform_data, sans rien créer dans le cloud. Les parties Scaleway ont été validées contre le schéma du fournisseur (terraform init et terraform validate), jamais appliquées.
Le défaut : un secret dans l'état
Pour mémoire, un mot de passe généré par la ressource classique :
resource "random_password" "classique" {
length = 16
special = false
}Le plan et terraform show le masquent (result = (sensitive value)), mais le fichier d'état, lui, le contient :
$ jq -c '.resources[] | select(.type=="random_password") | .instances[0].attributes | {result, bcrypt_hash}' terraform.tfstate
{"result":"w1kUcaUD3QvcYNLp","bcrypt_hash":"$2a$10$ugKF7QC1.7YLcDtzTA4/z.pQAzeiOJ5dUlq6b.7waGFLGT7BRzH5y"}
La valeur et son empreinte bcrypt sont là, lisibles par quiconque lit l'état. C'est le cas que nous voulons éliminer.
Une ressource éphémère, et le refus de Terraform
On déclare la version éphémère du même générateur :
ephemeral "random_password" "db" {
length = 16
special = false
}Si l'on essaie de la mettre dans un argument ordinaire, Terraform refuse dès la validation :
$ terraform validate -no-color
Error: Invalid use of ephemeral value
with terraform_data.essai,
on main.tf line 20, in resource "terraform_data" "essai":
20: input = ephemeral.random_password.db.result
Ephemeral values are not valid for "input", because it is not a write-only
attribute and must be persisted to state.
C'est le garde-fou central : le message dit pourquoi (l'argument « must be persisted to state »). Un secret éphémère ne peut aller que là où rien n'est conservé.
Une variable éphémère : ce qui entre dans le plan et l'état
Soit une ressource qui utilise un jeton passé en variable éphémère, dans un fournisseur (ici, un local-exec, un des endroits autorisés) :
variable "jeton" {
type = string
ephemeral = true
}
variable "version_jeton" {
type = number
default = 1
}
resource "terraform_data" "service" {
triggers_replace = var.version_jeton
provisioner "local-exec" {
command = "echo \"longueur du jeton reçu : $${#JETON}\""
environment = { JETON = var.jeton }
}
}On fournit la valeur par l'environnement et on enregistre le plan :
$ export TF_VAR_jeton="jeton-de-demonstration-0042"
$ terraform plan -no-color -out=t.plan
...
Plan: 1 to add, 0 to change, 0 to destroy.
Saved the plan to: t.plan
$ terraform apply -no-color t.plan
terraform_data.service: Creating...
terraform_data.service: Provisioning with 'local-exec'...
terraform_data.service (local-exec): (output suppressed due to ephemeral value in config)
terraform_data.service: Creation complete after 0s [id=3cc59b61-e146-5661-acbd-859a04068d9e]
Deux constats. Terraform supprime la sortie du provisioner (« output suppressed due to ephemeral value in config »), parce qu'elle pourrait contenir le secret : c'est une précaution voulue. Et la valeur n'est ni dans le plan, ni dans l'état. Une recherche dans les deux fichiers (le plan enregistré est une archive ZIP) :
$ python3 - <<'EOF'
import zipfile
z = zipfile.ZipFile("t.plan")
print("occurrences dans le plan :", sum(z.read(n).count(b"jeton-de-demonstration") for n in z.namelist()))
print("occurrences dans l'état :", open("terraform.tfstate").read().count("jeton-de-demonstration"))
EOF
occurrences dans le plan : 0
occurrences dans l'état : 0
La sortie JSON du plan ne liste que les variables non éphémères :
$ terraform show -json t.plan | jq -c '.variables'
{"version_jeton":{"value":1}}
La variable jeton n'y est pas du tout.
Le numéro de version : pourquoi il existe
Pour voir la mécanique des arguments en écriture seule sans cloud, on utilise le bloc store de terraform_data (Terraform 1.16), dont l'argument input est en écriture seule :
resource "terraform_data" "essai" {
store {
input = ephemeral.random_password.db.result
version = 1
}
}À la création, le plan l'affiche comme tel :
# terraform_data.essai will be created
+ resource "terraform_data" "essai" {
+ id = (known after apply)
+ store {
+ input = (write-only attribute)
+ output = (known after apply)
+ version = 1
}
}
Ensuite, le mot de passe éphémère change à chaque exécution (il est régénéré), pourtant le plan est vide :
$ terraform plan -no-color
No changes. Your infrastructure matches the configuration.
C'est la raison d'être de password_wo_version : tant que le numéro ne bouge pas, Terraform n'a aucun moyen de savoir que la valeur est autre. Quand on passe version à 2, le plan propose la mise à jour :
# terraform_data.essai will be updated in-place
~ resource "terraform_data" "essai" {
id = "1a368aa9-6318-5d05-836c-b047ede67a00"
~ store {
~ output = "mTBRdddDTTSbtidL" -> (known after apply)
~ version = 1 -> 2
# (1 unchanged attribute hidden)
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
Warning
Cet exemple montre aussi un piège. Le bloc store de terraform_data est une échappatoire volontaire : il a pour rôle de conserver une valeur éphémère, dans store.output ou store.sensitive_output, qui sont écrits dans l'état. On l'a vérifié : la valeur de l'input se retrouve en clair dans le fichier d'état, sous store.output, et le plan suivant l'affiche. Il sert à capturer une valeur pour un usage ultérieur, jamais à protéger un secret. Un secret ne passe jamais par store.
Scaleway : mot de passe de base généré, rangé dans Secret Manager
Le dépôt signalements-iac crée la base sig-db et range son mot de passe dans Secret Manager (voir la leçon 8 de Scaleway en pratique). Version sans secret dans l'état :
ephemeral "random_password" "db" {
length = 32
special = false
}
resource "scaleway_rdb_instance" "db" {
name = "sig-db"
node_type = "DB-DEV-S"
engine = "PostgreSQL-16"
user_name = "signalements"
password_wo = ephemeral.random_password.db.result
password_wo_version = var.version_mot_de_passe
}
resource "scaleway_secret" "db" {
name = "sig-db-password"
}
resource "scaleway_secret_version" "db" {
secret_id = scaleway_secret.db.id
data_wo = ephemeral.random_password.db.result
data_wo_version = var.version_mot_de_passe
}La même valeur éphémère alimente les deux arguments pendant l'opération : la base reçoit le mot de passe, Secret Manager reçoit le secret, et ni l'un ni l'autre n'est dans l'état. Cette configuration est valide d'après le schéma du fournisseur 2.84 (terraform validate). Dans le schéma, password_wo et data_wo portent bien l'attribut write_only, et la description de password_wo précise : « password_wo will not be set in the Terraform state. To update the password_wo, you must also update the password_wo_version ».
Un détail propre à cette construction : random_password éphémère est régénéré à chaque opération. Tant que version_mot_de_passe ne change pas, la base et le secret ne sont pas touchés, ce qui est voulu. Mais le jour où l'on incrémente la version, les deux reçoivent la nouvelle valeur au même apply : ils restent cohérents. Les deux versions doivent donc rester liées à la même variable.
Lire un secret existant, sans le garder
Quand le secret est déjà dans Secret Manager (rangé par un autre dépôt, ou à la main), on le lit par la ressource éphémère :
ephemeral "scaleway_secret_version" "db" {
secret_name = "sig-db-password"
}
resource "scaleway_rdb_instance" "db" {
name = "sig-db"
node_type = "DB-DEV-S"
engine = "PostgreSQL-16"
user_name = "signalements"
password_wo = ephemeral.scaleway_secret_version.db.data
password_wo_version = var.version_mot_de_passe
}Le schéma de la ressource éphémère scaleway_secret_version (fournisseur 2.84) accepte secret_id ou secret_name, un revision optionnel, et expose data, sensible. Le contraste avec la source de données est net : data "scaleway_secret_version" aurait écrit data dans l'état.
Le chiffrement de l'état avec OpenTofu
Pour ce qui reste dans l'état, voici la configuration d'OpenTofu (d'après la documentation officielle ; non exécutée ici, OpenTofu n'étant pas installé) :
terraform {
encryption {
key_provider "pbkdf2" "phrase" {
passphrase = var.phrase_etat
}
method "aes_gcm" "etat" {
keys = key_provider.pbkdf2.phrase
}
state {
method = method.aes_gcm.etat
enforced = true
}
plan {
method = method.aes_gcm.etat
enforced = true
}
}
}key_providerdit d'où vient la clé. OpenTofu en propose :pbkdf2(dérivation depuis une phrase secrète),aws_kms,gcp_kms,azure_vault,openbao(moteur transit) etexternal(un programme que vous fournissez). Scaleway n'a pas de fournisseur de clé dédié : en production,openbao(OpenBao auto-hébergé) ouexternal(un petit programme qui interroge le service de gestion de clés retenu) sont les voies, etpbkdf2reste le cas simple si la phrase est bien gardée.method "aes_gcm"chiffre en AES-GCM avec une clé de 16, 24 ou 32 octets.stateetplandésignent ce qu'on chiffre,enforced = trueinterdit d'écrire un état non chiffré (utile pour qu'un changement de configuration ne dégrade pas la protection sans bruit).
La configuration peut aussi venir de la variable d'environnement TF_ENCRYPTION, ce qui évite de commiter la configuration quand elle contient la phrase. Les variables et les valeurs locales sont autorisées dans ce bloc à condition de ne dépendre ni de l'état ni de fonctions de fournisseur : tout doit se résoudre dès tofu init.
Important
Sans la clé, l'état est perdu. Sauvegardez la clé (ou la phrase) hors du dépôt et hors du système qu'elle protège, et ne renommez pas un fournisseur de clé ou une méthode une fois l'état chiffré (la documentation le déconseille sans procédure de migration).
Le fournisseur terraform_remote_state d'une configuration qui lit cet état doit connaître la configuration de chiffrement de l'état lu : OpenTofu la déclare dans un bloc remote_state_data_sources.
La rotation
Trois rotations sont à distinguer.
Rotation d'un secret en écriture seule. On incrémente le numéro : version_mot_de_passe = 2. Le plan montre une mise à jour de password_wo_version (et de data_wo_version), l'apply envoie la nouvelle valeur (un nouveau random_password éphémère), et les deux destinations changent ensemble. Les applications qui lisent le secret dans Secret Manager le récupèrent à leur prochain accès ; il faut qu'elles supportent un changement de mot de passe sans redémarrage ou qu'un déploiement suive.
Rotation de la clé de chiffrement de l'état (OpenTofu). On déclare la nouvelle méthode et on garde l'ancienne en repli :
state {
method = method.aes_gcm.nouvelle
fallback {
method = method.aes_gcm.ancienne
}
}La lecture essaie la méthode principale, puis le repli ; l'écriture utilise toujours la méthode principale. Après un apply, l'état est réécrit avec la nouvelle clé, et le repli peut être retiré.
Rotation des identifiants de la plateforme. Les clés d'API Scaleway utilisées par la CI et l'état sont un sujet de la leçon 12 du premier cours : identités par usage, courte durée.
Sous le capot
Pourquoi la valeur éphémère ne fuit pas. Terraform marque la valeur avec une marque « éphémère » qui l'accompagne dans toutes les expressions. Les endroits qui persistent (attributs ordinaires, sorties non éphémères, count, for_each) refusent une valeur marquée à la validation. Les endroits qui ne persistent pas (arguments en écriture seule, configuration de fournisseur) l'acceptent. Le fichier de plan et l'état sont écrits à partir des valeurs non éphémères.
Pourquoi l'ouverture et la fermeture. Un plan et un apply sont deux processus distincts, parfois séparés de plusieurs heures. Une ressource éphémère est donc rouverte à chaque phase qui en a besoin : au plan, puis à l'apply. Deux ouvertures successives de random_password donnent des valeurs différentes, ce qui explique le « No changes » vu plus haut. Pour un secret lu (scaleway_secret_version), les deux ouvertures donnent la même valeur tant que le secret n'a pas changé.
Le fournisseur reçoit quand même la valeur. Rien n'empêche le fournisseur de la journaliser ou de la transmettre. La garantie porte sur ce que Terraform écrit. Elle ne couvre pas le contenu de la requête HTTP vers l'API ni ce que fait l'API ensuite.
Pas d'effet rétroactif. Passer une ressource de password à password_wo n'efface rien du passé : l'historique des versions de l'état (les versions du bucket, les sauvegardes terraform.tfstate.backup) conserve les anciennes valeurs. Une fuite passée se traite par une rotation, pas par la correction du code.
Pièges courants
Invalid use of ephemeral value : on a branché une valeur éphémère sur un argument ordinaire. Le message dit que l'argument « must be persisted to state ». Il faut un argument _wo, ou une autre approche (laisser le fournisseur générer le secret, voir plus bas).
Oublier le numéro de version. On change le secret, on lance le plan : « No changes ». Le mot de passe réel n'a pas changé. C'est le comportement attendu, et la raison d'être de _wo_version.
Passer le secret par store. Voir l'avertissement plus haut : il l'écrit dans l'état.
Une sortie de module éphémère consommée au mauvais endroit. Une sortie ephemeral = true ne peut aller que dans une valeur éphémère, une configuration de fournisseur ou un argument _wo. Les erreurs sont les mêmes que ci-dessus, mais la cause est dans un autre module.
Un secret dans terraform.tfvars. Une variable éphémère n'écrit rien dans l'état, mais si la valeur est dans un fichier commité, elle est dans Git. Passez-la par l'environnement (TF_VAR_...) ou par la CI, depuis un gestionnaire de secrets.
Compatibilité entre les deux outils. Une configuration qui utilise ephemeral et _wo demande Terraform 1.11 ou OpenTofu 1.11. Le bloc encryption n'existe qu'avec OpenTofu : un module partagé qui le contiendrait est illisible par Terraform. Gardez-le dans la configuration racine, jamais dans un module réutilisable.
Sécurité
- L'état est un secret. Même si vous avez éliminé le plus gros, il contient des identifiants, des adresses, des clés publiques. Bucket privé, versionné, chiffré côté serveur, accès limité à l'identité de la CI, journalisation des lectures. Voir la leçon 7 du premier cours.
- Un secret fuité se traite par une rotation. Corriger le code ne retire pas l'ancienne valeur des versions de l'état ni des journaux de la CI.
- Préférez la génération côté fournisseur quand elle existe. Si le service sait créer lui-même le mot de passe initial (ou un compte lié à une identité, sans mot de passe), Terraform n'a rien à manipuler. Un mot de passe qui n'existe pas dans votre configuration ne peut pas fuir.
- Séparez qui peut lire le secret de qui peut planifier. Le compte du plan sur demande de fusion n'a pas besoin du droit de lire le mot de passe de production : une ressource éphémère
scaleway_secret_versionn'ouvre qu'avec le droit d'accès au secret, ce qui se règle par une politique IAM (voir le premier cours pour les identités par usage). - La sortie des provisioners est supprimée avec une valeur éphémère. C'est voulu ; ne la contournez pas en écrivant la valeur dans un fichier.
- Chiffrer l'état n'est pas supprimer le secret. Qui obtient la clé et le fichier lit tout. Placez la clé dans un système distinct (KMS, OpenBao) et tracez ses usages.
En production
Un chemin de migration progressif. Inventoriez d'abord les secrets de l'état : terraform show -json | jq filtré sur les valeurs sensibles, ou une recherche dans le schéma des ressources utilisées. Pour chaque secret, passez à _wo quand le fournisseur le permet, en commençant par les mots de passe de bases (le gros du risque). Prévoyez une rotation à l'occasion du changement : c'est elle qui rend les anciennes valeurs inoffensives.
Un état qui n'est plus une corvée. Si plus aucun secret ne passe par l'état, ses droits de lecture peuvent être élargis à l'équipe (diagnostic) sans exposer de mot de passe. C'est un gain d'exploitation concret.
Terraform ou OpenTofu ? Les deux traitent désormais les valeurs éphémères et l'écriture seule. Le chiffrement natif de l'état est le seul avantage fonctionnel d'OpenTofu dans cette leçon ; il compte si les exigences d'un client imposent que l'état soit chiffré au niveau applicatif, pas seulement par le stockage. La leçon 10 traite des passages d'un outil à l'autre, et un état chiffré par OpenTofu ne se relit pas par Terraform.
Qui crée le secret ? Un schéma courant : un dépôt « plateforme » crée les secrets vides dans Secret Manager et gère les droits ; le dépôt applicatif lit par ephemeral. Le mot de passe est créé et roulé par un processus dédié (ou par le fournisseur), pas par la configuration qui consomme.
Les couches de la leçon 4 : les secrets sont un cas où l'on évite le partage par terraform_remote_state. Une couche ne publie pas un mot de passe en sortie pour qu'une autre le lise ; elle publie l'identifiant du secret et l'autre couche le lit par la ressource éphémère.
Exercices
Exercice 1 : classer
Pour chacun, dites si la valeur finit dans l'état et ce qu'il faut changer si c'est inacceptable : (a) variable "mdp" { sensitive = true } passée à scaleway_rdb_instance.password ; (b) ephemeral = true passée à scaleway_rdb_instance.password_wo ; (c) data "scaleway_secret_version" qui alimente password_wo ; (d) ephemeral "random_password" dans scaleway_secret_version.data_wo ; (e) un output ordinaire qui expose ephemeral.random_password.db.result.
Solution
(a) Oui : sensitive ne masque que l'affichage, password est un attribut ordinaire. Passer à password_wo avec une variable éphémère et un numéro de version. (b) Non : c'est le cas voulu. (c) La source de données écrit data dans l'état ; seul l'argument de destination est en écriture seule. Remplacer par ephemeral "scaleway_secret_version". (d) Non, c'est le cas voulu. (e) Terraform refuse à la validation : une sortie ordinaire est persistée, la valeur éphémère n'y est pas admise (il faudrait ephemeral = true sur la sortie, d'un module enfant).
Exercice 2 : la rotation
La base sig-db utilise password_wo_version = 1. L'équipe sécurité demande une rotation. Décrivez la modification, ce que le plan affichera, et ce qui doit se passer côté application. Que se passe-t-il si vous oubliez de changer aussi data_wo_version sur le secret ?
Solution
On passe la variable version_mot_de_passe de 1 à 2, ce qui change password_wo_version et data_wo_version. Le plan montre deux mises à jour en place, avec (write-only attribute) pour les valeurs. À l'apply, un nouveau random_password éphémère est ouvert et envoyé aux deux ressources. L'application doit recharger le secret (prévoyez une courte fenêtre ou un redémarrage progressif, le temps que les connexions existantes se renouvellent). Si l'on oublie data_wo_version, la base a le nouveau mot de passe et Secret Manager conserve l'ancien : l'application, qui lit le secret, ne peut plus se connecter. D'où la règle de lier les deux numéros à la même variable.
Exercice 3 : retrouver un secret dans l'état
Écrivez la commande qui cherche dans un fichier d'état toute valeur d'attribut dont le nom contient password, secret ou token, puis dites comment vous vérifieriez qu'un secret précis n'est ni dans l'état ni dans le plan enregistré.
Solution
jq -r '.resources[] | . as $r | .instances[].attributes
| to_entries[] | select(.key | test("password|secret|token"; "i"))
| select(.value != null) | "\($r.type).\($r.name) \(.key)"' terraform.tfstatePour un secret précis, on cherche sa valeur : grep -c sur le fichier d'état, et sur le plan enregistré, qui est une archive ZIP (le plan se lit avec python3 -m zipfile ou unzip -p). Une valeur absente des deux est la preuve attendue, comme dans la démonstration de la leçon. Faites aussi la recherche sur les sauvegardes et versions antérieures de l'état.
Exercice 4 : choisir le chiffrement
Un client exige que l'état soit chiffré avec une clé qu'il contrôle, et que Lyneko ne puisse pas lire seul. Quelle architecture proposez-vous avec OpenTofu, et quelles en sont les contraintes ?
Solution
Chiffrement de l'état par OpenTofu avec un fournisseur de clé openbao ou external dont la clé est détenue par le client (un moteur de transit dans son périmètre), méthode aes_gcm, enforced = true pour l'état et le plan. Le bucket reste privé et chiffré côté serveur : deux couches. Contraintes : la CI doit pouvoir joindre le service de clés du client (disponibilité, réseau), la perte de la clé rend l'état irrécupérable (sauvegarde et procédure de séquestre), terraform_remote_state doit connaître la configuration de chiffrement, un module partagé ne doit pas porter le bloc, et l'on ne peut plus relire l'état avec Terraform.
Récapitulatif
sensitivemasque l'affichage, pas le fichier : l'état contient les secrets en clair, et il circule.- Une valeur éphémère (variable, sortie de module, ressource
ephemeral) n'est écrite ni dans le plan ni dans l'état, et Terraform la refuse partout où elle serait persistée (Invalid use of ephemeral value). Terraform 1.10 ; OpenTofu 1.11. - Un argument en écriture seule (
password_wo,data_wo) reçoit le secret sans le garder, apparu avec Terraform 1.11 et OpenTofu 1.11 ; son numéro de version est le seul moyen de déclencher une rotation, parce que Terraform ne compare pas la valeur. - Pour lire un secret, la ressource
ephemeral "scaleway_secret_version"remplace la source de données, qui l'écrit dans l'état. - Le bloc
storedeterraform_dataconserve une valeur éphémère dans l'état : jamais pour un secret. - OpenTofu chiffre l'état et les plans (depuis la 1.7) avec un fournisseur de clé et une méthode
aes_gcm; la rotation passe parfallback, et la perte de la clé est définitive. - Une fuite passée se traite par une rotation, pas par un correctif de code : les versions antérieures de l'état conservent la valeur.
Pour aller plus loin
- Le guide « Manage sensitive data » de la documentation de Terraform, la page du bloc
ephemeralet les notes de version 1.10 et 1.11. - Les notes de version d'OpenTofu 1.11 et la page « State and Plan Encryption », avec la section sur les migrations et les fournisseurs de clés.
- Le guide des arguments en écriture seule du fournisseur Scaleway, et la page de
scaleway_rdb_instancepour la liste à jour des arguments_wo. - La leçon 6 pour une politique qui refuse un secret ordinaire, la leçon 8 pour réduire le rayon d'impact d'un état, et le chapitre « Sécuriser » : gestion des secrets à l'échelle de l'organisation (cours à venir).
Sources
- HashiCorp, Terraform : gérer les données sensibles (valeurs éphémères, arguments en écriture seule)
- HashiCorp, Terraform : le bloc ephemeral
- HashiCorp, Terraform : la ressource terraform_data (bloc store)
- OpenTofu, notes de version 1.11 (valeurs éphémères, attributs en écriture seule)
- OpenTofu, chiffrement de l'état et des plans
- Fournisseur Scaleway : guide des arguments en écriture seule
- Fournisseur Scaleway : scaleway_secret_version, scaleway_rdb_instance
- Yevgeniy Brikman, Terraform: Up & Running (3e éd., O'Reilly, 2022), chapitre 6 : Production-grade Terraform / Managing secrets