Configurer GitHub Actions OIDC vers AWS sans clé statique
Section intitulée « Configurer GitHub Actions OIDC vers AWS sans clé statique »Une chaîne d’intégration et de déploiement continus, ou CI/CD (Continuous Integration and Continuous Delivery), ne doit pas contenir une clé AWS (Amazon Web Services) permanente. La fédération OpenID Connect, ou OIDC (OpenID Connect), permet au job GitHub Actions d’obtenir des identifiants AWS temporaires, liés à son dépôt, sa branche et son exécution.
Un badge visiteur permet d’entrer dans un bâtiment pour quelques heures, dans une zone précise, sans remettre le passe-partout du directeur. La fédération d’identité applique cette idée aux programmes automatisés : ils reçoivent une autorisation limitée pour accomplir une tâche, puis elle cesse d’exister.
→ Lire l’explication complète de la marmotte
Le compromis est net : on élimine une clé durable, donc une fuite exploitable pendant des mois, mais on transforme la politique de confiance en code de sécurité critique. Une condition trop large sur le dépôt ou la branche rend le jeton temporaire aussi dangereux qu’une clé permanente, avec davantage de syntaxe et moins de sommeil.
La recommandation est de restreindre le rôle AWS par la revendication sub complète, au minimum repo:ORGANISATION/DEPOT:ref:refs/heads/BRANCHE. Autoriser repo:organisation/* parce que « tous les dépôts sont de confiance » est une erreur d’architecture : tout dépôt compromis, créé par erreur ou mal gouverné devient une porte d’entrée cloud. Les fondations CI/CD ne remplacent pas une frontière d’autorisation.
Prérequis GitHub Actions, AWS IAM et OIDC
Section intitulée « Prérequis GitHub Actions, AWS IAM et OIDC »Le cas traité utilise GitHub Actions comme émetteur de jetons et AWS Identity and Access Management, ou IAM, comme moteur d’autorisation. Le compte AWS doit autoriser la création d’un fournisseur OIDC, d’un rôle IAM et de politiques IAM. L’AWS Command Line Interface, ou AWS CLI, version 2 doit être authentifiée localement avec une identité d’administration, uniquement pendant le paramétrage initial.
Préparer les constantes suivantes. Remplacer les valeurs d’exemple avant toute commande :
# Identifiant numérique du compte AWS cibleexport AWS_ACCOUNT_ID="123456789012"
# Dépôt GitHub autorisé, avec casse exacteexport GITHUB_REPOSITORY="tortuetech/infra-production"
# Branche seule autorisée à demander le rôleexport GITHUB_BRANCH="main"
# Nom du rôle assumé par le pipelineexport AWS_ROLE_NAME="github-actions-deploy-production"GitHub émet ses jetons depuis https://token.actions.githubusercontent.com. AWS vérifie la signature du jeton contre les clés publiques publiées par cet émetteur, puis évalue les revendications. Une revendication est une donnée signée dans le jeton, par exemple son audience, son dépôt ou sa référence Git. Elle n’est pas une variable d’environnement déclarative que le job peut réécrire avec enthousiasme et trois lignes de YAML.
Le job GitHub doit déclarer id-token: write. Cette permission n’autorise pas une écriture dans AWS, malgré son nom trompeur. Elle autorise uniquement l’obtention d’un jeton OIDC auprès de GitHub. Les autorisations AWS restent définies par la politique IAM attachée au rôle.
Ne pas exécuter un déploiement ayant accès à ce rôle depuis pull_request_target. Cet événement peut associer des permissions élevées à du code issu d’une demande de fusion non approuvée. Un jeton signé ne rend pas un script malveillant sympathique, il certifie seulement son origine et son contexte.
Fournisseur OIDC GitHub Actions dans AWS IAM
Section intitulée « Fournisseur OIDC GitHub Actions dans AWS IAM »Créer le fournisseur OIDC une seule fois par compte AWS. L’audience sts.amazonaws.com désigne AWS Security Token Service, ou STS, le service qui échange le jeton GitHub contre des identifiants temporaires AWS.
# Crée la relation de confiance entre le compte AWS et GitHub Actions.# La commande échoue si ce fournisseur existe déjà dans le compte.aws iam create-open-id-connect-provider \ --url "https://token.actions.githubusercontent.com" \ --client-id-list "sts.amazonaws.com"
# Contrôle de présence et récupération de l'ARN du fournisseur créé.aws iam list-open-id-connect-providers \ --query "OpenIDConnectProviderList[?contains(Arn, 'token.actions.githubusercontent.com')].Arn" \ --output textLe fournisseur OIDC n’accorde aucun droit AWS à lui seul. Il enregistre un émetteur accepté et l’audience attendue. Le rôle IAM porte la vraie décision : qui peut l’assumer, dans quel contexte, et quelles actions deviennent possibles ensuite.
Créer d’abord la politique de permissions. Elle doit viser les ressources précises manipulées par le déploiement. Le rôle ci-dessous peut lire et écrire dans un préfixe précis d’un compartiment Amazon Simple Storage Service, ou S3. Accorder s3:* sur * à un pipeline est le raccourci préféré des démonstrations commerciales et des incidents réels.
cat > permissions-deploy.json <<'EOF'{ "Version": "2012-10-17", "Statement": [ { "Sid": "DeployArtifactsOnly", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::tortuetech-production-artifacts/releases/*" }, { "Sid": "ListDeploymentPrefix", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::tortuetech-production-artifacts", "Condition": { "StringLike": { "s3:prefix": "releases/*" } } } ]}EOF
aws iam create-policy \ --policy-name "github-actions-deploy-artifacts" \ --policy-document file://permissions-deploy.jsonPolitique de confiance AWS limitée au dépôt et à la branche
Section intitulée « Politique de confiance AWS limitée au dépôt et à la branche »La politique de confiance est le point de contrôle déterminant. aud impose l’audience STS. sub lie le rôle à un dépôt et à la branche main. Utiliser StringEquals, pas StringLike, tant qu’aucun motif n’est indispensable. Un joker dans une identité est rarement un détail de confort, plutôt une permission qui attend son incident.
cat > trust-policy.json <<EOF{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowGitHubActionsFromMainOnly", "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", "token.actions.githubusercontent.com:sub": "repo:${GITHUB_REPOSITORY}:ref:refs/heads/${GITHUB_BRANCH}" } } } ]}EOF
aws iam create-role \ --role-name "$AWS_ROLE_NAME" \ --assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \ --role-name "$AWS_ROLE_NAME" \ --policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/github-actions-deploy-artifacts"Une protection supplémentaire consiste à déclarer repository_id dans les conditions IAM, lorsque la gouvernance impose une défense contre le renommage ou le transfert du dépôt. Cette revendication numérique est plus stable que le nom textuel. Elle demande toutefois de connaître l’identifiant GitHub du dépôt et de maintenir explicitement la politique si le dépôt est recréé. Pour la plupart des dépôts stables, combiner sub exact et revue des changements IAM constitue le minimum non négociable.
La politique de confiance ne protège pas contre un mainteneur autorisé qui modifie le workflow sur main. Ce risque relève des protections de branche, des règles de revue et de la séparation des droits GitHub. La fédération atteste le contexte GitHub, elle ne transforme pas ce contexte en sanctuaire.
Job GitHub Actions et échange du jeton temporaire
Section intitulée « Job GitHub Actions et échange du jeton temporaire »Créer .github/workflows/deploy.yml. L’exemple demande un jeton OIDC, appelle explicitement STS, exporte les identifiants temporaires et vérifie l’identité obtenue. Aucun secret AWS n’est stocké dans GitHub.
name: deploy-production
on: push: branches: [main]
permissions: contents: read id-token: write
jobs: deploy: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4
- name: Échanger le jeton GitHub contre des identifiants AWS shell: bash env: ROLE_ARN: arn:aws:iam::123456789012:role/github-actions-deploy-production run: | set -euo pipefail
# GitHub fournit une URL et un jeton d'accès éphémère au job. OIDC_TOKEN="$(curl --fail --silent --show-error \ -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com" \ | jq -r '.value')"
test -n "${OIDC_TOKEN}" test "${OIDC_TOKEN}" != "null"
# STS retourne des clés de session limitées dans le temps. aws sts assume-role-with-web-identity \ --role-arn "${ROLE_ARN}" \ --role-session-name "github-${GITHUB_RUN_ID}" \ --web-identity-token "${OIDC_TOKEN}" \ --duration-seconds 900 \ --output json > credentials.json
export AWS_ACCESS_KEY_ID="$(jq -r '.Credentials.AccessKeyId' credentials.json)" export AWS_SECRET_ACCESS_KEY="$(jq -r '.Credentials.SecretAccessKey' credentials.json)" export AWS_SESSION_TOKEN="$(jq -r '.Credentials.SessionToken' credentials.json)"
aws sts get-caller-identity rm -f credentials.json
- name: Déployer les artefacts run: aws s3 sync ./dist s3://tortuetech-production-artifacts/releases/Fixer la durée à 900 secondes réduit la fenêtre d’exploitation, mais ne révoque pas une session déjà délivrée. Désactiver le fournisseur OIDC ou modifier une politique IAM empêche les futurs échanges, pas les clés temporaires déjà émises. Les permissions sensibles doivent donc être minimales, et les sessions courtes.
Sur un exécuteur auto-hébergé, installer AWS CLI v2, curl et jq, puis empêcher les jobs non approuvés de partager cet exécuteur. Un exécuteur persistant peut conserver des fichiers, processus ou variables entre jobs si son nettoyage est défaillant. Une identité parfaite attachée à une machine poreuse reste une identité parfaite dans une machine poreuse.
Tests négatifs et preuves d’étanchéité OIDC
Section intitulée « Tests négatifs et preuves d’étanchéité OIDC »Une politique non testée est une décoration JSON. Créer une branche oidc-deny-test, y ajouter le même workflow avec un déclenchement sur cette branche, puis lancer le job. L’étape assume-role-with-web-identity doit échouer avec AccessDenied. Le journal doit montrer que STS refuse l’hypothèse du rôle, avant toute commande S3.
Tester aussi depuis un autre dépôt de la même organisation. Copier le workflow, conserver le même ROLE_ARN et lancer une exécution manuelle ou un push sur main. L’échec attendu est identique : la revendication sub contient le nom du second dépôt, qui ne correspond pas à la valeur autorisée.
Ne pas se contenter d’un test depuis la branche autorisée. Celui-ci prouve que le chemin nominal fonctionne, pas que la clôture tient. Les tests négatifs doivent être rejoués après toute modification de la politique de confiance, du nom du dépôt, de la stratégie de branche ou du mécanisme de déploiement.
Activer AWS CloudTrail pour conserver les appels STS et S3. Surveiller au minimum les événements AssumeRoleWithWebIdentity, les échecs répétés d’échange et les sessions dont le nom ne suit pas le format attendu. CloudTrail ne bloque rien, il permet de reconstituer un échange. C’est moins glamour qu’un tableau de bord violet, mais nettement plus exploitable après un problème.
La sauvegarde concerne le code de politique IAM, les workflows GitHub et la configuration d’infrastructure, pas des identifiants AWS inexistants. Versionner ces fichiers, protéger leurs modifications et prévoir un rôle d’urgence distinct, à authentification forte, pour réparer un fournisseur OIDC supprimé ou une politique devenue trop restrictive. Le rôle d’urgence ne doit jamais être le même rôle que celui du pipeline, sinon l’urgence devient une habitude avec des permissions d’administration.
Pour prolonger la réflexion sur les frontières d’identité et les privilèges, consulter la page identités et les principes d’architecture cybersécurité.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans fédération OIDC, l’accès AWS repose sur une clé statique déposée dans les secrets du dépôt. Cette clé reste valable jusqu’à rotation manuelle : une copie prise dans un journal de build, un cache d’exécuteur ou un poste de développement continue de fonctionner des mois plus tard, y compris hors de toute exécution GitHub. Le contexte disparaît aussi : CloudTrail n’enregistre qu’un utilisateur IAM unique, sans dépôt, sans branche, sans identifiant d’exécution, ce qui empêche de rattacher un appel S3 à un commit précis ou de distinguer un déploiement légitime d’un usage détourné. La révocation devient une opération globale qui interrompt tous les pipelines partageant la clé, et le test négatif décrit plus haut n’a plus d’équivalent : aucune condition ne peut refuser une branche non autorisée, puisque la clé ignore d’où elle est utilisée.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un programme de déploiement doit parfois modifier des ressources hébergées chez un fournisseur cloud. Une ancienne méthode consistait à enregistrer une clé secrète permanente dans la plateforme de code. Si cette clé fuit, elle peut être utilisée jusqu’à son remplacement, parfois très longtemps.
Ici, la plateforme de code émet à chaque exécution un document signé qui décrit le programme en cours. Ce document contient notamment le dépôt de code, la branche utilisée et le destinataire prévu. Le fournisseur cloud connaît à l’avance la signature de la plateforme et vérifie que le document est authentique.
Le fournisseur cloud possède aussi une règle. Cette règle autorise uniquement le dépôt nommé et la branche nommée. Si le document vient d’une autre branche ou d’un autre dépôt, les noms ne correspondent pas et le fournisseur refuse de donner des accès. Le programme ne peut alors pas déployer.
Si les conditions correspondent, le fournisseur cloud remet des identifiants temporaires. Ils permettent seulement les actions inscrites dans la règle, par exemple déposer des fichiers dans un emplacement précis. Ils expirent rapidement. Le programme les utilise pendant son exécution, puis ils deviennent inutilisables.
Cette protection ne garantit pas que le code autorisé est sans danger. Une personne pouvant modifier la branche autorisée peut aussi modifier le programme de déploiement. Il faut donc protéger cette branche, examiner les changements et vérifier régulièrement que les essais depuis une branche ou un dépôt non autorisé échouent bien.
À lire aussi
- Sécuriser les identifiants CI/CD avec OIDCGuide pas-à-pas pour remplacer les secrets statiques d’un pipeline CI/CD par une authentification fédérée OIDC, isoler les jobs et limiter les droits par environnement.
- Mettre en place un accès JIT AWS pour comptes à privilègesGuide pratique pour déployer un accès juste-à-temps AWS avec rôles temporaires, approbation, révocation automatisée et audit CloudTrail.
- Configurer OIDC entre Keycloak et application interneGuide pratique pour fédérer une application interne avec un fournisseur d’identité OpenID Connect, du client OIDC à l’exploitation en production.
- Détecter la persistance IAM dans AWSCatalogue de détection des techniques de persistance IAM dans AWS, incluant rôles de confiance, clés d'accès dormantes et fonctions déclenchées à intervalle.
Pages voisines
La citent
- Sécuriser les identifiants CI/CD avec OIDC
- Comptes de service Git à révoquer réellement
- Les comptes de service oubliés
- Réconcilier inventaire IaC et cloud réel
- Sécuriser les secrets et permissions CI/CD
- Sécuriser un monorepo avec des permissions fines
- SLSA : niveaux Build, provenance in-toto et vérification
Configurer GitHub Actions OIDC vers AWS sans clé statique
Elle cite
: les deux pages se citent l’une l’autre.
