Credentials à Courte Durée de Vie et Workload Identity Federation dans les Pipelines CI/CD

Introduction

Si vous auditez les coffres-forts de secrets de la plupart des plateformes CI/CD aujourd’hui, vous trouverez un cimetière de credentials à longue durée de vie : des clés d’accès AWS créées il y a des années, des clés JSON de comptes de service GCP partagées entre des dizaines de pipelines, des GitHub Personal Access Tokens avec des portées larges, et des mots de passe de bases de données qui n’ont jamais été renouvelés. Ces secrets statiques constituent le vecteur d’attaque le plus courant dans les compromissions de pipelines CI/CD.

La raison est simple. Un credential à longue durée de vie est une clé passe-partout. Une fois qu’un attaquant l’obtient — via un log fuité, une dépendance compromise, un coffre-fort de secrets mal configuré, ou une attaque supply chain sur la plateforme CI elle-même — il dispose d’un accès persistant, souvent sur-privilégié, à l’infrastructure de production. Il n’y a pas de compte à rebours d’expiration. Il n’y a pas de révocation automatique. L’attaquant peut utiliser ce credential depuis n’importe quelle IP, n’importe quel contexte, aussi longtemps qu’il faut à l’équipe de défense pour s’en apercevoir.

La workload identity federation change entièrement la donne. Au lieu d’injecter des secrets statiques dans les exécutions de pipelines, la plateforme CI elle-même devient un fournisseur d’identité. Chaque exécution de pipeline reçoit un token à courte durée de vie, signé cryptographiquement, qui prouve ce qui s’exécute (quel dépôt, quelle branche, quel workflow, quel environnement). Les fournisseurs cloud valident ce token et émettent des credentials temporaires limités exactement aux permissions nécessaires — des credentials qui expirent en minutes, pas en mois.

Ce guide parcourt le problème en détail, explique comment fonctionne la workload identity federation au niveau protocolaire, fournit des exemples complets et fonctionnels pour GitHub Actions et GitLab CI avec AWS, GCP et Azure, couvre les patterns avancés, et inclut un guide de migration pratique pour les équipes prêtes à éliminer leurs secrets à longue durée de vie.

Le problème des credentials à longue durée de vie

Avant de plonger dans la solution, il est utile de comprendre exactement pourquoi les credentials à longue durée de vie sont si dangereux dans les contextes CI/CD spécifiquement — et pas seulement en général.

Pas d’expiration ni de rotation automatique

Une clé d’accès AWS IAM, une fois créée, est valide indéfiniment sauf révocation explicite. Une clé JSON de compte de service GCP n’a pas de date d’expiration. Un GitHub PAT peut être configuré pour ne jamais expirer. En pratique, la plupart des équipes créent ces credentials une fois lors de la configuration initiale et n’y touchent plus jamais. L’âge médian d’un secret CI/CD dans la plupart des organisations se mesure en années.

Cela signifie que même si un credential a été fuité il y a six mois, il est toujours valide aujourd’hui. Les attaquants le savent et scannent régulièrement les dépôts publics, les images Docker et les logs CI à la recherche de credentials qui ont pu être exposés à un moment donné de l’histoire.

Rayon d’explosion étendu

Les credentials CI/CD tendent à être sur-privilégiés car ils doivent effectuer des tâches diverses : construire des conteneurs, pousser vers des registres, déployer de l’infrastructure, exécuter des migrations de bases de données, invalider des caches. Plutôt que de créer des credentials à portée restreinte pour chaque tâche, les équipes créent généralement un credential puissant et le réutilisent partout. Une seule clé fuitée peut donner accès simultanément aux bases de données de production, à l’infrastructure cloud et aux pipelines de déploiement.

Difficile à auditer

Quand la même clé de compte de service est utilisée dans 50 dépôts, 200 pipelines et trois environnements, il devient quasi impossible de répondre aux questions de sécurité de base :

  • Quel pipeline a effectué cet appel API vers la production à 3h du matin ?
  • Ce credential a-t-il été utilisé depuis un runner CI autorisé ou depuis la machine d’un attaquant ?
  • Quels dépôts dépendent encore de cette clé si nous devons la renouveler ?

