À la fin de ce tutoriel, vous enregistrerez GitHub comme Identity Provider OIDC dans IAM, créerez un rôle dont la trust policy borne le claim sub, et déploierez un site statique vers S3 (option CloudFront) depuis Actions — sans access keys longue durée, en région ca-central-1.

Niveau : Intermédiaire · Temps estimé : 55–70 min · Versions testées : GitHub Actions (OIDC 2026), IAM OIDC provider, aws-actions/configure-aws-credentials@v4, AWS CLI v2, Terraform 1.10+ (optionnel) · Dernière vérification : 2026-09-11 · Region : ca-central-1

Slug : github-actions-oidc-aws · Série : DevOps / AWS · Mot-clé SEO : github actions oidc aws

← Précédent : IAM users, groupes, rôles · → Suivant : Terraform IAM · Hub : Démarrer avec AWS · EN live : GitHub Actions AWS OIDC

Prérequis

  • Compte AWS avec droits pour créer un OIDC provider et des rôles IAM (IAM)
  • Repo GitHub avec Actions activées (my-org/my-repo à remplacer)
  • AWS CLI v2 locale + profil lab ; Terraform 1.10+ optionnel pour la variante IaC
  • Bucket S3 lab (idéalement déjà sécurisé : S3 sécurité, site : S3 site web)
  • Aucune access key GitHub Secrets à coller pour ce lab

Coût estimé : IdP + rôle IAM ≈ 0 $. S3 / invalidations CloudFront = Free Tier ou quelques cents. Surveillez surtout le volume sync et les invalidations répétées.

export AWS_PROFILE=lab
export AWS_DEFAULT_REGION=ca-central-1

Ce que nous allons construire

GitHub Actions (job + environment:production)
  └── JWT OIDC (token.actions.githubusercontent.com)
        └── aws-actions/configure-aws-credentials@v4
              └── sts:AssumeRoleWithWebIdentity
IAM
  ├── OIDC provider (une fois / compte)
  └── Rôle gha-my-repo-deploy
        ├── Trust : aud + sub (main / production)
        └── Permissions : S3 (+ CloudFront)
S3 my-site-bucket  (+ invalidation CloudFront optionnelle)

(Schéma — alt : « GitHub Actions OIDC vers AWS : jeton court, pas d’access keys ».)

Pourquoi OIDC plutôt que des access keys ?

Méthode Secret longue durée dans GitHub ? Risque Lab 2026
IAM user + access key Oui (AWS_ACCESS_KEY_ID / secret) Fuite logs/forks, rotation manuelle À éviter
Clés dans org secrets Oui, partagées Blast radius large Legacy
OIDC + rôle IAM Non : JWT par run Surface minimale, session CloudTrail Préférer

Flux : le job demande un JWT GitHub → configure-aws-credentials appelle AssumeRoleWithWebIdentity → AWS vérifie la signature via l’IdP enregistré → STS rend des credentials ~1 h. Le claim sub encode repo, branche, tag, PR ou environment : c’est le levier du moindre privilège.

Exemples de sub :

repo:my-org/my-repo:ref:refs/heads/main
repo:my-org/my-repo:environment:production
repo:my-org/my-repo:pull_request
repo:my-org/my-repo:ref:refs/tags/v1.2.3

Étape 1 — Créer l’Identity Provider GitHub (une fois / compte)

aws iam create-open-id-connect-provider 
  --url https://token.actions.githubusercontent.com 
  --client-id-list sts.amazonaws.com 
  --thumbprint-list ffffffffffffffffffffffffffffffffffffffff

aws iam list-open-id-connect-providers

Depuis mi-2023, AWS s’appuie sur sa liste de CA de confiance pour GitHub : le thumbprint n’est plus vraiment vérifié ; 40 × f est la valeur documentée acceptée. Les tutos qui recalculent le thumbprint avec openssl restent valides mais inutiles.

Si le provider existe déjà (compte partagé), ne le recréez pas : notez l’ARN arn:aws:iam::<YOUR_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com.

Étape 2 — Trust policy et rôle de déploiement

