Workload Identity est le mécanisme recommandé (et forcé en Autopilot) pour permettre à un pod d’appeler les APIs Google Cloud (Cloud Storage, Firestore, Pub/Sub…) sans exporter de clé JSON de service account.
Le problème qu’il résout
Par défaut, un pod hérite des permissions du compte de service du nœud sur lequel il tourne — souvent bien plus large que ce dont il a réellement besoin, et partagé par tous les pods du nœud. C’est l’équivalent GCP du problème des rôles IAM attachés à une instance EC2 entière sur AWS.
| Approche | Portée | Risque |
|---|---|---|
| Compte de service du nœud (legacy) | Tous les pods du nœud | Sur-permissionné, un pod compromis hérite de tout |
| Clé JSON exportée | Le pod qui la monte | Clé statique longue durée, fuite possible (secret en clair) |
| Workload Identity | Le pod, via son propre ServiceAccount Kubernetes | Scoping fin, credentials temporaires, pas de secret à gérer |
Mécanisme
- Activer Workload Identity sur le cluster (par défaut en Autopilot).
- Créer un ServiceAccount Kubernetes (KSA) dans le namespace applicatif.
- Lier le KSA au Service Account IAM (GSA) via une annotation + le binding IAM
roles/iam.workloadIdentityUser. - Le pod qui utilise ce KSA obtient automatiquement les permissions du GSA — sans clé, sans secret monté.
Exemple concret
Scénario : une app invoice-reader, dans le namespace billing, doit lire des fichiers depuis le bucket Cloud Storage invoices-prod — et uniquement ça.
PROJECT_IDci-dessous désigne l’identifiant du projet (ex.mon-projet-a1b2c3) — à ne pas confondre avec le nom d’affichage du projet (“Facturation Prod”) ni avec son Project Number, purement numérique. C’est le seul des trois qui est utilisable dans un identifiant technique (formata-z0-9-uniquement), et le seul utilisé dans les commandes ci-dessous. Récupérable viagcloud config get-value project.
1. Activer Workload Identity sur le cluster (si pas déjà fait à la création)
gcloud container clusters update mon-cluster \
--region europe-west1 \
--workload-pool=PROJECT_ID.svc.id.googLe workload pool (PROJECT_ID.svc.id.goog) est l’espace d’identités fédérées qui rend chaque KSA du cluster reconnaissable par IAM. IAM ne connaît pas nativement les ServiceAccounts Kubernetes — ce paramètre indique à GKE de générer, pour les pods de ce cluster, de vrais jetons d’identité vérifiables (OIDC) rattachés à ce pool. Sans lui, la chaîne KSA → token → IAM → GSA n’a rien à quoi s’accrocher : PROJECT_ID.svc.id.goog[namespace/ksa] (utilisé à l’étape 4) ne désignerait aucune identité existante.
2. Créer le GSA et lui donner le droit minimal
gcloud iam service-accounts create invoice-reader-gsa \
--project=PROJECT_ID \
--display-name="Lecture bucket invoices-prod"
gsutil iam ch \
serviceAccount:invoice-reader-gsa@PROJECT_ID.iam.gserviceaccount.com:roles/storage.objectViewer \
gs://invoices-prod- Commande 1 (
gcloud iam service-accounts create) : crée le GSA — une identité vide, sans aucune permission. Son nom unique devient automatiquementinvoice-reader-gsa@PROJECT_ID.iam.gserviceaccount.com(ça ressemble à un email, ce n’est qu’un identifiant). - Commande 2 (
gsutil iam ch) : donne un droit à cette identité, sur le modèle QUI : QUOI : OÙ — ici “invoice-reader-gsa” peut “roles/storage.objectViewer” (lire) sur “gs://invoices-prod” (et seulement ce bucket, pas tout le projet — c’est legs://invoices-proden fin de commande qui limite la portée).iam chajoute ce droit sans toucher aux autres (à l’inverse deiam setqui écraserait tout — à éviter).
Le rôle est accordé au niveau du bucket, pas du projet — le GSA ne peut rien lire ailleurs.
3. Créer le KSA, dans le namespace applicatif
apiVersion: v1
kind: ServiceAccount
metadata:
name: invoice-reader-ksa
namespace: billing
annotations:
iam.gke.io/gcp-service-account: invoice-reader-gsa@PROJECT_ID.iam.gserviceaccount.comL’annotation ne suffit pas à elle seule à autoriser quoi que ce soit — c’est un indice pour le SDK/metadata server côté cluster. L’autorisation réelle vient de l’étape suivante.
4. Lier les deux ServiceAccounts (le binding qui compte vraiment)
gcloud iam service-accounts add-iam-policy-binding \
invoice-reader-gsa@PROJECT_ID.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:PROJECT_ID.svc.id.goog[billing/invoice-reader-ksa]"- La commande (
add-iam-policy-binding) : ajoute un droit sur le GSA lui-même (pas sur un bucket cette fois) — le rôleroles/iam.workloadIdentityUsersignifie “a le droit d’endosser ce GSA”, rien de plus (pas encore un accès au bucket, ça a été fait à l’étape 2). - Le
--member: au lieu d’un compte GCP classique, il désigne un KSA précis via le workload pool — formatPROJECT_ID.svc.id.goog[NAMESPACE/NOM_DU_KSA], ici “le KSAinvoice-reader-ksadu namespacebilling”. Résultat : seul ce KSA précis (et aucun autre, même dans le même namespace) peut endosserinvoice-reader-gsa.
C’est ce binding, pas l’annotation du KSA (étape 3), qui autorise concrètement le lien — l’annotation n’est qu’un indice de configuration côté cluster.
5. Utiliser le KSA dans le pod
apiVersion: v1
kind: Pod
metadata:
name: invoice-reader
namespace: billing
spec:
serviceAccountName: invoice-reader-ksa
containers:
- name: app
image: europe-west1-docker.pkg.dev/PROJECT_ID/apps/invoice-reader:latestAucune clé, aucun secret monté : le pod appelle l’API Cloud Storage avec le SDK habituel (google-cloud-storage), et obtient automatiquement un token du GSA via le metadata server du nœud.
6. Vérifier depuis l’intérieur du pod
kubectl exec -n billing invoice-reader -- \
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"
# → invoice-reader-gsa@PROJECT_ID.iam.gserviceaccount.comkubectl exec -n billing invoice-reader --: exécute la commande à l’intérieur du pod, comme unsshvers le conteneur.curlversmetadata.google.internal: c’est l’appel que le SDK Google fait automatiquement en coulisses pour obtenir un token — un serveur spécial accessible uniquement depuis l’intérieur d’une VM/pod GCP, qui distribue les identités sans qu’aucune clé ne soit stockée. Le headerMetadata-Flavor: Googleest obligatoire (sécurité anti-SSRF)..../service-accounts/default/email: demande quelle identité le pod utilise actuellement. Si Workload Identity fonctionne, la réponse est l’email du GSA (invoice-reader-gsa@...) et non celui, plus large, du compte de service du nœud.
Si la réponse est le compte de service du nœud plutôt que celui du GSA attendu, l’annotation du KSA ou le binding IAM (étape 3 ou 4) est manquant ou mal orthographié — l’erreur la plus fréquente en pratique.
Points de vigilance
- Un GSA peut être lié à plusieurs KSA (plusieurs namespaces, plusieurs clusters) — utile pour un service partagé, mais à documenter pour ne pas perdre la trace de qui peut endosser quoi.
- Propagation IAM : un nouveau binding peut prendre jusqu’à quelques minutes avant d’être effectif — un
403juste après l’étape 4 n’est pas forcément une erreur de configuration. - Least privilege par KSA : préférer un GSA dédié par application (comme ici) plutôt qu’un GSA générique partagé par tout le namespace — sinon Workload Identity ne fait que déplacer le problème de sur-permission du nœud vers le namespace.
En relation avec
- GKE — Vue d’ensemble — hub GKE
- Gouvernance et gestion des identités GCP — IAM et Workload Identity Federation au sens large (au-delà de GKE)
- Autopilot vs Standard — Workload Identity est non désactivable en Autopilot