Zéro secret, zéro clé : comment le Workload Identity Federation a changé ma CI/CD

Pour être tout à fait honnête : pendant longtemps, la gestion des secrets dans mes CI/CD, c'était un peu le bazar organisé. Pas le chaos total, mais ce "truc" un peu inconfortable qu'on sait bancal et qu'on reporte à plus tard faute de mieux.
Vous voyez certainement de quoi je parle. Ces fichiers JSON qu'on colle en variable d'environnement dans GitLab, ces clés de compte de service qu'on se transmet entre équipes, parfois par mail, parfois sur des services de messageries internes en mode "c'est bon t'as récupéré que je supprime le message ?" ... On sait que ce n'est pas l'idéal. On le fait quand même.
Je ne vous parle même pas des autres atrocités que j'ai pu croiser comme des clés SSH de prod stockées sans aucune sécurisation, mais là c'est l'extrême et ça fait bien longtemps que cette pratique est bannie pour moi, si tant est qu'elle ait déjà existée ...
Malgré tout, on essaie d'apporter à cet ensemble un peu de cadre, de structure, on tente de protéger au maximum toutes ces informations qui peuvent être ultra sensibles, on parle quand même de secrets permettant d'accéder à minima à du code source applicatif qui pourrait être une véritable mine d'or pour des pirates.
Récemment, j'ai dû m'attaquer à un cas concret : autoriser des runners GitLab partagés à l'échelle de l'entreprise à pousser des artefacts dans plusieurs projets GCP distincts, sans créer un monstre de gestion de permissions.
Ce monstre aurait pu ressembler :
À un compte de service GCP avec des droits d'écriture sur tous les projets, parce que c'est plus simple que de gérer un compte par projet. Une seule clé JSON, stockée en variable globale dans GitLab, accessible à tous les pipelines de l'instance.
Chaque projet a son propre compte de service, ce qui part d'une bonne intention, jusqu'au jour où des centaines de comptes existent dans GCP avec des droits jamais revus
Et il ne faut pas oublier un aspect très important dans cette histoire, nous sommes une entreprise certifiée IOS27001, et la sécurité est donc primordiale, il me fallait donc trouver une solution sécurisée, robuste et avec peu de maintenance.
C'est en creusant ce sujet que je suis tombé sur le Workload Identity Federation (WIF), et franchement, ça a changé ma façon de voir les choses.
Ce qu'on faisait avant (et pourquoi on avait tous un peu honte)
La méthode classique, tout le monde la connaît :
Créer un Compte de Service dans GCP.
Générer une clé privée (le fameux JSON).
La coller dans les variables sécurisées du projet GitLab.
Espérer ne pas l'oublier dans un log ou un mail.
Le vrai problème des clés statiques, ce n'est pas juste la sécurité dans l'absolu — c'est que la clé n'expire pas. Elle vit éternellement. Si elle fuite (et ça arrive, les logs de console sont traitres), le problème ne s'arrête pas tout seul. Quelqu'un doit intervenir, souvent en catastrophe et souvent trop tard.
Et dans le cas de runners transverses — des machines partagées qui exécutent les pipelines de toute l'entreprise — on tombait parfois dans un anti-pattern encore plus problématique : donner au runner lui-même des droits d'écriture un peu partout. Un "God Mode" implicite. Si le code d'un projet s'avérait malveillant, ou simplement buggué, rien n'empêchait techniquement d'aller toucher aux ressources d'un autre projet.
Ce n'est pas une faille théorique. C'est le genre de chose qui arrive.
Le principe du WIF
Le Workload Identity Federation repose sur une idée simple, presque évidente une fois qu'on l'a comprise : pourquoi donner un mot de passe à une machine quand on peut lui demander de prouver qui elle est ?
Je pourrais faire l'analogie du livreur :
L'ancienne méthode, c'est de laisser le code de votre digicode à tous vos livreurs habituels.
Ils peuvent le noter, le partager, revenir quand ils veulent.
La méthode WIF, c'est un code à usage unique généré automatiquement à chaque livraison, valable trente minutes, et lié à la commande précise en cours. Le livreur vous appelle, vous vérifiez son identité, et en échange vous générez ce digicode à usage unique.
Ici, c'est pareil et techniquement, ça s'appuie sur le protocole OIDC (OpenID Connect). Le Cloud ne gère plus de mots de passe. Il vérifie des preuves cryptographiques émises par vos outils, et en échange, il délivre des accès temporaires.
Ce qui change vraiment avec les runners partagés
Ce que j'ai trouvé le plus élégant dans cette approche, c'est le changement de paradigme : l'identité ne porte plus sur la machine, elle porte sur le code qui s'exécute.
Concrètement, voici ce qui se passe désormais si un Projet A à besoin de d'envoyer un artéfact que la CI/CD a construite vers un Bucket A dans projet Google Cloud Platform :
Un développeur pousse du code sur le Projet A.
Le runner lance un le job de CI/CD et demande à GitLab un jeton d'identité.
GitLab génère un JWT, qui en substance dit : "cette exécution vient bien du Projet A*, j'en suis l'émetteur et j'en garantis la signature"*.
Le runner présente ce jeton à GCP.
GCP vérifie la signature, constate que c'est bien le Projet A, et délivre un accès temporaire limité au Bucket A.
Si ce même runner tente ensuite d'utiliser un jeton du Projet B pour accéder au Bucket A ? Refus immédiat. L'isolation est structurelle, pas juste organisationnelle.
En pratique
Voici les deux morceaux de configuration qui matérialisent tout ça.
Côté GCP — définir la confiance (une fois pour toutes)
On crée un fournisseur d'identité.
La ligne importante, c'est la condition attribute-mapping : on dit à GCP d'accepter uniquement les jetons correspondant à l'ID du projet GitLab concerné.
SHARED_PROJECT="shared-runners"
WIF_POOL="gitlab-pool"
WIF_PROVIDER="gitlab-provider"
GITLAB_URL="https://mon-instance-gitlab.fr"
gcloud iam workload-identity-pools create $WIF_POOL \
--project=$SHARED_PROJECT \
--location=global \
--display-name="GitLab OIDC Pool"
# Créer le provider
gcloud iam workload-identity-pools providers create-oidc $WIF_PROVIDER \
--project=$SHARED_PROJECT \
--location=global \
--workload-identity-pool=$WIF_POOL \
--issuer-uri="$GITLAB_URL" \
--allowed-audiences="$GITLAB_URL" \
--attribute-mapping="google.subject=assertion.sub,attribute.project_path=assertion.project_path,attribute.ref=assertion.ref,attribute.project_id=assertion.project_id"
Une fois le fournisseur d'identité créé, pour chaque projet GCP il faut créer un compte de service, lui octroyer les droits nécessaires (Artifact registry writer, admin storage, ...) et faire le lien entre le projet gitlab et ce compte de service
GITLAB_PROJECT_ID="1234" # ID numérique GitLab (Project > Settings > General)
GCP_PROJECT="mon-projet-gcp" # projet GCP dédié
WIF_PROJECT_NUMBER="$(gcloud projects describe $SHARED_PROJECT --format='value(projectNumber)')"
SA_EMAIL="artifact-writer@$GCP_PROJECT.iam.gserviceaccount.com" # SA existante ou à créer
gcloud iam service-accounts create artifact-writer \
--project=$GCP_PROJECT \
--display-name="GitLab CI artifact writer"
gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/artifactregistry.writer"
gcloud projects add-iam-policy-binding $GCP_PROJECT \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/storage.objectAdmin"
gcloud iam service-accounts add-iam-policy-binding mon-compte-de-service@mon-projet.iam.gserviceaccount.com \
--project=mon-projet \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${WIF_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${WIF_POOL}/attribute.project_id/${GITLAB_PROJECT_ID}"
Côté GitLab — un pipeline sans aucun secret
Plus de variables secrètes.
On demande le jeton OIDC directement au job, et on l'échange contre un accès temporaire.
C'est l'attribut id_tokens qui s'occupe de ça (documenté ici).
deploy_to_gcp:
image: google/cloud-sdk:slim
tags: [runner-partage]
id_tokens: # <-- Attribut gitlab permettant la génération du token JWT
GITLAB_OIDC_TOKEN:
aud: https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/mon-pool-ci/providers/mon-provider-gitlab
script:
- echo "${GITLAB_OIDC_TOKEN}" > /tmp/oidc_token.txt
- >
echo '{
"type": "external_account",
"audience": "//iam.googleapis.com/...",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": { "file": "/tmp/oidc_token.txt" }
}' > /tmp/gcp_cred.json
- export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp_cred.json
- gcloud auth login --cred-file=/tmp/gcp_cred.json
- gcloud storage cp artefact.zip gs://mon-bucket-projet-a/
Et c'est tout. A partir de ce moment là, toutes les commandes sont possibles coté GCP, si tant est que le compte de service associé ait les droits suffisants.
Ce que ça change au quotidien
Au-delà de la sécurité dans l'absolu, voilà ce que j'ai constaté concrètement :
Fini la rotation des clés. Plus personne ne reçoit un rappel "La clé de service du projet X expire dans n jours". Cette charge disparaît.
Les jetons sont éphémères. Même intercepté, un jeton temporaire a une fenêtre de vie très courte. Le risque résiduel existe mais il est structurellement limité.
On peut affiner par branche ou par tag. On peut configurer GCP pour n'accepter un déploiement que si le pipeline vient de
mainou d'un tag derelease. Le déploiement depuis une branche defeaturedevient impossible par construction.Le pipeline ne stocke rien. Il n'y a plus rien à faire fuiter.
Quelques autres cas d'usage qui valent le détour
Ce qui est intéressant avec OIDC, c'est que la logique est universelle. Quelques exemples concrets au-delà du cas GitLab → GCP :
AWS vers GCP : une application hébergée sur AWS a besoin d'interroger BigQuery. Plutôt que de gérer des clés entre les deux clouds, l'application AWS utilise son rôle IAM pour négocier un accès temporaire chez GCP.
Serveurs on-premise : de vieux serveurs physiques qui doivent envoyer des backups vers le Cloud. On peut fédérer un annuaire type Active Directory ou Okta avec GCP pour gérer ça proprement.
Terraform Cloud : fini les credentials d'admin stockés dans les variables de workspace. Terraform assume temporairement les droits nécessaires via WIF, et c'est tout.
En résumé
Si vos pipelines utilisent encore des clés statiques pour parler à votre Cloud, ce n'est pas un reproche — c'était le standard pendant longtemps, Sachez que des outils existent maintenant pour faire autrement, sans que ce soit particulièrement complexe à mettre en place.
C'est le genre de chantier qu'on repousse souvent parce que "ça marche bien comme ça". Jusqu'au jour où ça ne marche plus très bien.
Autant ne pas attendre ce jour.





