Aller au contenu

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 »

À 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é.
  • Terraform >= 1.7 et la CLI aws.
  • Un dépôt GitHub où exécuter le workflow.
  • Le TP 29 (pipeline) comme base.

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.

  1. Le workflow GitHub demande un jeton OIDC (JWT) signé par GitHub.
  2. L’action configure-aws-credentials présente ce jeton à AWS STS via AssumeRoleWithWebIdentity.
  3. 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).
  4. 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 »
oidc.tf
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.

permissions.tf
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
}
Fenêtre de terminal
terraform apply -var='github_repo=infrabank/plateforme'
terraform output ci_role_arn # à reporter dans le workflow

La nouveauté clé est le bloc permissions: id-token: write, qui autorise le workflow à demander un jeton OIDC.

.github/workflows/terraform-apply.yml
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-approve

Aucune clé AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY dans les secrets : c’est l’objectif atteint.

  • aws sts get-caller-identity dans le job affiche une identité de type rôle assumé (assumed-role/infrabank-terraform-ci/...).
  • Un push sur une autre branche que main ne peut pas assumer le rôle (condition sub).
  • Aucun secret d’accès long terme n’existe dans le dépôt.
Fenêtre de terminal
terraform destroy -var='github_repo=infrabank/plateforme' -auto-approve