La trust policy répond « qui peut assume » ; la permissions policy répond « quoi faire ». Un rôle par repo (et idéalement par environment), permissions bornées aux ressources déployées.

trust.json :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GitHubActionsOIDC",
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<YOUR_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
      },
      "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",
            "repo:my-org/my-repo:environment:production"
          ]
        }
      }
    }
  ]
}
aws iam create-role --role-name gha-my-repo-deploy 
  --assume-role-policy-document file://trust.json 
  --max-session-duration 3600

cat > permissions.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::my-site-bucket"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:DeleteObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::my-site-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": ["cloudfront:CreateInvalidation"],
      "Resource": "arn:aws:cloudfront::<YOUR_ACCOUNT_ID>:distribution/E1234567890ABC"
    }
  ]
}
EOF

aws iam put-role-policy 
  --role-name gha-my-repo-deploy 
  --policy-name deploy 
  --policy-document file://permissions.json

Interdits en prod : "sub": "repo:my-org/*" ou absence de condition sub (n’importe quel repo GitHub pourrait assume). Évitez aussi de faire confiance à pull_request pour un rôle qui mute l’infra : une PR (surtout fork) exécute du code non approuvé.

Étape 3 — Workflow YAML (deploy S3)

Deux obligations : permissions.id-token: write (JWT) et l’action configure-aws-credentials avec role-to-assume. Stockez l’ARN du rôle en variable de repo (vars), pas en secret : ce n’est pas sensible.

# .github/workflows/deploy-site.yml
name: Deploy static site

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

concurrency:
  group: deploy-prod
  cancel-in-progress: false

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

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
          aws-region: ca-central-1
          role-session-name: gha-${{ github.run_id }}

      - name: Who am I?
        run: aws sts get-caller-identity

      - name: Build
        run: |
          npm ci
          npm run build

      - name: Sync to S3
        run: >
          aws s3 sync ./dist s3://my-site-bucket
          --delete
          --cache-control "public,max-age=300"

      - name: Invalidate CloudFront
        run: >
          aws cloudfront create-invalidation
          --distribution-id E1234567890ABC
          --paths "/*"

Poussez sur main. get-caller-identity doit montrer assumed-role/gha-my-repo-deploy/gha-<run_id> — visible dans CloudTrail.

GitHub : Environment production (reviewers optionnels) + variable AWS_DEPLOY_ROLE_ARN.

Étape 4 — Terraform du rôle (optionnel)

Si vous versionnez l’IAM avec Terraform (Terraform IAM, state S3 : backend S3) :

variable "github_repo" {
  type    = string
  default = "my-org/my-repo"
}

data "aws_caller_identity" "current" {}

resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["ffffffffffffffffffffffffffffffffffffffff"]
}

data "aws_iam_policy_document" "trust" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.github.arn]
    }

    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values = [
        "repo:${var.github_repo}:ref:refs/heads/main",
        "repo:${var.github_repo}:environment:production",
      ]
    }
  }
}

resource "aws_iam_role" "gha_deploy" {
  name                 = "gha-my-repo-deploy"
  assume_role_policy   = data.aws_iam_policy_document.trust.json
  max_session_duration = 3600
}

output "role_arn" {
  value = aws_iam_role.gha_deploy.arn
}

Attachez une policy least-privilege (S3/CloudFront ou ECR/ECS). Un seul provider OIDC par compte : terraform import s’il existe déjà.

Variante — Conteneur ECR + ECS

Même handshake OIDC pour (ECS Fargate). Élargissez le rôle : ecr:GetAuthorizationToken, push sur un repo ECR, ecs:RegisterTaskDefinition, UpdateService/DescribeServices, iam:PassRole borné aux task / execution roles.

# Extrait deploy-ecs.yml — après configure-aws-credentials@v4 (ca-central-1)
- uses: aws-actions/amazon-ecr-login@v2
  id: ecr
- name: Build and push
  env:
    REGISTRY: ${{ steps.ecr.outputs.registry }}
  run: |
    IMAGE="$REGISTRY/my-api:${GITHUB_SHA::12}"
    docker build -t "$IMAGE" . && docker push "$IMAGE"
    echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
