Leçon 28 — Le code Terraform, couche par couche

12. `terraform.tfvars.example` : la configuration du déploiement

16 min de lecture

Le modèle du fichier où l'on donne ses valeurs (projet, groupes, connecteurs). C'est le seul fichier à modifier pour un déploiement standard. Bases Terraform : 00-bases-terraform.md.


1. À quoi sert ce fichier

Les fichiers .tf décrivent comment construire le socle ; ils utilisent des variables (00-bases §6.3) déclarées dans variables.tf. Un fichier .tfvars donne les valeurs de ces variables pour un déploiement précis. terraform.tfvars.example est un modèle : on le copie en terraform.tfvars, on remplace les valeurs, et Terraform le lit tout seul à chaque plan et apply. Il ne contient aucun secret : seulement des noms.


2. Vue d'ensemble

Le fichier suit l'ordre de ses commentaires :

Partie du fichierVariablesRôle en une phrase
En-tête(aucune)Comment créer terraform.tfvars et ne jamais le committer.
Projet et localisationproject_id, ge_location (en commentaire)Le projet Google Cloud cible et la zone des données.
Groupes et alertesuser_groups, admin_groups, security_groups, viewer_groups, alert_emailQui a accès à quoi, et qui reçoit les alertes.
Phrases interditesbanned_phrases (en commentaire)Expressions que l'assistant ne doit jamais produire.
ConnecteursconnectorsLes sources de données : une entrée par source.

Toutes les variables absentes du fichier prennent leur valeur par défaut de variables.tf (voir §3.6).


3. Le contenu expliqué, partie par partie

Rappel : qu'est-ce qu'un .tfvars ?

  • Une simple liste nom_de_variable = valeur, en syntaxe HCL. Pas de blocs resource, pas de var. : uniquement des valeurs.
  • Terraform charge automatiquement terraform.tfvars (et les fichiers *.auto.tfvars). Un autre nom (par exemple recette.tfvars) se passe à la main : terraform plan -var-file=recette.tfvars.
  • Une variable sans default dans variables.tf est obligatoire : si elle manque, plan s'arrête. Ici : project_id, user_groups, admin_groups, security_groups.

3.1 L'en-tête

# cp terraform.tfvars.example terraform.tfvars   (ne jamais committer terraform.tfvars)
  • La commande à lancer dans le dossier terraform/ pour créer votre fichier.
  • Pourquoi ne jamais committer terraform.tfvars : il décrit un environnement réel (projet de production, adresses, groupes, identifiants de tenant). Ce n'est pas secret au sens « mot de passe », mais ce sont des informations internes qui n'ont rien à faire dans un dépôt partagé. Le .example, lui, est committé : c'est la documentation du format.
  • Le projet contient un fichier .gitignore qui exclut déjà terraform.tfvars, *.tfstate*, *.tfplan et .terraform/. Vérifiez qu'il est bien présent si vous copiez le projet ailleurs.

3.2 Projet et localisation

project_id = "acme-gemini-enterprise-prd"
# ge_location = "eu"   # IMMUABLE
  • project_id : l'ID du projet Google Cloud (le texte, pas le numéro). Obligatoire. Il doit déjà exister, dans l'organisation de l'entreprise, avec la facturation activée (guide du socle (docs/00) §12.1).
  • ge_location : en commentaire, donc le défaut "eu" s'applique (données en Union européenne). Une validation dans variables.tf n'accepte que "eu" ou "us" (CMEK impossible en global). IMMUABLE : la changer après coup recrée l'app et ses données. Décidez-la une fois pour toutes.

3.3 Groupes et alertes

user_groups     = ["group:gemini-utilisateurs@acme.example"]
admin_groups    = ["group:gemini-admins@acme.example"]
security_groups = ["group:securite-ia@acme.example"]
viewer_groups   = ["group:gemini-pilotage@acme.example"]
alert_email     = "securite-ia@acme.example"
  • Ce sont des listes (crochets), même avec un seul groupe : on peut en mettre plusieurs, séparés par des virgules.
  • Le préfixe group: est le format IAM de Google pour désigner un groupe (les autres formats sont user: et serviceAccount:). Le socle n'attribue des droits qu'à des groupes, jamais à des personnes (guide du socle (docs/00) §5.2) : on ajoute ou retire quelqu'un dans l'annuaire, sans toucher à Terraform.
