TP 30 : Sécuriser l'authentification CI/CD avec OIDC
TP 30 : Sécuriser l’authentification CI/CD avec OIDC
Section intitulée « TP 30 : Sécuriser l’authentification CI/CD avec OIDC »Objectifs
Section intitulée « Objectifs »À l’issue de ce TP, vous serez capable de :
- expliquer les risques des clés d’accès AWS statiques en CI ;
- créer un fournisseur d’identité OIDC AWS pour GitHub Actions ;
- écrire une politique de confiance (
sts:AssumeRoleWithWebIdentity) restreinte au bon dépôt et à la bonne branche ; - configurer un workflow qui obtient des identifiants temporaires sans secret stocké.
Prérequis
Section intitulée « Prérequis »- Terraform
>= 1.7et la CLIaws. - Un dépôt GitHub où exécuter le workflow.
- Le TP 29 (pipeline) comme base.
Contexte
Section intitulée « Contexte »InfraBank stockait jusqu’ici une clé d’accès AWS dans les secrets GitHub. Une clé statique fuite, ne tourne pas d’elle-même, et donne un accès permanent. La fédération OIDC supprime le secret : GitHub prouve son identité à AWS, qui délivre des identifiants temporaires.
Étape 1 : Comprendre le flux OIDC
Section intitulée « Étape 1 : Comprendre le flux OIDC »- Le workflow GitHub demande un jeton OIDC (JWT) signé par GitHub.
- L’action
configure-aws-credentialsprésente ce jeton à AWS STS viaAssumeRoleWithWebIdentity. - AWS vérifie la signature auprès du fournisseur OIDC déclaré, contrôle la politique de confiance (dépôt, branche), puis renvoie des identifiants temporaires (validité de l’ordre de l’heure).
- Terraform utilise ces identifiants le temps du job, puis ils expirent.
Aucune clé longue durée n’est stockée nulle part.
Étape 2 : Déclarer le fournisseur OIDC et le rôle en Terraform
Section intitulée « Étape 2 : Déclarer le fournisseur OIDC et le rôle en Terraform »data "aws_caller_identity" "current" {}
variable "github_repo" { type = string description = "Dépôt au format owner/repository." # ex. "infrabank/plateforme"}
resource "aws_iam_openid_connect_provider" "github" { url = "https://token.actions.githubusercontent.com" client_id_list = ["sts.amazonaws.com"] thumbprint_list = ["ffffffffffffffffffffffffffffffffffffffff"] # Le thumbprint est vérifié par AWS pour ce fournisseur ; récupérez la # valeur courante via la doc GitHub/AWS. AWS gère désormais la rotation # du certificat pour ce fournisseur bien connu.}
data "aws_iam_policy_document" "trust" { statement { effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"]
principals { type = "Federated" identifiers = [aws_iam_openid_connect_provider.github.arn] }
# Le jeton doit être destiné à STS. condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:aud" values = ["sts.amazonaws.com"] }
# Restriction au dépôt ET à la branche main uniquement. condition { test = "StringLike" variable = "token.actions.githubusercontent.com:sub" values = ["repo:${var.github_repo}:ref:refs/heads/main"] } }}
resource "aws_iam_role" "ci" { name = "infrabank-terraform-ci" assume_role_policy = data.aws_iam_policy_document.trust.json}La condition sur sub est le point crucial : sans elle, n’importe quel dépôt GitHub pourrait assumer le rôle. On restreint au dépôt et à la branche attendus (ici main). Pour des environnements, on peut aussi cibler environment:prod.
Étape 3 : Attacher des permissions au moindre privilège
Section intitulée « Étape 3 : Attacher des permissions au moindre privilège »Le rôle ne doit avoir que les permissions nécessaires. À adapter au périmètre réel ; en formation, on peut partir d’une politique étroite plutôt que d’un accès administrateur.
data "aws_iam_policy_document" "ci_permissions" { statement { sid = "StateBackend" effect = "Allow" actions = [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket", ] resources = [ "arn:aws:s3:::infrabank-tfstate-XXXXXXXX", "arn:aws:s3:::infrabank-tfstate-XXXXXXXX/*", ] }
# Ajoutez ici les actions strictement nécessaires aux ressources gérées.}
resource "aws_iam_role_policy" "ci" { name = "infrabank-terraform-ci" role = aws_iam_role.ci.id policy = data.aws_iam_policy_document.ci_permissions.json}
output "ci_role_arn" { value = aws_iam_role.ci.arn}terraform apply -var='github_repo=infrabank/plateforme'terraform output ci_role_arn # à reporter dans le workflowÉtape 4 : Adapter le workflow GitHub Actions
Section intitulée « Étape 4 : Adapter le workflow GitHub Actions »La nouveauté clé est le bloc permissions: id-token: write, qui autorise le workflow à demander un jeton OIDC.
name: terraform-apply
on: push: branches: [main]
permissions: id-token: write # requis pour obtenir le jeton OIDC contents: read
env: TF_VERSION: "1.11.4" AWS_REGION: "eu-west-3"
jobs: apply: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Configurer les identifiants AWS via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/infrabank-terraform-ci aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: ${{ env.TF_VERSION }}
- name: Vérifier l'identité obtenue run: aws sts get-caller-identity
- name: Init + Apply run: | terraform init terraform apply -input=false -auto-approveAucune clé AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY dans les secrets : c’est l’objectif atteint.
Étape 5 : Vérifier
Section intitulée « Étape 5 : Vérifier »aws sts get-caller-identitydans le job affiche une identité de type rôle assumé (assumed-role/infrabank-terraform-ci/...).- Un push sur une autre branche que
mainne peut pas assumer le rôle (conditionsub). - Aucun secret d’accès long terme n’existe dans le dépôt.
Nettoyage
Section intitulée « Nettoyage »terraform destroy -var='github_repo=infrabank/plateforme' -auto-approve