- uses: aws-actions/amazon-ecs-render-task-definition@v1
  id: task
  with:
    task-definition: infra/taskdef.json
    container-name: api
    image: ${{ steps.build.outputs.image }}
- uses: aws-actions/amazon-ecs-deploy-task-definition@v2
  with:
    task-definition: ${{ steps.task.outputs.task-definition }}
    cluster: prod
    service: my-api
    wait-for-service-stability: true

Quand utiliser OIDC (critères de décision)

Choisissez OIDC GitHub → AWS dès que la CI déploie ou lit des ressources cloud. Les access keys longue durée dans GitHub Secrets restent un anti-pattern en 2026 : rotation manuelle, fuite possible via logs ou forks, et blast radius large si une seule clé org-wide circule.

Signaux pour basculer maintenant : workflow qui pousse vers S3/ECR/ECS/Lambda/state Terraform ; compte lab déjà prêt (coût IdP ≈ 0) ; exigence compliance d’éliminer les credentials statiques ; multi-environnements via GitHub Environments + trust sub.

Garder des clés seulement pour un outil legacy sans OIDC, avec date de sortie et alerte CloudTrail CreateAccessKey. Sinon IdP + rôle dès le lab — IAM · Terraform IAM · côté Azure : OIDC.

Durcissement et migration depuis les access keys

  • Rôles séparés staging/prod (trust environment:… + reviewers GitHub).
  • PR : rôle read-only (terraform plan) — jamais le rôle deploy.
  • role-duration-seconds: 900 si le job est court ; permission boundary sur gha-*.
  • Access Analyzer / Config pour repérer les access keys legacy ; alerte CloudTrail CreateAccessKey.

Migration : créer IdP + rôle → basculer le workflow sur role-to-assume → vérifier CloudTrail → désactiver puis supprimer clés/user → purger Secrets GitHub.

Même idée sur GitLab, Azure Pipelines (OIDC Azure), Bitbucket, CircleCI : IdP + trust aud/claim projet + AssumeRoleWithWebIdentity.

Approfondir les étapes HowTo (pièges réels)

IdP : une fois par compte, pas par repo

L’Identity Provider token.actions.githubusercontent.com se crée une fois par compte AWS (ou une fois par org multi-comptes si vous centralisez). Dupliquer l’IdP produit des ARN Federated confus et des erreurs « provider already exists ». Documentez l’ARN dans votre runbook interne à côté du hub Démarrer avec AWS.

Après création, vérifiez dans la console IAM → Identity providers que le thumbprint / certificat est à jour (AWS gère souvent la rotation, mais un IdP très ancien peut nécessiter une mise à jour — consultez la doc AWS du moment).

Trust policy : aud + sub, jamais l’un sans l’autre

Une trust qui ne filtre que aud = sts.amazonaws.com sans condition sur sub autorise tout dépôt GitHub capable d’émettre un JWT vers votre compte. Toujours borner :
repo:ORG/REPO:ref:refs/heads/main pour un déploiement depuis main ;
– ou repo:ORG/REPO:environment:production si le job utilise environment: (le claim change — c’est la cause n°1 des AssumeRole refusés).

Pour un terraform plan sur PR, créez un second rôle read-only avec un sub autorisant pull_request / branches de feature, jamais le rôle deploy prod. Croisez avec Terraform workflow et state backend S3 si le plan lit un state distant.

Workflow : permissions minimales

permissions: id-token: write est obligatoire ; contents: read suffit pour cloner. Réglez role-duration-seconds au plus court (souvent 900–3600). Pour S3 + CloudFront : S3 site web · S3 sécurité. Variante conteneur : ECS Fargate.

Troubleshooting

Erreur Cause / correction
Credentials could not be loaded… Manque permissions: id-token: write (workflow ou job)
Not authorized to perform sts:AssumeRoleWithWebIdentity sub du JWT ≠ trust policy. Souvent : job avec environment:sub = environment:production, pas ref:refs/heads/main
Incorrect token audience Trust exige aud = sts.amazonaws.com ; utiliser @v4
No OpenIDConnect provider found Provider absent sur ce compte, ou ARN Federated typé
AccessDenied sur l’appel AWS Trust OK ; permissions trop étroites — lire CloudTrail
OK en push, KO en workflow_dispatch Autre branche → autre ref ; élargir la condition ou passer par environments

