ADR 034 — Terraform déclare les identités CI, il n'en fabrique plus les clés (2026-09-04)¶
Statut¶
Accepté, écrit le 2026-09-04. Issue #202, Sprint 7 (Remédiation & Fiabilité).
Le constat a été trouvé le 2026-09-02, en instruisant la rotation qui suit la parade 2 de #198. Il ne figure dans aucun des 48 constats du rapport d'audit, et l'ADR 032 est passée à côté en traitant pourtant le sujet voisin : elle a protégé la variable CI, personne n'a regardé que la même valeur était en clair dans le state.
Cette ADR tranche la parade avant son exécution, pas après. Même raison que
l'ADR 033 : une fois jouée, la stack persistent ne refabrique plus les clés
d'accès de la CI. C'est un arbitrage opérationnel, il se décide par écrit.
Contexte¶
Ce que Terraform fait aujourd'hui¶
terraform/modules/iam/main.tf crée trois aws_iam_access_key. Le provider AWS
stocke l'attribut secret en clair dans le state, et il ne peut pas faire
autrement : l'API IAM ne rend le secret qu'une seule fois, à la création.
Pour pouvoir le restituer dans un output, le provider doit le garder.
aws_iam_access_key.gitlab_ci_infra
|
+--------------------------+
v v
variable CI GitLab terraform.tfstate
(masquée + protégée, #184) s3://yk-devops-terraform-state/
| fastapi-eks/persistent/
v |
ADR 032 : traitée v
AUCUN contrôle
Relevé sur le state persistent :
| Ressource | Utilisateur | Clé | Créée le |
|---|---|---|---|
gitlab_ci |
fastapi-eks-gitlab-ci |
AKIAS4X2I7YSKIGKQKXV |
2026-05-12 |
gitlab_ci_infra |
fastapi-eks-gitlab-ci-infra |
AKIAS4X2I7YSJCRT2OMM |
2026-05-17 |
cockpit_panel |
fastapi-eks-cockpit-panel |
AKIAS4X2I7YSFJC4WOPQ |
2026-07-11 |
Les trois sont actives, et le sont depuis mai.
Pourquoi c'est plus grave que #198¶
198 portait sur des secrets applicatifs. Ici ce sont des **identifiants¶
AWS**.
gitlab_ci_infra porte la policy terraform_ci, avec iam:CreateUser,
iam:CreateAccessKey et iam:AttachUserPolicy. L'ADR 032 l'a déjà écrit à
propos de la variable CI :
Avec ces trois derniers appels, on se fabrique un administrateur. C'est une prise de contrôle du compte AWS, pas un accès à un registre.
C'est le motif garde-fou-documentaire-vs-controle pour la troisième fois sur ce
dossier : une porte fermée pendant que l'autre reste ouverte.
Ce que la parade 2 de #198 n'a pas réglé¶
Elle a retiré random_password et aws_secretsmanager_secret_version, parce que
Terraform pouvait cesser de connaître ces valeurs.
Elle ne change rien ici. aws_iam_access_key est toujours géré par Terraform,
donc le provider réécrit secret dans le state à chaque apply. Pour ces
trois valeurs, le robinet est resté ouvert.
Ce que le lab a mesuré (2026-09-04)¶
Sur un émulateur AWS local, avant de toucher au vrai state. Le périmètre de cet outil est fixé par #200 : les questions de forme du state s'y traitent, les questions de droits jamais. Celle-ci est une question de forme.
La valeur est à deux endroits du state, pas un :
| Emplacement | Contient la valeur |
|---|---|
la ressource aws_iam_access_key |
oui |
la section outputs du state |
oui, la même, en clair |
sensitive = true est posé sur les six outputs. Il masque l'affichage, pas le
stockage — même confusion que masquée contre protégée sur les variables CI
(ADR 032).
Séquence mesurée, étape par étape :
| Étape | Résultat mesuré |
|---|---|
state rm de la ressource |
la ressource part, l'output garde la valeur en clair |
retrait des blocs, puis plan |
Changes to Outputs, et pas No changes |
apply |
0 added, 0 changed, 0 destroyed, outputs purgés |
| recherche sur le fichier de state | la valeur n'apparaît plus |
| la clé côté AWS | toujours Active |
Deux conclusions, et la première contredit la recette de l'ADR 033.
Décision¶
1. Terraform garde l'identité, il perd les identifiants¶
- Terraform garde
aws_iam_user,aws_iam_policyet les attachements. - Terraform perd les trois
aws_iam_access_keyet les six outputs correspondants, dansmodules/iam/outputs.tfet danspersistent/outputs.tf. - Les clés se créent hors Terraform (
aws iam create-access-key) et se posent directement dans les variables CI, comme les valeurs de secrets se posent parscripts/seed-secrets.shdepuis l'ADR 033.
Le partage se retient en une phrase, symétrique de celle de l'ADR 033 : Terraform déclare l'identité, il n'en connaît jamais les identifiants.
2. La séquence exige un apply, contrairement à l'ADR 033¶
C'est le point neuf, et il est contre-intuitif :
1. terraform state rm (les 3 aws_iam_access_key)
2. retrait des blocs ressource ET des 6 outputs
3. terraform plan -> annonce "Changes to Outputs"
4. terraform apply -> 0 ressource touchée, outputs purgés
5. vérification sur le fichier de state téléchargé
L'ADR 033 posait plan vide comme critère de succès et interdisait tout
apply. Appliquer cette recette ici laisserait la valeur dans les outputs.
Un opérateur qui suivrait l'ADR 033 à la lettre s'arrêterait à l'étape 3 devant
un plan non vide, en croyant à une erreur, et signerait une parade incomplète.
Cet apply est sûr, et la mesure le dit : il ne touche aucune ressource, il
ne réécrit que la section outputs. La clé reste Active chez AWS.
Aucun apply entre 1 et 2, en revanche. L'ordre inverse, ou un apply
glissé au milieu, supprimerait les clés d'accès chez AWS : Terraform les a
encore dans son state et ne les voit plus dans la configuration. La CI perdrait
ses identifiants au même instant, gitlab_ci_infra compris, c'est-à-dire
l'identité qui sert à monter et à détruire l'infrastructure.
Le critère de succès est la vérification d'absence sur le fichier de state
téléchargé, section outputs comprise — jamais la sortie du plan, jamais le
succès de l'apply. C'est la règle du « vert qui ne prouve rien ».
3. La rotation vient après la parade, jamais avant¶
Tant que Terraform fabrique la valeur, la tourner la fait revenir dans le state
au plan suivant. On tuerait des copies anciennes pour en créer une nouvelle,
vivante.
C'est la leçon de #198, et la raison pour laquelle la rotation du 2026-09-02 a été faite après la parade.
L'ancienne clé ne se supprime qu'une fois la nouvelle prouvée par un pipeline
vert, et pas sur le succès de l'appel create-access-key.
4. Le coût opérationnel est accepté, contre une procédure rejouée¶
La stack ne refabrique plus les clés seule. Après un destroy/apply de
persistent, il faudra en créer trois à la main et les poser dans six variables
CI.
Une contrainte propre aux clés d'accès, absente de l'ADR 033 : une valeur de Secrets Manager peut se relire autant de fois qu'on veut ; le secret d'une access key ne se lit qu'à la création. Il n'y a pas de « repose », seulement une re-création. Perdre la valeur entre sa création et sa pose dans GitLab oblige à supprimer la clé et à recommencer.
Accepté à une condition, la même que l'ADR 033 : la procédure est écrite et rejouée une fois de bout en bout avant que #202 se ferme. Une procédure jamais exécutée est une intention, pas un filet.
5. Le périmètre s'arrête au flux, et le reste est nommé¶
Cette ADR traite ce que Terraform écrit désormais. Restent non couverts :
- Le stock. Les 756 versions du state (139
persistent, 617ephemeral) portent les valeurs déjà écrites. C'est #199, et la parade ne les efface pas : après exécution, les anciennes versions restent lisibles. - Le mot de passe master RDS du state
ephemeral(aws_db_instance.password), même problème, même parade à instruire. terraform.tfstate.backup. Mesuré au lab : le backup local porte encore la valeur après l'apply. Sur le backend S3, ce rôle est tenu par le versioning de l'objet, ce qui renvoie à #199.
À écrire noir sur blanc : après cette parade, le state persistent ne contient
plus d'identifiant AWS, mais les versions précédentes en contiennent toujours.
La case du « Done when » de #202 se lit au périmètre de la version courante.
Conséquences¶
- #202 ne se ferme pas sur cette ADR. Restent le
state rm, le retrait des blocs et des outputs, l'applyde purge, la vérification sur le fichier, la rotation des trois clés et la procédure rejouée. - Six outputs disparaissent de
modules/iamet depersistent. Vérifié avant de décider : aucun consommateur automatique. Aucunterraform_remote_statedans le dépôt, et aucun script ne litterraform output *secret_access_key. Les valeurs étaient posées à la main dans les variables CI. - Un geste manuel de plus au montage from-scratch, donc un oubli possible. Le
symptôme serait un pipeline qui échoue en
NoCredentialssur le premier job AWS, ce qui est au moins un message clair. La procédure doit le dire. - La rotation devient possible sans Terraform. Créer une clé ne demande plus
un
applysurpersistent, donc plus les droits qui vont avec. - Le lien avec #193 : une politique de ressource
deny by defaultsur les sept secrets ne protège rien tant qu'un identifiant capable de tout lire traîne dans un fichier. Cette ADR retire cet identifiant du fichier. - L'arbitrage KMS sur le bucket de state se rouvre, et cette fois pour protéger des identifiants AWS et non des secrets applicatifs. Il appartient à #199.
Alternatives écartées¶
lifecycle { ignore_changes = [secret] }. C'est la parade qui vient à
l'esprit en premier, et elle ne règle rien : la valeur est déjà dans le state
depuis la création, et ignore_changes ne retire pas ce qui y est écrit. Elle
donnerait l'impression d'agir, ce qui est pire que ne rien faire.
Chiffrer le bucket de state avec une clé KMS dédiée, et s'arrêter là. Écartée comme parade principale, pour la même raison qu'en ADR 033 : une seconde barrière d'autorisation réduit qui lit, elle laisse la valeur dans le fichier. Reste souhaitable, et appartient à #199.
Générer les clés hors Terraform mais les lui passer en TF_VAR_*. Écartée
immédiatement : Terraform écrirait la valeur reçue dans le state exactement comme
aujourd'hui. Générer n'est pas la condition, écrire suffit.
Sortir tout le module iam de Terraform. Écartée : on perdrait les
utilisateurs, les policies et les attachements en tant que code, c'est-à-dire
tout ce qui se relit et se révise en MR. Le contenant a de la valeur, c'est le
contenu qui n'en a pas.
Remplacer les clés statiques par une fédération OIDC entre GitLab et AWS.
C'est la seule alternative qui supprime le problème à la racine plutôt que de le
déplacer : plus de clé longue durée du tout, donc plus rien à stocker, à tourner
ni à fuiter. Elle n'est pas écartée sur le fond, elle est hors périmètre de
cette ADR : elle demande un provider OIDC, une refonte des rôles et une reprise
des deux fichiers CI, quand la parade décidée ici tient en un state rm et un
retrait de blocs. Retenue comme direction cible, à ouvrir en issue propre ;
cette ADR réduit l'exposition en attendant, elle ne la referme pas.
Références¶
- Issue #202 (le constat, les trois clés), #198 (la même mécanique sur les
secrets applicatifs), #199 (les 756 versions du state), #193 (politique de
ressource
deny by default), #200 (périmètre de l'émulateur AWS local qui a servi à la mesure), #184 (variables CI masquées et protégées) - ADR 032 (privilèges des clés CI, masquée n'est pas protégée),
ADR 033 (secrets hors du state — dont cette ADR corrige la recette sur le
point de l'
apply) - Fiches :
garde-fou-documentaire-vs-controle,critere-qui-ne-discrimine-pas(la preuve est la vérification d'absence, jamais le succès de l'appel)