VariableQuiCe que ça donne
user_groupsLes utilisateurs de l'appRôle utilisateur Gemini Enterprise.
admin_groupsLes administrateurs de l'app (petit groupe)Rôle administrateur Gemini Enterprise.
security_groupsL'équipe sécuritéSeule à gérer Model Armor, les clés KMS et à lire les journaux privés.
viewer_groupsPilotage, DPOLecture seule et statistiques. Facultatif (défaut []).
  • alert_email : la boîte qui reçoit les alertes de 70_detection.tf. Vide ("", défaut) = pas de canal e-mail, donc des alertes que personne ne reçoit. Mettez une liste de diffusion d'équipe.

Le détail des rôles est dans 04-identity-access.md.

3.4 Phrases interdites

# banned_phrases = ["nom de code projet confidentiel"]
  • En commentaire, donc défaut [] : aucune phrase interdite.
  • Pour l'activer : retirer le # et lister les expressions (par exemple des noms de code de projets confidentiels). Chaque élément devient un bloc banned_phrases de l'assistant (voir 08-app.md, bloc dynamic).

3.5 Les connecteurs

L'en-tête de la section :

# -----------------------------------------------------------------------------
# CONNECTEURS - une entrée par source. La clé (ex. "gdrive") devient l'ID de collection.
# Les secrets sont créés AVANT par l'équipe sécurité dans Secret Manager ;
# ici on ne met que le NOM du secret.
#   gcloud secrets create sharepoint-client-secret --replication-policy=user-managed --locations=europe-west1
#   printf '%s' 'VALEUR' | gcloud secrets versions add sharepoint-client-secret --data-file=-
# Les noms exacts de paramètres d'une source sont à confirmer dans la page du connecteur
# (docs.cloud.google.com/gemini/enterprise/docs/connectors/...) : marqués [à confirmer].
# -----------------------------------------------------------------------------

Le principe. connectors est une map : chaque clé (gdrive, sharepoint…) est un connecteur, et sa valeur est un objet qui le décrit. 40_connectors.tf crée un connecteur par entrée grâce à for_each (voir 07-connectors.md). La clé devient l'ID de collection chez Google : choisissez-la courte, en minuscules, et ne la changez plus (la changer = supprimer et recréer le connecteur).

Les champs possibles (déclarés dans variables.tf) :

ChampObligatoire ?DéfautRôle
data_sourceouiLe type de source Google (google_drive, sharepoint, github…).
display_nameouiLe nom affiché.
modesnon["FEDERATED"]FEDERATED = interrogation en direct, sans copie ; DATA_INGESTION = copie indexée chez Google.
paramsnon{}Paramètres non secrets (URL, ID de tenant…).
secret_paramsnon{}Paramètres secrets : « nom du paramètre → nom du secret dans Secret Manager ».
entitiesnon[]Les types d'objets à interroger (fichiers, tickets…), chacun avec un name.
refresh_intervalnon"86400s"Fréquence de synchronisation (1 jour). Utile surtout en ingestion.
static_ipnonfalseAppeler la source depuis des IP fixes. Immuable.

Les secrets, par leur NOM. Le principe central de la sécurité des connecteurs (guide du socle (docs/00) §6.2) :

  1. Avant Terraform, l'équipe sécurité crée chaque secret et sa valeur avec gcloud :
    • gcloud secrets create sharepoint-client-secret --replication-policy=user-managed --locations=europe-west1 : crée le secret (une « boîte » vide), stocké uniquement en europe-west1 (UE) ;
    • printf '%s' 'VALEUR' | gcloud secrets versions add sharepoint-client-secret --data-file=- : met la valeur dedans. printf '%s' évite d'ajouter un retour à la ligne parasite ; --data-file=- lit la valeur depuis l'entrée standard, ce qui évite de la voir apparaître dans les arguments de la commande.
  2. Dans terraform.tfvars, on écrit seulement le nom : client_secret = "sharepoint-client-secret".
  3. Terraform lit le secret par son nom, donne au compte de Gemini Enterprise le droit de le lire (20_data_protection.tf), puis passe au connecteur une référence de la forme projects/<numéro>/secrets/sharepoint-client-secret/versions/latest. Gemini Enterprise lit la valeur au moment de l'appel. La valeur ne passe jamais par le code, ni par l'état Terraform.