Astuce debug : décoder le JWT OIDC du job (audience sts.amazonaws.com) et comparer le sub au StringLike IAM — sans logger le token complet.

FAQ

L’ARN du rôle doit-il être un secret ?
Non : vars.AWS_DEPLOY_ROLE_ARN suffit. La sécurité est dans la trust policy + OIDC.

Job environment: production en échec alors que main est autorisé ?
Le sub devient repo:…:environment:production. Ajoutez-le (ou retirez l’environment).

Un seul rôle pour toute l’org ?
À éviter. Un rôle par repo limite le blast radius.

OIDC remplace-t-il le SSO humain ?
Non : ici on fédère la CI. Les humains restent sur Identity Center / SSO.

Forks de PR ?
Ne donnez jamais un rôle deploy à un sub trop large « pour la CI contributeurs ».

Faut-il un IdP par région AWS ?
Non. L’Identity Provider IAM est une ressource globale au compte. Les rôles et policies, eux, ciblent des ressources régionales (ca-central-1, etc.).

Puis-je réutiliser le même rôle pour Terraform et pour un sync S3 ?
Possible techniquement, déconseillé. Séparez les permissions (et idéalement les rôles) : un rôle gha-…-terraform borné au state + plan/apply, un rôle gha-…-static borné au bucket site. Moins de blast radius si un workflow est compromis.

Comment auditer qui a assumé le rôle ?
CloudTrail : événements AssumeRoleWithWebIdentity. Filtrez sur le rôle ARN et corrélez le userIdentity.principalId / session name avec le run GitHub Actions (commit SHA, workflow run id). Activez aussi Access Analyzer pour les findings liés aux trust trop larges.

Quiz (5 questions)

1. Le principal avantage d’OIDC GitHub → AWS est de :
– A. Stocker des access keys plus longues dans Secrets
– B. Obtenir des credentials courts via JWT, sans clé longue durée
– C. Remplacer S3 par Git LFS

2. Sans condition sur token.actions.githubusercontent.com:sub :
– A. Seul main peut assume
– B. Tout repo GitHub pourrait tenter d’assumer le rôle (si aud match)
– C. Terraform refuse d’appliquer

3. Quelle permission Actions est obligatoire pour OIDC ?
– A. contents: write uniquement
– B. id-token: write
– C. actions: read

4. Un job avec environment: production produit typiquement un sub :
– A. repo:org/repo:environment:production
– B. Toujours ref:refs/heads/main seulement
– C. Un PAT GitHub

5. Après bascule OIDC réussie, la suite saine est de :
– A. Garder les access keys « au cas où » en clair
– B. Désactiver puis supprimer les anciennes clés / user IAM CI
– C. Committer AWS_SECRET_ACCESS_KEY dans main

Réponses : 1‑B · 2‑B · 3‑B · 4‑A · 5‑B

Pour aller plus loin

Maillage série DevOps / AWS

← Précédent IAM users, groupes, rôles
→ Suivant Terraform IAM
Aussi S3 sécurité · ECS Fargate · Azure OIDC · Hub AWS · Actions OIDC

Meta publication (SEO) — Publish: HOLD

  • Title SEO : GitHub Actions vers AWS avec OIDC : sans access keys (2026)
  • Meta description : Configurez OIDC GitHub → rôle IAM, trust policy sur sub, workflow configure-aws-credentials. Lab S3 ca-central-1, sans access keys.
  • Focus keyword : github actions oidc aws
  • Image mise en avant : assets/web/devopelastichayway/cover-github-actions-oidc-aws-1200x630.webp (Visuels DevOps/AWS — WebP 1200×630)
  • Catégorie : DevOps / AWS · Niveau : Intermédiaire
  • Slug / URL : github-actions-oidc-aws/tutoriels/github-actions-oidc-aws/
  • Statut : densifié P0 — prêt publication WP (pages Tutoriels)

Retour parcours Git — hub de la série et leçons sœurs.