[67] Bootstrap de l'état Terraform distant #121

Merged
lenaic merged 5 commits from florian/67-bootstrap-etat-terraform into develop 2026-09-04 09:05:19 +00:00
Member

Crée hors Terraform le conteneur qui portera terraform.tfstate : le backend
azurerm est lu avant tout resource, il ne peut pas créer son propre support.

Le script s'écarte du ticket sur quatre points, imposés par l'abonnement école :

  • le groupe de ressources est rg-FHeuze2023_cours-projet-eadl, pas
    rg-FHeuze-eadl, qui n'existe pas ;
  • le compte stenervisiong2tfstate existait déjà et est adopté plutôt que
    doublé : l'étiquette storageaccountnumber du groupe plafonne leur nombre ;
  • une politique en effet deny exige une étiquette user sur toute ressource,
    reprise de celle du groupe pour que le script serve à chaque membre ;
  • le rôle Devops-cours-projet-eadl n'a aucune dataAction : créer le conteneur
    ne donne pas le droit d'y écrire. Storage Blob Data Contributor est donc
    attribué au compte connecté, sans quoi le terraform init du #68 échouerait
    en 403 avec tout en place. L'attribution passe par az rest, az role
    assignment échouant en MissingSubscription faute d'accès à Entra ID.

Aucune clé de compte n'est lue : tout passe par le plan de gestion et
l'identité d'az login, comme le demande l'ADR 0007.

Ce que ça change

Closes #67

Preuve

Branche florian/67-bootstrap-etat-terraform, commit 094d961. Livrables :
infra/terraform/bootstrap.sh et docs/runbooks/terraform-etat.md.

Le script, deuxième passage — rejoué quatre fois, sortie identique, aucune erreur ni effet de bord :

$ bash infra/terraform/bootstrap.sh
== état Terraform distant : stenervisiong2tfstate/tfstate dans rg-FHeuze2023_cours-projet-eadl ==
session florian.heuze2023@campus-eni.fr
groupe rg-FHeuze2023_cours-projet-eadl (user=FHeuze2023)
compte stenervisiong2tfstate déjà présent
blob versioning actif
conteneur tfstate déjà présent
rôle « Storage Blob Data Contributor » déjà attribué

== conteneur d'état prêt ==

Le conteneur et les propriétés demandées :

$ az storage container show -n tfstate --account-name stenervisiong2tfstate --auth-mode login
{ "nom": "tfstate", "public": null }

$ az storage account show -n stenervisiong2tfstate -g rg-FHeuze2023_cours-projet-eadl
{ "sku": "Standard_LRS", "kind": "StorageV2", "tls": "TLS1_2",
"blobPublic": false, "region": "francecentral", "tags": { "user": "FHeuze2023" } }

$ az storage account blob-service-properties show --account-name stenervisiong2tfstate -g rg-FHeuze2023_cours-projet-eadl
{ "versioning": true }


Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Ce qui suit le code

Où regarder en priorité

Crée hors Terraform le conteneur qui portera terraform.tfstate : le backend azurerm est lu avant tout `resource`, il ne peut pas créer son propre support. Le script s'écarte du ticket sur quatre points, imposés par l'abonnement école : - le groupe de ressources est rg-FHeuze2023_cours-projet-eadl, pas rg-FHeuze-eadl, qui n'existe pas ; - le compte stenervisiong2tfstate existait déjà et est adopté plutôt que doublé : l'étiquette storageaccountnumber du groupe plafonne leur nombre ; - une politique en effet deny exige une étiquette `user` sur toute ressource, reprise de celle du groupe pour que le script serve à chaque membre ; - le rôle Devops-cours-projet-eadl n'a aucune dataAction : créer le conteneur ne donne pas le droit d'y écrire. Storage Blob Data Contributor est donc attribué au compte connecté, sans quoi le `terraform init` du #68 échouerait en 403 avec tout en place. L'attribution passe par az rest, az role assignment échouant en MissingSubscription faute d'accès à Entra ID. Aucune clé de compte n'est lue : tout passe par le plan de gestion et l'identité d'az login, comme le demande l'ADR 0007. ## Ce que ça change <!-- Deux phrases. Ce qu'un relecteur doit comprendre avant d'ouvrir le code. --> Closes #67 ## Preuve Branche florian/67-bootstrap-etat-terraform, commit 094d961. Livrables : infra/terraform/bootstrap.sh et docs/runbooks/terraform-etat.md. Le script, deuxième passage — rejoué quatre fois, sortie identique, aucune erreur ni effet de bord : $ bash infra/terraform/bootstrap.sh == état Terraform distant : stenervisiong2tfstate/tfstate dans rg-FHeuze2023_cours-projet-eadl == session florian.heuze2023@campus-eni.fr groupe rg-FHeuze2023_cours-projet-eadl (user=FHeuze2023) compte stenervisiong2tfstate déjà présent blob versioning actif conteneur tfstate déjà présent rôle « Storage Blob Data Contributor » déjà attribué == conteneur d'état prêt == Le conteneur et les propriétés demandées : $ az storage container show -n tfstate --account-name stenervisiong2tfstate --auth-mode login { "nom": "tfstate", "public": null } $ az storage account show -n stenervisiong2tfstate -g rg-FHeuze2023_cours-projet-eadl { "sku": "Standard_LRS", "kind": "StorageV2", "tls": "TLS1_2", "blobPublic": false, "region": "francecentral", "tags": { "user": "FHeuze2023" } } $ az storage account blob-service-properties show --account-name stenervisiong2tfstate -g rg-FHeuze2023_cours-projet-eadl { "versioning": true } ``` ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Ce qui suit le code ## Où regarder en priorité
[67] Bootstrap de l'état Terraform distant
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m39s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m46s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m23s
caffe61824
Crée hors Terraform le conteneur qui portera terraform.tfstate : le backend
azurerm est lu avant tout `resource`, il ne peut pas créer son propre support.

