La configuration IAM qui a permis de lancer votre plateforme data n’est pas celle qui devrait la faire tourner au quotidien. Chaque équipe que j’ai auditée a accumulé ce que j’appelle une dette IAM : des rôles Editor attribués parce qu’ils « marchaient tout de suite », des service accounts partagés parce que créer de nouveaux comptes semblait bureaucratique, et des clés de service accounts commitées dans des dépôts parce que le déploiement devait partir en production.
Cette dette s’accumule. Quand vous ne pouvez pas répondre à « qui peut accéder à cette table ? » ou expliquer un pic de coûts BigQuery lié à des requêtes dont personne ne se souvient, vous payez les intérêts de raccourcis pris il y a des mois ou des années.
Corriger la dette IAM demande deux phases : auditer l’existant, puis mettre en place des pratiques qui empêchent la réaccumulation. Ce guide couvre les deux, avec un focus sur BigQuery et les services de la plateforme data autour.
L’audit IAM révèle ce que vous avez ignoré
Commencez par dresser un état des lieux de votre situation actuelle. Exécutez ces requêtes sur votre organisation GCP pour faire ressortir les problèmes les plus courants.
Listez tous les principals avec un rôle Editor ou Owner au niveau projet :
gcloud projects get-iam-policy YOUR_PROJECT_ID \ --flatten="bindings[].members" \ --format="table(bindings.role,bindings.members)" \ --filter="bindings.role:(roles/editor OR roles/owner)"Identifiez les service accounts avec des clés :
gcloud iam service-accounts list --project=YOUR_PROJECT_ID \ --format="value(email)" | while read sa; do keys=$(gcloud iam service-accounts keys list --iam-account="$sa" \ --format="value(name)" --filter="keyType=USER_MANAGED" 2>/dev/null) if [ -n "$keys" ]; then echo "Service account with keys: $sa" fidoneRepérez les service accounts utilisés par plusieurs workloads en consultant l’historique récent des jobs BigQuery :
SELECT user_email, COUNT(DISTINCT job_id) AS job_count, COUNT(DISTINCT REGEXP_EXTRACT(query, r'FROM `([^`]+)`')) AS tables_accessedFROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECTWHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY) AND user_email LIKE '%.iam.gserviceaccount.com'GROUP BY user_emailORDER BY job_count DESCUn service account qui exécute des centaines de requêtes différentes sur des dizaines de tables est presque certainement partagé entre plusieurs workloads. C’est votre première cible de remédiation.
Le pattern RBAC à 2 couches structure efficacement les accès
L’erreur fondamentale des équipes est d’attribuer les rôles IAM directement à des utilisateurs individuels. Cela crée une charge de gestion (ajouter les permissions une par une pour chaque nouvelle recrue) et rend les audits confus (pourquoi cette personne précise a-t-elle le rôle Data Editor sur ce dataset ?).
Le pattern à 2 couches sépare l’accès aux objets de la fonction métier :
Couche 1 : Les rôles IAM prédéfinis définissent les actions possibles sur les ressources BigQuery. Les trois que vous utiliserez le plus :
roles/bigquery.dataViewerpermet de lire les tables et les vuesroles/bigquery.dataEditorpermet de lire, écrire et supprimer les données des tablesroles/bigquery.dataOwnerdonne le contrôle total, y compris le partage
Couche 2 : Les Google Groups représentent les fonctions métier. Créez des groupes comme :
data-loaders@yourdomain.compour les service accounts et les personnes qui écrivent les données brutesdata-engineers@yourdomain.compour ceux qui transforment et modélisent les donnéesdata-analysts@yourdomain.compour ceux qui requêtent les tables de production sans pouvoir les modifier
Attribuez les rôles aux groupes, pas aux individus. Quand quelqu’un rejoint l’équipe, ajoutez-le au groupe approprié. Quand il part, retirez-le. Les bindings de rôles ne changent jamais.
# Accorder au groupe analystes un accès en lecture sur un dataset de productiongcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ --member="group:data-analysts@yourdomain.com" \ --role="roles/bigquery.dataViewer" \ --condition='expression=resource.name.startsWith("projects/YOUR_PROJECT_ID/datasets/prod_"),title=prod-datasets-only'La condition IAM restreint le rôle aux datasets préfixés par prod_. Les analystes accèdent aux données de production sans pouvoir lire les datasets de développement.
Un service account par workload remplace les credentials partagés
Le problème des service accounts partagés est omniprésent. Un unique etl-service-account@project.iam.gserviceaccount.com exécute les DAGs Airflow, les Cloud Run Jobs, les scheduled queries, et peut-être quelques cron jobs sur une instance Compute Engine dont personne ne se souvient.
Quand quelque chose casse ou que les coûts explosent, impossible de déterminer quel workload est en cause. Quand vous devez faire une rotation des credentials, vous risquez de tout casser.
Créez un service account par workload, avec des permissions limitées à ce dont ce workload a exactement besoin.
Une convention de nommage qui rend les logs lisibles :
crj-dbt-daily@project.iam.gserviceaccount.com # Cloud Run Job pour le dbt quotidiencmp-extraction-dag@project.iam.gserviceaccount.com # DAG Composer pour l'extractionwlif-github-actions@project.iam.gserviceaccount.com # Workload Identity Federation pour la CILe préfixe indique la plateforme de compute (crj pour Cloud Run Jobs, cmp pour Composer, wlif pour Workload Identity Federation). Quand vous voyez une requête dans INFORMATION_SCHEMA.JOBS, le nom du service account vous dit exactement quel workload l’a exécutée.
Remplacez les clés par l’impersonation :
Les clés de service accounts sont des credentials qui peuvent être copiées, commitées dans des dépôts et utilisées depuis n’importe où. L’impersonation de service account délivre des credentials temporaires qui exigent que l’appelant s’authentifie d’abord.
# Autoriser un utilisateur à impersonner un service accountgcloud iam service-accounts add-iam-policy-binding \ crj-dbt-daily@project.iam.gserviceaccount.com \ --member="user:engineer@yourdomain.com" \ --role="roles/iam.serviceAccountTokenCreator"L’ingénieur peut maintenant exécuter dbt en local avec les credentials de production :
gcloud auth application-default login --impersonate-service-account=crj-dbt-daily@project.iam.gserviceaccount.comdbt runLe journal d’audit montre à la fois l’identité humaine et le service account impersonné. Pas de clés à faire tourner. Pas de credentials à fuiter.
La sécurité au niveau des colonnes repose sur les policy tags, pas sur les vues
Avant les policy tags, les équipes créaient des vues pour masquer les colonnes sensibles :
-- Ne faites plus çaCREATE VIEW safe_customers ASSELECT customer_id, signup_date, country-- SSN et email délibérément omisFROM raw_customersCela génère une charge de maintenance (les nouvelles colonnes nécessitent de mettre à jour les vues), une dégradation des performances (des vues sur des vues sur des vues) et des failles de gouvernance (qui se souvient de quelles vues masquent quelles colonnes ?).
Les policy tags de Data Catalog offrent une sécurité au niveau des colonnes directement sur la couche de stockage. Leur mise en place se fait en quatre étapes.
- Créez une taxonomie avec des catégories hiérarchiques :
gcloud data-catalog taxonomies create "PII" \ --location=us \ --description="Personally identifiable information"
gcloud data-catalog taxonomies policy-tags create "High_Sensitivity" \ --taxonomy="projects/YOUR_PROJECT/locations/us/taxonomies/PII" \ --description="SSN, passport numbers, financial accounts"
gcloud data-catalog taxonomies policy-tags create "Medium_Sensitivity" \ --parent-policy-tag="projects/YOUR_PROJECT/locations/us/taxonomies/PII/policyTags/High_Sensitivity" \ --description="Email, phone, address"- Activez le contrôle d’accès sur la taxonomie :
gcloud data-catalog taxonomies set-iam-policy \ "projects/YOUR_PROJECT/locations/us/taxonomies/PII" \ policy.json- Taguez les colonnes dans vos schémas BigQuery. Vous pouvez le faire dans la Console, via l’API ou avec Terraform :
resource "google_bigquery_table" "customers" { # ... config de la table ...
schema = jsonencode([ { name = "email" type = "STRING" policyTags = { names = ["projects/YOUR_PROJECT/locations/us/taxonomies/PII/policyTags/Medium_Sensitivity"] } } ])}- Accordez l’accès aux utilisateurs qui ont besoin de voir les colonnes taguées :
gcloud data-catalog taxonomies policy-tags add-iam-policy-binding \ "projects/YOUR_PROJECT/locations/us/taxonomies/PII/policyTags/Medium_Sensitivity" \ --member="group:data-analysts@yourdomain.com" \ --role="roles/datacatalog.categoryFineGrainedReader"Taguez au niveau le plus haut approprié dans votre hiérarchie. Si un groupe doit voir toutes les données Medium_Sensitivity, accordez l’accès à ce niveau. La permission se propage aux tags enfants. Gérer dix catégories reste faisable ; gérer des centaines de tags individuels par colonne ne l’est pas.
La row-level security filtre sans maintenir de vues séparées
La sécurité au niveau des colonnes contrôle quels champs un utilisateur voit. La row-level security contrôle quels enregistrements.
L’approche classique crée des vues filtrées par segment :
-- Des vues régionales que personne ne veut maintenirCREATE VIEW sales_emea AS SELECT * FROM sales WHERE region = 'EMEA';CREATE VIEW sales_apac AS SELECT * FROM sales WHERE region = 'APAC';CREATE VIEW sales_americas AS SELECT * FROM sales WHERE region = 'AMERICAS';Les Row Access Policies de BigQuery remplacent cela par un filtrage dynamique basé sur l’utilisateur qui exécute la requête :
CREATE ROW ACCESS POLICY region_filterON project.dataset.salesGRANT TO ("group:sales-emea@yourdomain.com")FILTER USING (region = 'EMEA');
CREATE ROW ACCESS POLICY region_filter_apacON project.dataset.salesGRANT TO ("group:sales-apac@yourdomain.com")FILTER USING (region = 'APAC');Désormais, quand un utilisateur du groupe sales-emea requête la table sales, il ne voit que les lignes EMEA. Pas de gestion de vues. Le filtre s’applique automatiquement.
Pour un filtrage plus dynamique basé sur les attributs de l’utilisateur, utilisez SESSION_USER() :
CREATE ROW ACCESS POLICY manager_sees_their_teamON project.dataset.employee_metricsGRANT TO ("group:managers@yourdomain.com")FILTER USING ( manager_email = SESSION_USER() OR SESSION_USER() IN (SELECT email FROM project.dataset.hr_admins));Les managers voient les métriques de leurs subordonnés directs. Les admins RH voient tout le monde. La policy référence des tables de correspondance maintenues par les RH, pas des filtres statiques mis à jour par les ingénieurs.
Row-level et column-level security se combinent proprement. Un analyste commercial pourrait ne voir que les lignes EMEA (row-level) sans accès à la colonne de marge (column-level). Les deux policies s’appliquent de manière indépendante.
Le dynamic data masking montre la structure sans exposer les valeurs
Parfois, les analystes ont besoin de travailler avec la structure de données sensibles sans voir les valeurs réelles. Ils construisent des requêtes, testent des jointures ou valident la qualité des données sur des exemples synthétiques.
Le dynamic data masking étend la sécurité au niveau des colonnes. Au lieu de bloquer entièrement l’accès, il affiche des valeurs masquées :
-- Créer une data policy avec masquageCREATE DATA POLICY pii_maskON project.dataset.customersCOLUMN emailUSING SHA256; -- Affiche un hash au lieu de l'email réelAccordez le rôle maskedReader aux analystes qui doivent voir les données masquées :
gcloud bigquery data-policies add-iam-policy-binding \ --data-policy=pii_mask \ --location=us \ --member="group:data-analysts@yourdomain.com" \ --role="roles/bigquery.maskedReader"Les analystes voient maintenant a7f3d2e1b4c5... au lieu de user@example.com. Ils peuvent écrire des requêtes, tester des jointures sur les valeurs hashées et vérifier les décomptes de lignes. Quand ils ont besoin des valeurs réelles pour un cas d’usage précis, ils demandent un accès élevé via votre workflow de validation.
Les options de masquage incluent :
SHA256produit un hash déterministe (même entrée = même sortie, utile pour les jointures)DEFAULT_MASKING_VALUErenvoie des valeurs par défaut adaptées au type (chaîne vide, 0, null)NULLIFYrenvoie toujours null
Attribuez le rôle masked reader au niveau de la data policy, pas du projet. Un accès au niveau projet donne un accès masqué à toutes les colonnes masquées du projet, ce qui dépasse probablement les besoins de n’importe quel analyste.
Les raccourcis de sécurité courants à éliminer
Ces patterns apparaissent dans presque tous les audits. Chacun semble raisonnable isolément, mais leur accumulation crée un risque réel.
Les clés de service accounts dans les dépôts
La voie « facile » pour authentifier la CI/CD. La voie sécurisée : Workload Identity Federation. GitHub Actions, GitLab CI et la plupart des plateformes CI supportent l’authentification OIDC, qui échange leurs tokens contre des credentials GCP sans stocker de clés.
# GitHub Actions avec Workload Identity Federation- uses: google-github-actions/auth@v2 with: workload_identity_provider: 'projects/123456/locations/global/workloadIdentityPools/github/providers/github' service_account: 'wlif-github-actions@project.iam.gserviceaccount.com'BigQuery Data Viewer sans Job User
Une confusion fréquente. roles/bigquery.dataViewer permet de voir les métadonnées et les données des tables. Mais exécuter une requête nécessite roles/bigquery.jobUser pour créer des jobs. Les utilisateurs qui n’ont que Data Viewer peuvent parcourir les tables dans la Console mais ne peuvent pas réellement les requêter.
Accordez toujours les deux, ou utilisez roles/bigquery.user qui inclut la création de jobs et la possibilité de créer des datasets pour un usage personnel.
Les policy tags au niveau projet
Accorder roles/datacatalog.categoryFineGrainedReader au niveau projet donne accès à tous les policy tags de ce projet. Si vous ajoutez plus tard une nouvelle catégorie pour des données plus sensibles, toute personne ayant un accès au niveau projet pourra automatiquement la voir.
Accordez l’accès au niveau du policy tag pour respecter le principe du moindre privilège.
Le service account par défaut de Compute Engine
Le service account par défaut (PROJECT_NUMBER-compute@developer.gserviceaccount.com) obtient le rôle Editor sur le projet par défaut. Ce rôle ne peut pas être réduit sans casser certaines fonctionnalités GCP. Ne l’utilisez pas pour vos workloads.
Créez des service accounts dédiés pour chaque workload Compute Engine. Si des systèmes legacy dépendent du compte par défaut, planifiez une migration.
Les signaux de monitoring détectent la dérive des permissions
La dette IAM se réaccumule. Quelqu’un accorde le rôle Editor « temporairement » et oublie de le retirer. Un nouveau workload réutilise un service account existant parce que c’était plus rapide. Des revues trimestrielles permettent de prévenir la dérive.
IAM Recommender analyse l’utilisation réelle et identifie les principals sur-privilégiés :
gcloud recommender recommendations list \ --project=YOUR_PROJECT_ID \ --location=global \ --recommender=google.iam.policy.RecommenderIl pourrait vous indiquer qu’un service account avec le rôle Data Editor n’a fait que lire des données pendant 90 jours. Vous pouvez le réduire en toute sécurité à Data Viewer.
INFORMATION_SCHEMA.JOBS montre qui exécute quoi dans BigQuery :
SELECT user_email, DATE(creation_time) AS query_date, COUNT(*) AS query_count, SUM(total_bytes_billed) / POW(10,12) AS tb_billedFROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECTWHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)GROUP BY 1, 2ORDER BY tb_billed DESCDes service accounts ou des utilisateurs inattendus apparaissant dans cette liste méritent une investigation.
Les logs d’audit capturent tous les changements IAM. Configurez un log sink vers BigQuery et requêtez les attributions de permissions récentes :
SELECT timestamp, protoPayload.authenticationInfo.principalEmail AS grantor, protoPayload.serviceData.policyDelta.bindingDeltasFROM `your_audit_dataset.cloudaudit_googleapis_com_activity`WHERE protoPayload.methodName = 'SetIamPolicy' AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)ORDER BY timestamp DESCLes équipes qui surveillent ces signaux obtiennent généralement 30 à 40 % de réduction de coûts dès leur premier trimestre d’optimisation ciblée, principalement en identifiant et supprimant les accès inutiles qui permettaient des requêtes incontrôlées.
La suite
Nettoyer la dette IAM semble fastidieux jusqu’à ce que vous viviez un incident qu’une IAM propre aurait empêché : une clé de service account qui fuite, une requête qui scanne des pétaoctets parce que quelqu’un avait un accès plus large que nécessaire, un audit incapable de déterminer qui a accédé à des données sensibles.
Les requêtes d’audit de ce guide font ressortir les éléments à plus haut risque : les rôles Editor, les service accounts partagés et les clés de service accounts. À partir de là, migrez un workload à la fois vers des service accounts dédiés avec des permissions minimales, et ajoutez des policy tags pour les colonnes sensibles.
L’objectif est de savoir qui peut accéder à quoi, et de s’assurer que la réponse est intentionnelle plutôt qu’un accident accumulé.