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.

ApprochePortéeRisque
Compte de service du nœud (legacy)Tous les pods du nœudSur-permissionné, un pod compromis hérite de tout
Clé JSON exportéeLe pod qui la monteClé statique longue durée, fuite possible (secret en clair)
Workload IdentityLe pod, via son propre ServiceAccount KubernetesScoping fin, credentials temporaires, pas de secret à gérer

Mécanisme

  1. Activer Workload Identity sur le cluster (par défaut en Autopilot).
  2. Créer un ServiceAccount Kubernetes (KSA) dans le namespace applicatif.
  3. Lier le KSA au Service Account IAM (GSA) via une annotation + le binding IAM roles/iam.workloadIdentityUser.
  4. 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_ID ci-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 (format a-z0-9- uniquement), et le seul utilisé dans les commandes ci-dessous. Récupérable via gcloud 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.goog

Le 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 automatiquement invoice-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 le gs://invoices-prod en fin de commande qui limite la portée). iam ch ajoute ce droit sans toucher aux autres (à l’inverse de iam set qui é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.com

L’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ôle roles/iam.workloadIdentityUser signifie “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 — format PROJECT_ID.svc.id.goog[NAMESPACE/NOM_DU_KSA], ici “le KSA invoice-reader-ksa du namespace billing”. Résultat : seul ce KSA précis (et aucun autre, même dans le même namespace) peut endosser invoice-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:latest

Aucune 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.com
  • kubectl exec -n billing invoice-reader -- : exécute la commande à l’intérieur du pod, comme un ssh vers le conteneur.
  • curl vers metadata.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 header Metadata-Flavor: Google est 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 403 juste 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