Le script s'écarte du ticket sur quatre points, imposés par l'abonnement école :

- le groupe de ressources est rg-FHeuze2023_cours-projet-eadl, pas
  rg-FHeuze-eadl, qui n'existe pas ;
- le compte stenervisiong2tfstate existait déjà et est adopté plutôt que
  doublé : l'étiquette storageaccountnumber du groupe plafonne leur nombre ;
- une politique en effet deny exige une étiquette `user` sur toute ressource,
  reprise de celle du groupe pour que le script serve à chaque membre ;
- le rôle Devops-cours-projet-eadl n'a aucune dataAction : créer le conteneur
  ne donne pas le droit d'y écrire. Storage Blob Data Contributor est donc
  attribué au compte connecté, sans quoi le `terraform init` du #68 échouerait
  en 403 avec tout en place. L'attribution passe par az rest, az role
  assignment échouant en MissingSubscription faute d'accès à Entra ID.

Aucune clé de compte n'est lue : tout passe par le plan de gestion et
l'identité d'az login, comme le demande l'ADR 0007.
gabriel left a comment

Revue du script bootstrap.sh et du runbook. Rien de bloquant : le script tourne, il est idempotent (preuve à l'appui), les 4 critères du #67 sont couverts, et les 4 écarts au ticket (RG, compte adopté, tag user, rôle de données) sont chacun justifiés dans le corps de PR et dans le runbook. Restent des fragilités réelles, surtout autour de l'attribution de rôle.

Majeur

A. Le repli d'extraction de l'OID (l.161-162) est cassé. Le payload d'un JWT est du base64url non paddé. cut -d. -f2 | base64 -d échoue dès qu'il y a un -/_ ou une longueur non multiple de 4 -> sortie vide -> objet vide -> « identité non déterminée, rôle non posé ». Ça marche « parfois », par chance sur la longueur. Or ce repli existe précisément pour le cas « pas d'accès Entra ID », c'est donc lui qui a le plus de chances d'être exercé. Correctif :

p=$(az account get-access-token --query accessToken -o tsv | cut -d. -f2)
while [ $(( ${#p} % 4 )) -ne 0 ]; do p="$p="; done
objet=$(printf '%s' "$p" | tr '_-' '/+' | base64 -d 2>/dev/null | sed -n 's/.*"oid":"\([^"]*\)".*/\1/p')

Au minimum : vérifier qu'il produit bien un GUID sur un poste où az ad signed-in-user show échoue.

B. L'idempotence de l'attribution de rôle repose sur un GET dont toutes les erreurs sont avalées (2>/dev/null, l.176). Sur un throttle/429 ou une erreur transitoire du plan de gestion, le grep -q '^[1-9]' échoue -> le script tente un PUT avec un GUID neuf -> Azure répond 409 RoleAssignmentExists -> affiche « ATTENTION : n'a pas pu être attribué » alors que tout va bien. Le « rejoué 4 fois, sortie identique » ne tient que si le control plane est sain à chaque passage. Distinguer « déjà attribué » d'une vraie erreur de lecture rendrait le message honnête.

C. compte_id (l.157) et abonnement (l.52) sont les deux seuls appels az non gardés dans un script par ailleurs soigneusement gardé. Un compte_id vide donne une URL API_ROLES malformée -> échec confus en fin de course. Deux lignes de garde suffisent.

Mineur

D. Le parsing booléen -o tsv (dispo, existe) suppose true/false minuscules. Vérifié OK sur az 2.90, mais les CLI plus anciennes rendent True/False et les comparaisons [ "$existe" = "true" ] passent silencieusement à côté (tente de recréer un conteneur existant, saute le message « nom déjà pris »). Épingler une version az minimale, ou comparer en insensible à la casse.

E. « remet le compte dans les clous » est un peu surévalué : le ré-assert (l.115-121) ne couvre que TLS / public-access / https-only (+ versioning l.126). Ni SKU, ni kind, ni le tag user. Et si le compte adopté perd son tag user, c'est l'az storage account update lui-même qui se fait deny par la politique, avec le message générique « impossible de réappliquer les propriétés ».

F. Le fichier est 100644, pas exécutable, malgré le shebang. Les docs l'appellent via bash ... donc ça marche, mais chmod +x est la convention.

G. Aucun contrôle de tenant. Le script fait confiance à l'abonnement par défaut ; l'ADR 0007 verrouille le tenant 7f4f3591-.... Un membre dont le tenant ENI n'est pas le défaut obtient « le groupe n'existe pas » au lieu de « mauvais tenant ». az account show --query tenantId comparé en une ligne.

Nits

  • La section « Preuve » cite le commit 094d961 ; la tête de PR est caffe61. Obsolète.
  • Le runbook est vraiment bon — l'entrée « 403 au terraform init » fera gagner du temps réel au prochain.

Ce qui est bien

Plan de gestion uniquement (container-rm, az rest) pour ne jamais lire de clé de compte : conforme ADR 0007, propre. Design d'idempotence réfléchi (show-puis-agir, messages « créé » / « déjà présent » distincts). Portabilité poste Windows anticipée (repli uuidgen). Les écarts au ticket sont tracés aux deux endroits.

Revue du script `bootstrap.sh` et du runbook. Rien de bloquant : le script tourne, il est idempotent (preuve à l'appui), les 4 critères du #67 sont couverts, et les 4 écarts au ticket (RG, compte adopté, tag `user`, rôle de données) sont chacun justifiés dans le corps de PR **et** dans le runbook. Restent des fragilités réelles, surtout autour de l'attribution de rôle. ## Majeur **A. Le repli d'extraction de l'OID (l.161-162) est cassé.** Le payload d'un JWT est du base64**url** non paddé. `cut -d. -f2 | base64 -d` échoue dès qu'il y a un `-`/`_` ou une longueur non multiple de 4 -> sortie vide -> `objet` vide -> « identité non déterminée, rôle non posé ». Ça marche « parfois », par chance sur la longueur. Or ce repli existe précisément pour le cas « pas d'accès Entra ID », c'est donc lui qui a le plus de chances d'être exercé. Correctif : ```bash p=$(az account get-access-token --query accessToken -o tsv | cut -d. -f2) while [ $(( ${#p} % 4 )) -ne 0 ]; do p="$p="; done objet=$(printf '%s' "$p" | tr '_-' '/+' | base64 -d 2>/dev/null | sed -n 's/.*"oid":"\([^"]*\)".*/\1/p') ``` Au minimum : vérifier qu'il produit bien un GUID sur un poste où `az ad signed-in-user show` échoue. **B. L'idempotence de l'attribution de rôle repose sur un GET dont toutes les erreurs sont avalées (`2>/dev/null`, l.176).** Sur un throttle/429 ou une erreur transitoire du plan de gestion, le `grep -q '^[1-9]'` échoue -> le script tente un `PUT` avec un GUID neuf -> Azure répond 409 `RoleAssignmentExists` -> affiche « ATTENTION : n'a pas pu être attribué » alors que tout va bien. Le « rejoué 4 fois, sortie identique » ne tient que si le control plane est sain à chaque passage. Distinguer « déjà attribué » d'une vraie erreur de lecture rendrait le message honnête. **C. `compte_id` (l.157) et `abonnement` (l.52) sont les deux seuls appels `az` non gardés** dans un script par ailleurs soigneusement gardé. Un `compte_id` vide donne une URL `API_ROLES` malformée -> échec confus en fin de course. Deux lignes de garde suffisent. ## Mineur **D.** Le parsing booléen `-o tsv` (`dispo`, `existe`) suppose `true`/`false` minuscules. Vérifié OK sur az 2.90, mais les CLI plus anciennes rendent `True`/`False` et les comparaisons `[ "$existe" = "true" ]` passent silencieusement à côté (tente de recréer un conteneur existant, saute le message « nom déjà pris »). Épingler une version `az` minimale, ou comparer en insensible à la casse. **E.** « remet le compte dans les clous » est un peu surévalué : le ré-assert (l.115-121) ne couvre que TLS / public-access / https-only (+ versioning l.126). Ni SKU, ni kind, ni le tag `user`. Et si le compte adopté perd son tag `user`, c'est l'`az storage account update` lui-même qui se fait deny par la politique, avec le message générique « impossible de réappliquer les propriétés ». **F.** Le fichier est `100644`, pas exécutable, malgré le shebang. Les docs l'appellent via `bash ...` donc ça marche, mais `chmod +x` est la convention. **G.** Aucun contrôle de tenant. Le script fait confiance à l'abonnement par défaut ; l'ADR 0007 verrouille le tenant `7f4f3591-...`. Un membre dont le tenant ENI n'est pas le défaut obtient « le groupe n'existe pas » au lieu de « mauvais tenant ». `az account show --query tenantId` comparé en une ligne. ## Nits - La section « Preuve » cite le commit `094d961` ; la tête de PR est `caffe61`. Obsolète. - Le runbook est vraiment bon — l'entrée « 403 au `terraform init` » fera gagner du temps réel au prochain. ## Ce qui est bien Plan de gestion uniquement (`container-rm`, `az rest`) pour ne jamais lire de clé de compte : conforme ADR 0007, propre. Design d'idempotence réfléchi (show-puis-agir, messages « créé » / « déjà présent » distincts). Portabilité poste Windows anticipée (repli `uuidgen`). Les écarts au ticket sont tracés aux deux endroits.
lenaic requested reviews from gabriel and removed review requests for lenaic 2026-09-04 07:36:07 +00:00
[67] Revue de Gabriel : fiabilise le bootstrap de l'état Terraform
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 51s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m46s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 2m31s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 58s
Intégration / Tableau de bord (pull_request) Successful in 5m22s
2fcdb2ee65
Le repli d'extraction de l'OID décodait le payload du jeton comme du base64
standard. C'est du base64url non paddé : « base64 -d » s'arrête au premier
« - » ou « _ », et rend une chaîne vide dès que ce caractère précède l'oid.
Le repli existait précisément pour les postes sans accès à Entra ID, donc
c'était le chemin le plus exercé qui était le plus fragile. Padding rétabli,
alphabet ramené au standard, et le résultat n'est retenu que s'il a la forme
d'un GUID.

L'idempotence de l'attribution de rôle reposait sur une lecture dont toutes
les erreurs étaient avalées : un throttle du plan de gestion faisait conclure
« rien n'est attribué », tenter un PUT, récolter un 409 RoleAssignmentExists,
et annoncer un échec alors que tout était en place. Lecture en échec et
lecture sans résultat sont désormais deux cas distincts — dans le premier,
rien n'est tenté. Un 409 malgré tout est reconnu et rapporté pour ce qu'il
est : le rôle est là.

Le reste suit la même revue : gardes sur l'abonnement et sur l'identifiant du
compte, contrôle du tenant verrouillé par l'ADR 0007 plutôt qu'un « le groupe
n'existe pas » trompeur, comparaisons de booléens insensibles à la casse pour
les CLI qui rendent « True », erreur de réaffirmation qui nomme l'étiquette
« user » quand la politique refuse, et bit exécutable posé.

Le runbook cessait de dire vrai sur un point : le script réaffirme quatre
propriétés, pas l'état complet du compte. Le SKU, le kind et l'étiquette n'en
font pas partie, et c'est délibéré sur un compte adopté.

Vérifié : les cinq branches d'attribution de rôle et les quatre gardes jouées
sur un faux « az », le décodage éprouvé sur un jeton portant un « _ » avant
l'oid — vide avant, correct après — et le script rejoué contre Azure, sortie
inchangée.

Refs #67
gabriel approved these changes 2026-09-04 08:13:49 +00:00
Dismissed
gabriel removed review request for justine 2026-09-04 08:13:54 +00:00
Merge develop dans florian/67-bootstrap-etat-terraform
Some checks failed
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m26s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Failing after 6m3s
957aa5de57
Un seul conflit, dans docs/runbooks/README.md : les deux côtés ajoutent des
lignes au même endroit du tableau des manuels. Aucune opposition de fond, les
trois entrées sont conservées — terraform-etat.md de cette branche, puis
supervision.md et acces-serveur.md venues de develop.
florian dismissed gabriel's review 2026-09-04 08:29:33 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

gabriel approved these changes 2026-09-04 08:35:53 +00:00
Dismissed
Merge branch 'develop' into florian/67-bootstrap-etat-terraform
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 22s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m6s
dd1f52a6e7
Merge branch 'develop' into florian/67-bootstrap-etat-terraform
Some checks failed
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 4s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Has been cancelled
Intégration / Python — qualité, tests et dépendances (pull_request) Has been cancelled
23ac71b299
gabriel dismissed gabriel's review 2026-09-04 09:04:02 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

lenaic merged commit eb12df2b19 into develop 2026-09-04 09:05:19 +00:00
lenaic deleted branch florian/67-bootstrap-etat-terraform 2026-09-04 09:05:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!121
No description provided.