Les credentials à longue durée de vie ne fournissent aucune information contextuelle sur qui ou quoi les utilise. Chaque utilisation apparaît de manière identique dans les logs d’audit.

Stockés dans les coffres-forts de secrets des plateformes CI

Les plateformes CI comme GitHub Actions, GitLab CI et Jenkins stockent les secrets dans leurs propres coffres-forts. Ce sont des cibles de grande valeur. Une seule compromission du coffre-fort de secrets de la plateforme CI expose tous les credentials de tous les projets. La compromission de CircleCI en janvier 2023 en est un exemple type : les attaquants ont compromis les systèmes internes de CircleCI et exfiltré les secrets des clients, forçant chaque client CircleCI à renouveler chaque secret stocké dans la plateforme.

Compromissions réelles

Le schéma se répète dans toute l’industrie :

  • Codecov (2021) : Des attaquants ont modifié le Bash Uploader de Codecov pour exfiltrer les variables d’environnement — y compris les secrets CI/CD — des pipelines de milliers de clients. Les credentials à longue durée de vie stockés comme variables d’environnement ont été envoyés vers des serveurs contrôlés par les attaquants.
  • CircleCI (2023) : Un ordinateur portable d’employé compromis a conduit à l’exfiltration des secrets clients depuis le stockage de secrets de CircleCI. Chaque client a été invité à renouveler immédiatement tous ses secrets.
  • Travis CI (2021) : Une vulnérabilité a exposé les secrets des builds de dépôts publics, y compris des clés AWS, des tokens GitHub et des credentials Docker Hub.
  • Uber (2022) : Un attaquant a accédé aux systèmes internes via un pipeline CI/CD compromis, en exploitant des credentials codés en dur trouvés dans des scripts PowerShell.

Dans chaque cas, la cause racine était la même : des credentials à longue durée de vie stockés dans les environnements CI fournissaient un accès persistant et sur-privilégié que les attaquants pouvaient exploiter longtemps après la compromission initiale.

Comment fonctionne la Workload Identity Federation

La workload identity federation remplace les credentials statiques par un flux d’authentification dynamique, basé sur des tokens, construit sur OpenID Connect (OIDC). Voici comment cela fonctionne au niveau protocolaire.

Le flux OIDC

Le flux d’authentification implique trois parties : la plateforme CI (fournisseur d’identité), le fournisseur cloud (partie de confiance) et l’exécution du pipeline (workload).

  1. Émission du token : Lorsqu’un job de pipeline démarre, la plateforme CI génère un JWT (JSON Web Token) signé pour cette exécution spécifique. Ce token contient des claims sur le workload : quel dépôt l’a déclenché, quelle branche, quel workflow, quel acteur et l’environnement.
  2. Échange du token : Le pipeline présente ce JWT au Security Token Service (STS) du fournisseur cloud et demande des credentials temporaires.
  3. Validation : Le fournisseur cloud récupère le document de découverte OIDC et les clés de signature de la plateforme CI. Il valide la signature du JWT, vérifie que le token n’a pas expiré et confirme que les claims correspondent à la politique de confiance configurée.
  4. Émission des credentials : Si la validation réussit, le fournisseur cloud émet des credentials à courte durée de vie (généralement valides pour 1 heure ou moins) limités au rôle IAM ou au compte de service configuré dans la politique de confiance.

La relation de confiance

Le fondement de la workload identity federation est une relation de confiance entre le système IAM du fournisseur cloud et le fournisseur OIDC de la plateforme CI. Elle est configurée une seule fois :

  • AWS : Une ressource IAM OIDC Identity Provider qui fait confiance à token.actions.githubusercontent.com (pour GitHub Actions) ou gitlab.com (pour GitLab).
  • GCP : Un Workload Identity Pool et un Provider configurés pour faire confiance à l’émetteur OIDC de la plateforme CI.
  • Azure : Un Federated Identity Credential sur un App Registration ou une Managed Identity.

