À 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égionca-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-1Slug :
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: 900si le job est court ; permission boundary surgha-*.- 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
- Configuring OpenID Connect in Amazon Web Services
- aws-actions/configure-aws-credentials
- Creating OpenID Connect (OIDC) identity providers
- Version EN du site : GitHub Actions to AWS with OIDC
- Suite lab : S3 site web · ECS Fargate · Terraform state S3
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.