Le marqueur [à confirmer] : un nom de paramètre ou de source pris dans la documentation Google, mais pas encore vérifié sur un vrai déploiement (guide du socle (docs/00) §16). Au premier apply, si Google refuse le paramètre, lire la page du connecteur (lien dans le commentaire) ou créer le connecteur une fois dans la console et comparer.

Voyons maintenant chaque entrée.

gdrive et gcalendar : Google Workspace

connectors = {
  # Google Workspace : application OAuth gérée par Google, aucun paramètre.
  gdrive = {
    data_source  = "google_drive"
    display_name = "Google Drive"
  }
  gcalendar = {
    data_source  = "google_calendar"
    display_name = "Google Agenda"
  }
  • Seuls les deux champs obligatoires. Tout le reste prend son défaut : mode fédéré, pas de secret (l'application OAuth est gérée par Google), pas d'IP fixe.
  • Fédéré = chacun ne voit que les fichiers et événements auxquels il a déjà accès dans Drive / Agenda.

gmail : désactivé

  # gmail = { data_source = "google_mail", display_name = "Gmail" }   # sur décision explicite (minimisation)
  • En commentaire volontairement : la messagerie contient beaucoup de données personnelles. À activer seulement sur décision explicite (principe de minimisation, guide du socle (docs/00) §7.3).
  • Exemple d'écriture sur une seule ligne : entre accolades, les champs sont alors séparés par des virgules. Sur plusieurs lignes, pas de virgule.

sharepoint : Microsoft 365

  # Microsoft 365 : application Entra ID (mono-tenant, consentement admin).
  # Avec l'identité Google, les sources Microsoft sont en FEDERATED (l'ingestion exigerait Entra ID).
  sharepoint = {
    data_source  = "sharepoint" # [à confirmer] la doc montre aussi "sharepoint_federated_search"
    display_name = "SharePoint"
    params = {
      instance_uri = "https://acme.sharepoint.com"
      tenant_id    = "00000000-0000-0000-0000-000000000000"
    }
    secret_params = {
      client_id     = "sharepoint-client-id"
      client_secret = "sharepoint-client-secret"
    }
    entities  = [{ name = "file" }]
    static_ip = true
  }
  • Entra ID (ex-Azure AD) est l'annuaire de Microsoft. On y crée une application (mono-tenant, avec consentement administrateur) que Gemini Enterprise utilise pour interroger SharePoint.
  • data_source = "sharepoint" : [à confirmer], la doc mentionne aussi "sharepoint_federated_search". Repli : essayer l'autre valeur (guide du socle (docs/00) §16).
  • params (non secrets) :
    • instance_uri : l'adresse du SharePoint de l'entreprise ;
    • tenant_id : l'identifiant du tenant (de l'« instance ») Microsoft 365. Les zéros sont à remplacer par le vrai ID (visible dans le portail Entra).
  • secret_params : l'ID et le secret de l'application Entra. On range aussi client_id dans Secret Manager, par prudence et pour tout gérer au même endroit. Les valeurs à droite sont des noms de secrets.
  • entities = [{ name = "file" }] : interroger les fichiers. Une liste d'objets : on peut en ajouter d'autres.
  • static_ip = true : IP fixes, à autoriser côté Microsoft (terraform output connector_static_ips, 11-outputs.md).
  • Pas de modes : défaut ["FEDERATED"]. Le commentaire explique pourquoi : avec l'identité Google, l'ingestion des sources Microsoft exigerait Entra ID comme fournisseur d'identité.

teams

  teams = {
    data_source  = "teams" # recherche fédérée uniquement
    display_name = "Microsoft Teams"
    params       = { tenant_id = "00000000-0000-0000-0000-000000000000" } # [à confirmer]
    secret_params = {
      client_id     = "teams-client-id"
      client_secret = "teams-client-secret"
    }
    static_ip = true
  }
  • Teams n'existe qu'en mode fédéré chez Google.
  • params : seulement le tenant_id, [à confirmer] (nom exact du paramètre à vérifier).
  • Ses propres secrets (teams-client-id, teams-client-secret) : on peut utiliser une application Entra différente de SharePoint, avec ses propres droits minimaux.
  • Pas d'entities : défaut [].

github

  # GitHub : une GitHub App (pas de jeton personnel), installée sur l'organisation.
  github = {
    data_source   = "github"
    display_name  = "GitHub"
    params        = { organization = "acme" } # [à confirmer]
    secret_params = { client_id = "github-client-id", client_secret = "github-client-secret" }
    static_ip     = true
  }
  • Une GitHub App (pas un jeton personnel, qui serait lié à une personne et souvent trop puissant), installée sur l'organisation GitHub, avec les permissions minimales.
  • params = { organization = "acme" } : l'organisation GitHub. Nom du paramètre [à confirmer].
  • secret_params écrit sur une ligne : deux noms de secrets, séparés par une virgule.

jira : un exemple d'ingestion (en commentaire)

  # Jira : exemple d'ingestion (copie indexée, chiffrée CMEK, droits Jira synchronisés).
  # jira = {
  #   data_source  = "jira"
  #   display_name = "Jira"
  #   modes        = ["DATA_INGESTION"]
  #   params       = { instance_uri = "https://acme.atlassian.net", instance_id = "...", auth_type = "OAUTH" }
  #   secret_params = { client_id = "jira-client-id", client_secret = "jira-client-secret", refresh_token = "jira-refresh-token" }
  #   entities     = [{ name = "project" }, { name = "issue" }, { name = "comment" }]
  #   static_ip    = true
  # }
  • Désactivé : c'est un modèle pour le cas où l'on choisit l'ingestion.
  • modes = ["DATA_INGESTION"] : les données sont copiées chez Google. Elles sont alors chiffrées avec la clé CMEK de l'entreprise (automatique dans 40_connectors.tf dès que le mode contient DATA_INGESTION).
  • params : l'adresse Atlassian, l'ID de l'instance (à remplir) et le type d'authentification.
  • secret_params : trois secrets, dont un refresh_token (jeton qui permet d'obtenir de nouveaux accès).
  • entities : trois types d'objets (projets, tickets, commentaires) : en ingestion, il faut dire quoi copier.
  • Pourquoi l'ingestion n'est pas le défaut : voir guide du socle (docs/00) §7.2.

Databricks

  # Databricks : pas de connecteur natif. Voir le guide, section "Databricks".
}
  • Il n'existe pas de data_source Databricks : les options (serveur MCP, agent dédié, publication vers BigQuery) sont décrites dans le guide du socle (docs/00) §7.4. L'accolade } ferme la map connectors.