Scoping basé sur les claims

La véritable puissance sécuritaire de la fédération OIDC provient du scoping basé sur les claims. Le JWT émis par la plateforme CI contient des claims contextuels riches. Vous configurez le fournisseur cloud pour n’accepter que les tokens dont les claims correspondent à des conditions spécifiques :

  • Dépôt : N’accepter que les tokens de my-org/my-repo
  • Branche : N’accepter que les tokens de la branche main
  • Environnement : N’accepter que les tokens de l’environnement production
  • Workflow : N’accepter que les tokens d’un fichier workflow spécifique

Cela signifie que même si un attaquant compromet un dépôt différent dans la même organisation GitHub, il ne peut pas obtenir de credentials limités au rôle de déploiement en production — les claims ne correspondront pas.

Le flux visualisé

┌─────────────┐     1. Request JWT      ┌──────────────────┐
│  CI Runner   │ ──────────────────────▶ │  CI OIDC Provider │
│  (Pipeline)  │ ◀────────────────────── │  (GitHub/GitLab)  │
└─────┬───────┘     2. Signed JWT        └──────────────────┘
      │                                          │
      │  3. Present JWT +                        │
      │     Request credentials                  │
      ▼                                          │
┌─────────────┐     4. Validate JWT      ┌──────┴───────────┐
│  Cloud IAM  │ ──────────────────────▶  │  OIDC Discovery   │
│  (STS)      │     (fetch public keys)  │  + JWKS Endpoint  │
└─────┬───────┘                          └──────────────────┘
      │
      │  5. Issue short-lived
      │     credentials (1hr)
      ▼
┌─────────────┐
│  Cloud APIs │
│  (S3, GCS,  │
│   etc.)     │
└─────────────┘

GitHub Actions : Fédération OIDC

GitHub Actions dispose d’un support OIDC natif. Chaque exécution de workflow peut demander un JWT signé au fournisseur OIDC de GitHub à l’adresse token.actions.githubusercontent.com. Voici comment configurer la fédération avec chaque fournisseur cloud majeur.

AWS : IAM OIDC Provider + Rôle IAM

Commencez par créer le fournisseur OIDC et le rôle IAM dans AWS. Voici la configuration Terraform :

# Terraform: AWS OIDC Provider for GitHub Actions
resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

# IAM Role that GitHub Actions can assume
resource "aws_iam_role" "github_actions_deploy" {
  name = "github-actions-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:ref:refs/heads/main"
          }
        }
      }
    ]
  })
}

# Attach permissions to the role
resource "aws_iam_role_policy_attachment" "deploy_policy" {
  role       = aws_iam_role.github_actions_deploy.name
  policy_arn = "arn:aws:iam::policy/my-deploy-policy"
}

Ensuite, utilisez le rôle dans votre workflow GitHub Actions :

# .github/workflows/deploy.yml
name: Deploy to AWS
on:
  push:
    branches: [main]

permissions:
  id-token: write   # Required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: us-east-1
          role-session-name: github-actions-deploy-${{ github.run_id }}

      - name: Deploy
        run: |
          aws s3 sync ./dist s3://my-bucket/
          aws cloudfront create-invalidation --distribution-id E12345 --paths "/*"

GCP : Workload Identity Pool + Provider

GCP utilise la Workload Identity Federation avec des pools et des providers. Voici la configuration avec la CLI gcloud :

# Create the Workload Identity Pool
gcloud iam workload-identity-pools create "github-actions-pool" \
  --project="my-project" \
  --location="global" \
  --display-name="GitHub Actions Pool"

# Create the OIDC Provider
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
  --project="my-project" \
  --location="global" \
  --workload-identity-pool="github-actions-pool" \
  --display-name="GitHub OIDC" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.ref=assertion.ref" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-condition="assertion.repository_owner=='my-org'"

