--- title: "Projet final : valider, planifier et auditer la plateforme" --- # Projet final : valider, planifier et auditer la plateforme > [!NOTE] > Dans cette leçon, vous allez dérouler la chaîne complète d'une livraison d'infrastructure IA : valider le module, le planifier contre votre projet, contrôler la posture, décider de l'appliquer (ou non), puis nettoyer. Vous terminez avec une grille de revue à présenter à votre équipe sécurité. ## Le cahier des charges L'assistant interne doit être livré dans `assistant-dev` avec : 1. une identité dédiée, quatre rôles projet et un rôle sur un seul bucket ; 2. aucune sortie internet, API Google via PSC restreint ; 3. un bucket de documents UE, privé, chiffré CMEK ; 4. un accès réservé à un groupe via IAP, Cloud Run injoignable en direct ; 5. Model Armor, journaux Data Access, bucket de logs à 365 jours ; 6. si l'organisation le permet : périmètre VPC-SC en dry run, contrainte UE, budget. ```mermaid flowchart LR V["1. validate + fmt"] --> P["2. plan (lecture)"] P --> R["3. revue : plan + grille"] R -->|"accord"| A["4. apply (projet de test)"] A --> C["5. check_posture.py"] C --> D["6. destroy + vérification"] R -->|"refus"| V ``` ## Étape 1 et 2 : valider puis planifier ```bash cd files/ cp terraform.tfvars.example terraform.tfvars # à adapter, jamais commité terraform init -backend=false # en équipe : backend "gcs" terraform validate && terraform fmt -check terraform plan -out=plan.tfplan # ne crée rien ``` `plan` lit votre projet (ici le numéro via `data.google_project`) et affiche ce qui **serait** créé. C'est l'artefact que l'on fait relire. > [!TIP] > Ajoutez ces trois commandes à votre CI (intégration continue) sur chaque demande de fusion, et faites tourner un analyseur de politiques (par exemple `gcloud beta terraform vet` ou Checkov) sur le plan exporté en JSON (`terraform show -json plan.tfplan`) : la revue de sécurité devient automatique. ## Étape 3 : la grille de revue | Contrôle | Où le voir dans le plan | Attendu | | --- | --- | --- | | Aucun rôle primitif | `google_project_iam_member.*` | pas de `roles/owner`, `roles/editor` | | Pas d'accès public | bucket, Cloud Run | `public_access_prevention = "enforced"`, pas d'`allUsers` | | Ingress Cloud Run | `google_cloud_run_v2_service.app` | `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` | | Région | toutes les ressources | `europe-west1` / `EUROPE-WEST1` | | CMEK | `google_storage_bucket.kb` | `default_kms_key_name` renseigné | | Sortie réseau | `google_compute_firewall.*` | deny `0.0.0.0/0`, allow PSC seulement | | Audit | `google_project_iam_audit_config.ai` | `DATA_READ`, `DATA_WRITE` | | VPC-SC | `..._service_perimeter.ai` | `use_explicit_dry_run_spec = true` au premier déploiement | ```quiz question: "Dans la revue du plan, vous voyez google_project_iam_binding pour roles/aiplatform.user. Pourquoi bloquer la fusion ?" options: - "Parce que ce rôle n'existe pas" - "Parce que _binding fait autorité sur tous les membres de ce rôle dans le projet : l'apply retirerait l'accès aux autres équipes qui l'ont" - "Parce que les bindings ne fonctionnent pas avec Vertex AI" answer: 1 explanation: "Une ressource _binding remplace la liste complète des membres d'un rôle. Dans un projet partagé, elle supprime silencieusement des accès. Un module applicatif utilise _member." ``` ## Étapes 4 à 6 : appliquer, contrôler, nettoyer Si et seulement si vous êtes dans un **projet de test** et que la revue est validée : ```bash terraform apply plan.tfplan python check_posture.py --project mon-projet-ia-dev # ... tests fonctionnels : DNS privé, 403 en accès direct à run.app, connexion IAP ... terraform destroy gcloud run services list --project=mon-projet-ia-dev --region=europe-west1 --filter="metadata.name~lab-archi-ia" gcloud compute forwarding-rules list --project=mon-projet-ia-dev --filter="name~labarchiia OR name~lab-archi-ia" ``` > [!WARNING] > Après `destroy`, deux éléments **restent** volontairement : les **versions de clé KMS** (Cloud KMS ne supprime pas immédiatement : les versions passent en « destruction programmée », 30 jours par défaut, et le trousseau ne peut jamais être supprimé) et les **API activées** (`disable_on_destroy = false`). Le load balancer, lui, est facturé à l'heure tant qu'il existe : ne laissez pas un `apply` de test tourner. ```quiz question: "Après terraform destroy, la commande gcloud kms keys list montre encore la clé lab-archi-ia-cmek. Est-ce un échec du nettoyage ?" options: - "Oui, il faut supprimer la clé à la main" - "Non : Cloud KMS ne supprime ni trousseaux ni clés ; Terraform programme la destruction des versions, la clé reste listée sans version active" - "Oui, c'est le signe que prevent_destroy était à true" answer: 1 explanation: "C'est le comportement de Cloud KMS, conçu pour éviter les pertes de données irréversibles. Avec prevent_destroy = true, le destroy aurait échoué avec une erreur, pas réussi." ``` ## Exercice final Transformez le module en deux instances, `dev` et `prod`, avec un backend GCS distinct par environnement, des domaines différents, et le périmètre VPC-SC **appliqué** en prod seulement. Décrivez l'arborescence et les fichiers de variables.
Voir la solution ```text plateforme-ia/ ├── module/ ← le contenu de files/*.tf, sans bloc backend ├── envs/ │ ├── dev/ │ │ ├── main.tf module "plateforme" { source = "../../module" ... } │ │ ├── backend.tf backend "gcs" { bucket = "tfstate-ia", prefix = "dev" } │ │ └── dev.tfvars project_id = "assistant-dev", app_domain = "assistant-dev.exemple.fr", vpcsc_dry_run = true │ └── prod/ │ ├── main.tf │ ├── backend.tf prefix = "prod" │ └── prod.tfvars project_id = "assistant-prod", app_domain = "assistant.exemple.fr", vpcsc_dry_run = false ``` Points d'attention : - le bloc `provider` passe dans `envs/*/main.tf` (un module réutilisable ne configure pas son provider) ; - en prod, `prevent_destroy = true` sur la clé KMS, et la clé dans le projet KMS central : ajoutez une variable `kms_key_id` qui, si elle est fournie, remplace la clé créée par le module ; - le passage dry run → appliqué se fait par un simple changement de `vpcsc_dry_run`, après lecture des violations (leçon 2.2).
## Résultat obtenu Chaîne réelle du 2026-10-15 (Terraform 1.16.0, provider 6.50.0 ; projet masqué ; **pas d'apply**) : ```text $ terraform validate Success! The configuration is valid. $ terraform fmt -check && echo FMT_OK FMT_OK $ terraform plan -var project_id= -var app_domain=assistant.exemple.fr \ -var 'iap_members=["group:assistant@exemple.fr"]' ... Plan: 46 to add, 0 to change, 0 to destroy. $ terraform plan -var project_id= -var access_policy_id=123456789012 \ -var billing_account=000000-AAAAAA-BBBBBB -var enforce_eu_location_policy=true ... Plan: 40 to add, 0 to change, 0 to destroy. $ pytest -q 4 passed in 0.01s $ python check_posture.py --project ... 7 contrôles : 1 FAIL, 2 WARN ``` Le second plan n'a pas de load balancer (pas de domaine) mais ajoute le périmètre, la contrainte UE et le budget. Aucune ressource n'a été créée dans le projet : il n'y a donc rien à nettoyer, ce que confirme `gcloud run services list --filter="metadata.name~lab-archi-ia"` (« Listed 0 items »). ## Je sais faire - [ ] Dérouler validate, fmt, plan et faire relire un plan avec une grille - [ ] Appliquer dans un projet de test puis détruire et vérifier - [ ] Expliquer ce qui subsiste après destroy (KMS, API) et pourquoi - [ ] Découper le module en environnements dev et prod