3.5 bis Journalisation

# -----------------------------------------------------------------------------
# JOURNALISATION (voir guide §10.4)
# -----------------------------------------------------------------------------
# log_prompts_responses       = false  # texte complet des questions/réponses : décision DPO/CSE
# security_log_retention_days = 365
# usage_log_retention_days    = 90
# lock_security_logs          = false  # true en production : rétention non modifiable (irréversible)
# link_logs_to_bigquery       = false  # true pour des tableaux de bord Looker Studio

Tout est commenté : les valeurs par défaut s'appliquent (le plus sobre). Pour activer un réglage, retirez le # et changez la valeur. Chaque réglage est une décision à tracer (DPO pour les deux premiers, sécurité pour le verrouillage). Explications : 13-logging.md et 02-variables.md.

3.6 Les variables absentes : leurs valeurs par défaut

Tout ce qui n'est pas dans le fichier prend la valeur de variables.tf. Les principales :

VariableDéfautEffet
region"europe-west1"Région des ressources régionales (Agent Gateway).
app_id / app_display_name / company_name"assistant-ia" / "Assistant IA" / "Acme"Identité de l'app.
enable_cmektrueChiffrement avec les clés de l'entreprise.
session_ttl_days30Conservation des conversations (à valider avec le DPO).
armor_confidence"MEDIUM_AND_ABOVE"Sévérité des filtres Model Armor.
sdp_mode"advanced"Détecteurs de données sensibles français / UE.
log_screened_contentfalsePas de contenu filtré dans les journaux.
web_grounding"WEB_GROUNDING_TYPE_DISABLED"Pas de recherche web.
features4 réglages (partages désactivés, feedback activé)Voir 08-app.md.
enable_observabilitytrueTraces sans contenu.
enable_agent_gateway / vpc_sc_access_policyfalse / ""Options avancées désactivées (09-advanced.md).

Pour en changer une, ajoutez simplement la ligne dans terraform.tfvars, par exemple session_ttl_days = 15.


4. Ce que Terraform crée concrètement

