IAM au moindre privilège et comptes de service
Les trois mots de l'IAM
IAM (Identity and Access Management) répond à « qui peut faire quoi sur quelle ressource ». Une liaison (binding) associe :
- un membre :
user:,group:,serviceAccount:(identité d'un programme) ; - un rôle : un paquet de permissions, par exemple
roles/aiplatform.userqui permet d'appeler les modèles Vertex AI ; - une ressource : projet, bucket, clé KMS, service Cloud Run…
Le moindre privilège consiste à donner le plus petit rôle, sur la plus petite ressource, au plus petit groupe.
flowchart LR
subgraph Mauvais["À éviter"]
A1["Compute par défaut"] -->|"roles/editor sur le projet"| R1["Tout le projet"]
end
subgraph Bon["Le module"]
A2["lab-archi-ia-app (dédié)"] -->|"roles/aiplatform.user (projet)"| R2["Appeler Gemini"]
A2 -->|"roles/storage.objectViewer (1 bucket)"| R3["Lire les documents"]
A2 -->|"roles/modelarmor.user"| R4["Filtrer prompts et réponses"]
end
| Rôle | Pourquoi l'assistant en a besoin | Ce qu'il ne permet pas |
|---|---|---|
roles/aiplatform.user | appeler Gemini sur Vertex AI | supprimer des ressources Vertex, gérer IAM |
roles/storage.objectViewer sur le bucket | lire les documents de connaissances | lire les autres buckets, écrire |
roles/modelarmor.user | appeler Model Armor | modifier les modèles Model Armor |
roles/logging.logWriter, roles/monitoring.metricWriter | écrire ses journaux et métriques | lire les journaux des autres |
resource "google_service_account" "app" {
account_id = "${var.prefix}-app"
display_name = "Assistant interne (Cloud Run)"
}
resource "google_project_iam_member" "app_roles" {
for_each = toset([
"roles/aiplatform.user",
"roles/logging.logWriter",
"roles/monitoring.metricWriter",
"roles/modelarmor.user",
])
project = var.project_id
role = each.value
member = google_service_account.app.member
}
# Lecture du seul bucket de connaissances, pas de tous les buckets du projet.
resource "google_storage_bucket_iam_member" "app_reads_kb" {
bucket = google_storage_bucket.kb.name
role = "roles/storage.objectViewer"
member = google_service_account.app.member
}Pas de clés de comptes de service
Une clé de compte de service est un fichier JSON qui permet à n'importe qui le possédant d'agir comme ce compte, sans expiration par défaut. C'est la première cause de fuite d'identifiants cloud. Les alternatives :
| Où tourne le code | Identité sans clé |
|---|---|
| Cloud Run, Cloud Functions, GKE | le compte de service attaché (ici template.service_account) |
| Poste d'un développeur | gcloud auth application-default login (ses propres droits) |
| CI GitHub / GitLab | Workload Identity Federation (jeton OIDC échangé contre un jeton Google) |
La contrainte d'organisation iam.disableServiceAccountKeyCreation interdit d'en créer.
Le compte Compute par défaut (PROJECT_NUMBER-compute@developer.gserviceaccount.com) a souvent roles/editor. Pourquoi est-ce dangereux pour une plateforme IA ?
roles/editor donne l'écriture sur la quasi-totalité des ressources. Tout service lancé sans compte explicite l'utilise. Un compte dédié à rôles minimaux limite les dégâts d'une compromission. Les comptes de service ne sont pas facturés.
Contrôler la posture en lecture seule
Le script check_posture.py appelle trois API en lecture (Service Usage, Resource Manager getIamPolicy, IAM) puis passe le tout à une fonction pure, evaluate, testable sans réseau.
for b in bindings:
for m in b.get("members", []):
if m in PUBLIC: # allUsers, allAuthenticatedUsers
public.append(f"{m} -> {b['role']}")
if m == default_sa:
default_sa_roles.append(b["role"])
if b["role"] in PRIMITIVE: # roles/owner, roles/editor
(sa_primitive if m.startswith("serviceAccount:") else humans_primitive).append(m)Le rapport masque les adresses (user:al***@exemple.fr) : un rapport circule, il ne doit pas devenir une fuite de données personnelles.
pip install -r requirements.txt
pytest -q # 4 tests, sans réseau
gcloud auth application-default login
python check_posture.py --project mon-projet # code de sortie 1 s'il y a un FAILQuelle ressource Terraform utiliser pour ajouter roles/aiplatform.user à un groupe sans risquer d'effacer les autres membres de ce rôle ?
_member ajoute une seule liaison membre-rôle. _binding fait autorité sur la liste complète des membres d'un rôle, et _policy sur toute la politique du projet : les deux peuvent retirer des accès posés ailleurs.
Exercice
Ajoutez au script un contrôle : « aucun utilisateur individuel (user:) ne doit avoir roles/aiplatform.admin ; seuls des groupes le peuvent ». Écrivez aussi le test correspondant.
Voir la solution
Dans evaluate, après la boucle :
admins = [m for b in bindings if b["role"] == "roles/aiplatform.admin"
for m in b.get("members", []) if m.startswith("user:")]
out.append(("WARN" if admins else "OK", "Admins Vertex AI nominatifs",
", ".join(mask(m) for m in admins) or "aucun (groupes uniquement)"))Et dans test_check_posture.py :
def test_admin_vertex_nominatif():
policy = {"bindings": [{"role": "roles/aiplatform.admin", "members": ["user:bob@exemple.fr"]}]}
r = levels(evaluate(N, set(EXPECTED_APIS), policy, {}))
assert r["Admins Vertex AI nominatifs"] == "WARN"Résultat obtenu
Exécution réelle le 2026-10-15 sur un projet de test (identifiant masqué), en lecture seule :
$ pytest -q
.... [100%]
4 passed in 0.01s
$ python check_posture.py --project <projet-de-test>
[WARN] API attendues manquantes : cloudkms.googleapis.com, iap.googleapis.com, modelarmor.googleapis.com, accesscontextmanager.googleapis.com
[OK ] Accès public (allUsers) aucun
[FAIL] Compte Compute par défaut a roles/editor
[OK ] Comptes de service owner/editor aucun
[OK ] Humains owner/editor 1 (cible : 3 au plus, idéalement via un groupe)
[OK ] Clés de comptes de service aucune clé utilisateur
[WARN] Audit accès aux données Vertex AI manque DATA_READ, DATA_WRITE
7 contrôles : 1 FAIL, 2 WARNLecture : le projet de test a le défaut classique (compte Compute par défaut en roles/editor), et les journaux d'accès aux données de Vertex AI ne sont pas activés. Le module Terraform corrige le second point (leçon 3.2) ; le premier se corrige en retirant le rôle après avoir donné un compte dédié à chaque service.
Je sais faire
- Choisir des rôles prédéfinis minimaux et les poser à la bonne granularité (projet ou ressource)
- Expliquer pourquoi
_memberplutôt que_bindingou_policy - Remplacer une clé de compte de service par une identité sans clé
- Lancer un contrôle de posture IAM en lecture seule et interpréter le rapport