# Grant the pool access to a service account
gcloud iam service-accounts add-iam-policy-binding \
  deploy-sa@my-project.iam.gserviceaccount.com \
  --project="my-project" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository/my-org/my-repo"

Le workflow GitHub Actions pour GCP :

# .github/workflows/deploy-gcp.yml
name: Deploy to GCP
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider"
          service_account: "deploy-sa@my-project.iam.gserviceaccount.com"

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy my-service \
            --image gcr.io/my-project/my-app:${{ github.sha }} \
            --region us-central1

Azure : Federated Credentials sur App Registration

Azure prend en charge les federated identity credentials sur les App Registrations et les Managed Identities. Créez la fédération avec Azure CLI :

# Create an App Registration
az ad app create --display-name "github-actions-deploy"

# Create a federated credential
az ad app federated-credential create \
  --id <APP_OBJECT_ID> \
  --parameters '{
    "name": "github-actions-main",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:my-org/my-repo:ref:refs/heads/main",
    "audiences": ["api://AzureADTokenExchange"],
    "description": "GitHub Actions deploy from main branch"
  }'

# Create a service principal and assign roles
az ad sp create --id <APP_CLIENT_ID>
az role assignment create \
  --assignee <APP_CLIENT_ID> \
  --role "Contributor" \
  --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/my-rg

Le workflow GitHub Actions pour Azure :

# .github/workflows/deploy-azure.yml
name: Deploy to Azure
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to Azure Web App
        uses: azure/webapps-deploy@v3
        with:
          app-name: my-web-app
          package: ./dist

Remarque : Avec Azure OIDC, vous stockez toujours le client ID, le tenant ID et le subscription ID comme secrets — mais ce sont des identifiants non sensibles, pas des credentials d’authentification. Aucun client secret n’est nécessaire.

Conditions sur les claims : restreindre l’accès

Le claim subject dans les tokens OIDC de GitHub Actions suit un format prévisible. Utilisez ces patterns dans vos politiques de confiance :

  • repo:my-org/my-repo:ref:refs/heads/main — Uniquement la branche main
  • repo:my-org/my-repo:environment:production — Uniquement l’environnement de production
  • repo:my-org/my-repo:pull_request — Uniquement les workflows de pull request
  • repo:my-org/my-repo:ref:refs/tags/v* — Uniquement les tags de version (utilisez StringLike dans AWS)