Ce fichier ne crée rien lui-même : il paramètre les autres. Avec ces valeurs, les points marquants :

  • 5 connecteurs (gdrive, gcalendar, sharepoint, teams, github), tous en mode fédéré, dont 3 avec IP fixes ;
  • 6 secrets référencés (2 par source tierce) ; ils doivent déjà exister dans Secret Manager ;
  • des droits pour 4 groupes ;
  • un canal d'alerte vers securite-ia@acme.example ;
  • tout le reste selon les défauts (CMEK activé, conservation 30 jours, pas de web, options avancées désactivées).

5. Dépendances avec les autres fichiers

ValeurDéclarée dansUtilisée dansFiche
Toutesvariables.tf (types, défauts, validations)02-variables.md
Groupes10_identity_access.tf04-identity-access.md
Noms des secrets20_data_protection.tf, 40_connectors.tf05-data-protection.md
connectors40_connectors.tf, outputs.tf07-connectors.md, 11-outputs.md
banned_phrases50_app.tf08-app.md
alert_email70_detection.tf10-detection.md

6. Pour reproduire / adapter

Besoin type : ajouter un nouveau connecteur.

connectors = {
  # ... connecteurs existants ...

  confluence = {                                   # clé = ID de collection : courte, stable, minuscules
    data_source   = "confluence"                   # type de source (page du connecteur dans la doc Google)
    display_name  = "Confluence"
    # modes       = ["FEDERATED"]                  # défaut ; "DATA_INGESTION" seulement sur décision justifiée
    params        = { instance_uri = "https://monentreprise.atlassian.net" }   # NON secret [à confirmer]
    secret_params = {                              # paramètre => NOM du secret (jamais la valeur)
      client_id     = "confluence-client-id"
      client_secret = "confluence-client-secret"
    }
    # entities    = [{ name = "page" }]            # si la source en exige [à confirmer]
    static_ip     = true                           # IMMUABLE : décider dès la création
  }
}
  1. Côté source : créer l'application OAuth (Entra ID, GitHub App, Atlassian…) avec les permissions minimales en lecture et l'URL de redirection https://vertexaisearch.cloud.google.com/oauth-redirect (guide du socle (docs/00) §7.6).
  2. Équipe sécurité : créer les secrets en UE, avant Terraform :
    gcloud secrets create confluence-client-secret --replication-policy=user-managed --locations=europe-west1
    printf '%s' 'VALEUR' | gcloud secrets versions add confluence-client-secret --data-file=-
  3. Code : ajouter l'entrée dans terraform.tfvars ; reporter le format (sans valeurs réelles) dans terraform.tfvars.example pour documenter. Noms des paramètres : page du connecteur (docs.cloud.google.com/gemini/enterprise/docs/connectors/…) et doc Terraform https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/discovery_engine_data_connector
  4. terraform plan : vérifier qu'il n'y a qu'un connecteur ajouté (+), et lire si l'app est recréée (voir §7).
  5. Après apply : autoriser les IP (terraform output connector_static_ips) côté source, faire l'autorisation OAuth initiale dans la console, vérifier terraform output connector_states.
  6. Recette : un utilisateur sans droit sur un document de test ne doit pas le voir dans l'assistant.

7. Pièges et bonnes pratiques

  • Ne jamais committer terraform.tfvars (ni terraform.tfstate). Ajoutez-les à un .gitignore.
  • Jamais de valeur secrète dans ce fichier : seulement des noms de secrets. Une valeur ici finirait dans Git, dans l'état et dans les journaux de CI.
  • Les secrets doivent exister avant plan : Terraform les lit par leur nom ; un secret absent fait échouer le plan (« secret not found »). Vérifiez l'orthographe exacte.
  • Clé de connecteur = immuable en pratique : la renommer supprime et recrée le connecteur (et ses data stores).
  • static_ip et ge_location sont immuables : les décider dès la création.
  • Ajouter un connecteur modifie data_store_ids de l'app, ce qui peut recréer l'app (bug connu du provider, guide du socle (docs/00) §16). Lisez le plan ; idéalement, déclarez tous les connecteurs avant l'ouverture aux utilisateurs.
  • [à confirmer] = à vérifier au premier déploiement. Retirez le marqueur une fois le nom validé, pour que la prochaine personne sache que c'est sûr.
  • Les ID de tenant 00000000-… sont des exemples : les laisser ferait échouer le connecteur.
  • Préfixe group: obligatoire dans les listes de groupes : sans lui, Google refuse le membre IAM.
  • Plusieurs environnements (recette, production) : un fichier par environnement (recette.tfvars, prod.tfvars) passé avec -var-file=…, et un état séparé pour chacun.
Suivant