Utilisez toujours la condition de claim la plus restrictive possible. Évitez les wildcards comme repo:my-org/* sauf si vous avez véritablement besoin d’un accès à l’échelle de l’organisation.

GitLab CI : Fédération OIDC

GitLab CI a introduit le support OIDC natif avec le mot-clé id_tokens. Le flux est similaire à GitHub Actions mais avec quelques différences dans la structure des claims et la configuration.

Demander des tokens OIDC dans GitLab CI

Dans GitLab CI, vous déclarez les tokens OIDC dans la configuration de votre job en utilisant le mot-clé id_tokens :

# .gitlab-ci.yml
deploy_to_aws:
  stage: deploy
  image: amazon/aws-cli:latest
  id_tokens:
    AWS_OIDC_TOKEN:
      aud: https://sts.amazonaws.com
  script:
    - |
      CREDENTIALS=$(aws sts assume-role-with-web-identity \
        --role-arn arn:aws:iam::123456789012:role/gitlab-deploy \
        --role-session-name "gitlab-ci-${CI_PIPELINE_ID}" \
        --web-identity-token "${AWS_OIDC_TOKEN}" \
        --duration-seconds 3600 \
        --query 'Credentials')
      export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.AccessKeyId')
      export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.SecretAccessKey')
      export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.SessionToken')
      aws s3 sync ./dist s3://my-bucket/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Configuration AWS pour GitLab

Le fournisseur IAM OIDC pour GitLab utilise une URL d’émetteur différente :

# Terraform: AWS OIDC Provider for GitLab
resource "aws_iam_openid_connect_provider" "gitlab" {
  url             = "https://gitlab.com"
  client_id_list  = ["https://sts.amazonaws.com"]
  thumbprint_list = ["b3dd7606d2b5a8b4a13771dbecc9ee1cecafa38a"]
}

resource "aws_iam_role" "gitlab_deploy" {
  name = "gitlab-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.gitlab.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "gitlab.com:aud" = "https://sts.amazonaws.com"
          }
          StringLike = {
            "gitlab.com:sub" = "project_path:my-group/my-project:ref_type:branch:ref:main"
          }
        }
      }
    ]
  })
}

GCP Workload Identity Federation pour GitLab

# Create the OIDC Provider for GitLab
gcloud iam workload-identity-pools providers create-oidc "gitlab-provider" \
  --project="my-project" \
  --location="global" \
  --workload-identity-pool="cicd-pool" \
  --display-name="GitLab OIDC" \
  --attribute-mapping="google.subject=assertion.sub,attribute.project_path=assertion.project_path,attribute.ref=assertion.ref" \
  --issuer-uri="https://gitlab.com" \
  --attribute-condition="assertion.namespace_path=='my-group'"

Le job GitLab CI correspondant pour GCP :

# .gitlab-ci.yml
deploy_to_gcp:
  stage: deploy
  image: google/cloud-sdk:slim
  id_tokens:
    GCP_OIDC_TOKEN:
      aud: https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/cicd-pool/providers/gitlab-provider
  script:
    - |
      echo "${GCP_OIDC_TOKEN}" > /tmp/oidc_token.txt
      gcloud iam workload-identity-pools create-cred-config \
        "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/cicd-pool/providers/gitlab-provider" \
        --service-account="deploy-sa@my-project.iam.gserviceaccount.com" \
        --output-file=/tmp/cred_config.json \
        --credential-source-file=/tmp/oidc_token.txt
      gcloud auth login --cred-file=/tmp/cred_config.json
      gcloud run deploy my-service --image gcr.io/my-project/my-app:${CI_COMMIT_SHA} --region us-central1
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Différences de filtrage des claims par rapport à GitHub Actions

Les tokens OIDC de GitLab utilisent un format de claim subject différent de celui de GitHub Actions :

  • GitLab : project_path:my-group/my-project:ref_type:branch:ref:main
  • GitHub : repo:my-org/my-repo:ref:refs/heads/main

GitLab inclut également des claims supplémentaires utilisables pour le filtrage :

  • namespace_id et namespace_path — Le groupe ou l’espace de noms utilisateur
  • project_id et project_path — Le projet spécifique
  • pipeline_source — Comment le pipeline a été déclenché (push, merge_request, schedule, etc.)
  • environment — L’environnement de déploiement, s’il est défini
  • ref_protected — Si la ref est une branche protégée

Le claim ref_protected est particulièrement utile : vous pouvez configurer des politiques de confiance qui n’acceptent que les tokens provenant de branches protégées, ajoutant une couche de sécurité supplémentaire.

Patterns avancés

Chaînage OIDC : CI vers Vault vers Cloud

HashiCorp Vault peut agir comme un broker d’identité intermédiaire entre votre plateforme CI et les fournisseurs cloud. C’est utile lorsque vous avez besoin d’une gestion centralisée des secrets, de credentials dynamiques pour les bases de données ou d’autres services non-cloud, ou d’une piste d’audit unifiée entre toutes les plateformes CI.

# Configure Vault JWT auth backend for GitHub Actions
vault auth enable jwt

vault write auth/jwt/config \
  oidc_discovery_url="https://token.actions.githubusercontent.com" \
  bound_issuer="https://token.actions.githubusercontent.com"

# Create a role that maps GitHub Actions claims to Vault policies
vault write auth/jwt/role/deploy \
  role_type="jwt" \
  bound_audiences="https://vault.mycompany.com" \
  bound_claims_type="glob" \
  bound_claims='{"repository":"my-org/my-repo","ref":"refs/heads/main"}' \
  user_claim="repository" \
  policies="deploy-policy" \
  ttl="15m"

Le workflow GitHub Actions utilisant Vault comme broker :

# .github/workflows/deploy-via-vault.yml
name: Deploy via Vault
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Import Secrets from Vault
        uses: hashicorp/vault-action@v3
        with:
          url: https://vault.mycompany.com
          method: jwt
          role: deploy
          jwtGithubAudience: https://vault.mycompany.com
          secrets: |
            aws/creds/deploy access_key | AWS_ACCESS_KEY_ID ;
            aws/creds/deploy secret_key | AWS_SECRET_ACCESS_KEY ;
            aws/creds/deploy security_token | AWS_SESSION_TOKEN

      - name: Deploy
        run: aws s3 sync ./dist s3://my-bucket/

Identités par environnement

Créez des rôles IAM séparés pour chaque environnement, chacun avec des politiques de confiance progressivement plus strictes :

# Terraform: Per-environment roles
locals {
  environments = {
    dev = {
      branch    = "*"
      condition = "StringLike"
    }
    staging = {
      branch    = "refs/heads/main"
      condition = "StringEquals"
    }
    production = {
      branch    = "refs/heads/main"
      condition = "StringEquals"
    }
  }
}

resource "aws_iam_role" "deploy" {
  for_each = local.environments
  name     = "github-actions-deploy-${each.key}"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          (each.value.condition) = {
            "token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:environment:${each.key}"
          }
        }
      }
    ]
  })
}

Dans GitHub Actions, utilisez les environnements pour limiter automatiquement la portée du token OIDC :

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production  # This changes the OIDC subject claim
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-production
          aws-region: us-east-1

Kubernetes Workload Identity pour les étapes de déploiement

Si votre pipeline CI déploie sur Kubernetes, vous pouvez chaîner les identités : le runner CI s’authentifie auprès du cluster via OIDC, et les pods du cluster utilisent Kubernetes workload identity pour accéder aux ressources cloud :

# GKE Workload Identity: Annotate the Kubernetes service account
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-deploy-sa
  namespace: production
  annotations:
    iam.gke.io/gcp-service-account: app-sa@my-project.iam.gserviceaccount.com

Patterns d’accès cross-account

Pour les organisations avec plusieurs comptes AWS, vous pouvez chaîner les assumptions de rôles. Le runner CI assume un rôle dans un compte « CI » central via OIDC, puis assume des rôles dans les comptes cibles :

# Step 1: Assume the hub role via OIDC
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::HUB_ACCOUNT:role/github-actions-hub
    aws-region: us-east-1

# Step 2: Assume a role in the target account
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::TARGET_ACCOUNT:role/deploy-role
    aws-region: us-east-1
    role-chaining: true

Combiner OIDC avec Terraform

Les déploiements Terraform bénéficient énormément de l’OIDC car ils nécessitent généralement des permissions d’infrastructure larges. Configurez le provider AWS pour utiliser le rôle assumé via OIDC :

# terraform/providers.tf
provider "aws" {
  region = "us-east-1"

  # No credentials configured here — they come from
  # the environment variables set by the OIDC step
  default_tags {
    tags = {
      ManagedBy   = "terraform"
      Repository  = "my-org/my-infra"
      Environment = var.environment
    }
  }
}

# terraform/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "infra/terraform.tfstate"
    region         = "us-east-1"
    # State backend also uses the OIDC credentials
    # No access_key or secret_key needed
  }
}

Considérations de sécurité

La workload identity federation est une amélioration massive par rapport aux credentials à longue durée de vie, mais elle n’est pas infaillible. Voici les risques à connaître et à atténuer.

Politiques de confiance trop larges

L’erreur la plus courante est de configurer des politiques de confiance trop permissives. Exemples de configurations dangereuses :

  • Faire confiance à une organisation entière : repo:my-org/* signifie que n’importe quel dépôt de l’organisation peut assumer le rôle. Un dépôt compromis ou malveillant obtient l’accès à la production.
  • Faire confiance à toutes les branches : repo:my-org/my-repo:* signifie qu’une branche de fonctionnalité avec du code non testé peut accéder aux credentials de production.
  • Restriction d’audience manquante : Sans vérification du claim aud, des tokens destinés à un service pourraient être rejoués contre un autre.

Suivez toujours le principe du moindre privilège : restreignez au dépôt, à la branche et à l’environnement spécifiques nécessaires.

Mauvaise configuration de l’audience du token

Le claim audience (aud) spécifie le destinataire prévu du token. Si votre politique de confiance ne valide pas l’audience, un attaquant qui obtient un token destiné à un autre service pourrait l’utiliser pour assumer votre rôle IAM. Validez toujours le claim audience dans vos politiques de confiance.

Compromission du fournisseur OIDC

Si le fournisseur OIDC de la plateforme CI est compromis (par exemple, un attaquant peut forger des JWT), toutes les relations de confiance construites sur ce fournisseur sont compromises. Les mesures d’atténuation incluent :

  • Surveiller les avis de sécurité de la plateforme CI
  • Utiliser des restrictions de claims supplémentaires au-delà du simple subject
  • Implémenter des restrictions basées sur l’IP lorsque c’est supporté
  • Définir des durées de session courtes (15 minutes au lieu d’1 heure)
  • Activer CloudTrail, GCP Audit Logs ou Azure Activity Logs pour détecter les accès fédérés anormaux

Surveillance et audit

L’accès fédéré doit être surveillé comme tout autre mécanisme d’authentification. Configurez des alertes pour :

  • Les assumptions de rôles depuis des dépôts ou branches inattendus
  • Les patterns d’accès inhabituels (par exemple, un rôle de production assumé à 3h du matin sans déploiement prévu)
  • Les tentatives échouées d’assumption de rôle (peuvent indiquer un attaquant qui sonde les politiques de confiance)
  • Les modifications des configurations de fournisseur OIDC ou des politiques de confiance
# AWS CloudTrail filter for OIDC role assumptions
{
  "eventName": "AssumeRoleWithWebIdentity",
  "requestParameters": {
    "roleArn": "arn:aws:iam::123456789012:role/github-actions-deploy-production"
  }
}

Stratégies de repli

La fédération OIDC dépend de services externes (endpoint OIDC de la plateforme CI, STS cloud). Planifiez les pannes :

  • Maintenez un jeu de credentials à longue durée de vie « break-glass » dans un coffre-fort sécurisé (par exemple, AWS Secrets Manager, 1Password), accessible uniquement aux ingénieurs seniors
  • Documentez la procédure break-glass : quand l’utiliser, qui peut l’autoriser, comment auditer son utilisation
  • Configurez des alertes lorsque les credentials break-glass sont utilisés
  • Renouvelez les credentials break-glass après chaque utilisation

Guide de migration : des credentials à longue durée de vie aux credentials à courte durée de vie

La migration des credentials à longue durée de vie vers la workload identity federation est un changement à forte valeur ajoutée et à faible risque lorsqu’il est effectué de manière systématique. Voici une approche par phases.

Phase 1 : Inventaire

Commencez par cataloguer chaque secret de votre plateforme CI/CD :

# GitHub: List all secrets for a repository
gh secret list --repo my-org/my-repo

# GitHub: List organization secrets
gh secret list --org my-org

# GitLab: List project variables
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.com/api/v4/projects/$PROJECT_ID/variables"

Pour chaque secret, documentez :

  • Ce que c’est (clé AWS, clé GCP, mot de passe de base de données, token API)
  • Ce à quoi il accède (quel compte cloud, quel service)
  • Quels pipelines l’utilisent
  • Quand il a été renouvelé pour la dernière fois
  • S’il peut être remplacé par la fédération OIDC

Phase 2 : Identifier les candidats

Bons candidats pour le remplacement par OIDC :

  • Clés d’accès AWS IAM utilisées en CI/CD
  • Clés JSON de comptes de service GCP
  • Client secrets de service principals Azure
  • Tout credential utilisé pour s’authentifier auprès d’un fournisseur cloud qui supporte OIDC

Non candidats pour OIDC (nécessitent des approches différentes) :

  • Mots de passe de bases de données (utilisez les credentials dynamiques de Vault à la place)
  • Clés API de services tiers (utilisez Vault ou un gestionnaire de secrets)
  • Clés SSH pour les opérations Git (utilisez des deploy keys ou des tokens GitHub App)
  • Mots de passe de registres de conteneurs (utilisez l’authentification native du registre cloud via OIDC)

Phase 3 : Déploiement progressif

  1. Commencez par le non-production : Configurez la fédération OIDC pour un seul pipeline non-production. Validez son fonctionnement fiable pendant une semaine.
  2. Étendez aux autres pipelines non-production : Convertissez les pipelines dev et staging restants. Développez la confiance et la documentation.
  3. Pilote production : Choisissez un pipeline de production avec un bon monitoring. Exécutez OIDC en parallèle du credential existant pendant une semaine (le pipeline utilise OIDC, mais l’ancien credential existe toujours comme solution de repli).
  4. Déploiement complet en production : Convertissez les pipelines de production restants. Conservez les anciens credentials uniquement comme break-glass.

Phase 4 : Décommissionner les anciens credentials

Une fois que l’OIDC fonctionne de manière fiable :

  1. Désactivez, ne supprimez pas : Désactivez d’abord l’ancien credential. Attendez deux semaines pour vous assurer que rien ne casse.
  2. Surveillez l’utilisation : Vérifiez CloudTrail, GCP Audit Logs ou l’équivalent pour toute utilisation de l’ancien credential.
  3. Supprimez : Une fois que vous êtes sûr que le credential n’est plus utilisé, supprimez-le.
  4. Supprimez des secrets CI : Supprimez le secret du coffre-fort de secrets de votre plateforme CI.
# AWS: Deactivate an old access key
aws iam update-access-key \
  --user-name ci-deploy \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive

# After validation period, delete it
aws iam delete-access-key \
  --user-name ci-deploy \
  --access-key-id AKIAIOSFODNN7EXAMPLE

Validation

Confirmez que la migration est complète :

  • Tous les pipelines CI/CD s’authentifient via OIDC (vérifiez les logs de workflow)
  • Aucun credential cloud à longue durée de vie ne reste dans les coffres-forts de secrets CI (à l’exception des credentials break-glass documentés)
  • Les utilisateurs IAM ou comptes de service précédemment utilisés par le CI sont supprimés ou n’ont plus de clés d’accès
  • La surveillance et les alertes sont en place pour l’accès fédéré
  • Les procédures break-glass sont documentées et testées

Conclusion

La workload identity federation est le changement à plus fort impact que la plupart des équipes peuvent apporter à leur posture de sécurité CI/CD. Elle élimine le vecteur d’attaque le plus courant — les credentials à longue durée de vie — et le remplace par un système plus sécurisé par défaut : à courte durée de vie, automatiquement limité en portée, auditable et résistant aux mouvements latéraux.

Le chemin de migration est simple. Commencez par un pipeline et un fournisseur cloud. Configurez la relation de confiance OIDC, mettez à jour le workflow pour utiliser l’authentification fédérée, validez que cela fonctionne, puis passez au pipeline suivant. En quelques semaines, vous pouvez éliminer chaque credential cloud à longue durée de vie de votre plateforme CI/CD.

Les outils sont matures et bien supportés. GitHub Actions, GitLab CI et les trois principaux fournisseurs cloud disposent d’un support de fédération OIDC prêt pour la production. Les actions officielles GitHub Actions (aws-actions/configure-aws-credentials, google-github-actions/auth, azure/login) gèrent automatiquement l’échange de tokens. Les ressources Terraform existent pour la configuration en infrastructure-as-code.

Il n’y a aucune raison de conserver des credentials à longue durée de vie dans vos pipelines CI/CD. Le risque est élevé, le coût de migration est faible et l’amélioration de la sécurité est immédiate. Commencez dès aujourd’hui.