Vous intégrez l'équipe Cloud Platform d'une entreprise SaaS.
L'entreprise utilise AWS et GitHub Actions pour ses déploiements.
Deux équipes coexistent :
Votre mission est de mettre en place cette séparation proprement, avec un pipeline CI/CD qui n'utilise pas de clés AWS statiques.
Vous allez travailler avec deux dépôts distincts.
Créez les deux dépôts GitHub suivants :
repo-iam-centralrepo-projet-alphaLa structure cible est la suivante :
repo-iam-central/
├── versions.tf
├── providers.tf
├── groups.tf
├── users.tf
├── policies-base.tf
└── .github/
└── workflows/
└── iam-apply.yml
repo-projet-alpha/
├── terraform/
│ ├── versions.tf
│ ├── providers.tf
│ ├── role.tf
│ └── policy.tf
├── app/
│ └── index.html
└── .github/
└── workflows/
└── deploy.yml
Fichier : repo-iam-central/versions.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "mon-tfstate-central"
key = "iam/central/terraform.tfstate"
region = "eu-west-3"
}
}
Fichier : repo-iam-central/providers.tf
provider "aws" {
region = "eu-west-3"
}
Fichier : repo-iam-central/groups.tf
resource "aws_iam_group" "developers" {
name = "developers"
}
resource "aws_iam_group" "ops" {
name = "ops"
}
Fichier : repo-iam-central/policies-base.tf
resource "aws_iam_policy" "read_only_s3" {
name = "global-read-only-s3"
description = "Lecture seule sur tous les buckets S3 - politique globale"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:ListBucket"]
Resource = "*"
}
]
})
}
resource "aws_iam_group_policy_attachment" "dev_read_s3" {
group = aws_iam_group.developers.name
policy_arn = aws_iam_policy.read_only_s3.arn
}
Question 1.1 : Pourquoi cette politique est-elle attachée au groupe et non à un utilisateur directement ?
Question 1.2 : Que se passe-t-il si un développeur change de projet ? Faut-il modifier cette politique ?
Question 1.3 : Qui devrait avoir le droit de modifier ce dépôt ? Pourquoi ?
Ce pipeline s'exécute uniquement sur la branche main, après une Pull Request validée.
Fichier : repo-iam-central/.github/workflows/iam-apply.yml
name: IAM Central Apply
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
terraform:
name: Apply IAM Central
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/role-iam-central-ci
aws-region: eu-west-3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan
- name: Terraform Apply
run: terraform apply -auto-approve
Question 1.4 : Pourquoi le pipeline utilise id-token: write ?
Question 1.5 : Pourquoi le pipeline ne se déclenche que sur main et pas sur toutes les branches ?
Question 1.6 : Que se passe-t-il si quelqu'un pousse directement sur main sans PR ? Quelle règle GitHub pourrait empêcher ça ?
Avant que le pipeline puisse fonctionner, il faut créer le rôle IAM que GitHub Actions va assumer.
Ce rôle est créé une seule fois manuellement ou via un bootstrap Terraform séparé.
Sans OIDC :
GitHub Actions
└── utilise une clé AWS statique stockée dans les secrets GitHub
└── risque : clé exposée = compte compromis
Avec OIDC :
GitHub Actions
└── présente un token signé par GitHub
└── AWS vérifie ce token
└── AWS délivre des credentials temporaires
└── valables le temps du job uniquement
Question 2.1 : Quelle est la durée de vie des credentials délivrés par OIDC ?
Question 2.2 : Que se passe-t-il si le token GitHub est intercepté après la fin du job ?
Fichier : repo-iam-central/oidc-github.tf
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
Question 2.3 : À quoi correspond le thumbprint_list ? Pourquoi ne faut-il pas le modifier sans vérification ?
Fichier : repo-iam-central/role-ci-central.tf
data "aws_iam_policy_document" "github_assume_role_central" {
statement {
effect = "Allow"
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:mon-org/repo-iam-central:ref:refs/heads/main"]
}
}
}
resource "aws_iam_role" "iam_central_ci" {
name = "role-iam-central-ci"
assume_role_policy = data.aws_iam_policy_document.github_assume_role_central.json
}
resource "aws_iam_role_policy_attachment" "iam_central_ci_policy" {
role = aws_iam_role.iam_central_ci.name
policy_arn = "arn:aws:iam::aws:policy/IAMFullAccess"
}
Remplacez mon-org/repo-iam-central par votre organisation et nom de dépôt GitHub réels.
Question 2.4 : La condition StringLike sur le sub limite l'accès à quoi exactement ?
Question 2.5 : Pourquoi attache-t-on IAMFullAccess à ce rôle et pas à un utilisateur ?
Question 2.6 : Serait-il correct d'attacher AdministratorAccess à ce rôle ? Justifiez.
L'équipe dev du projet-alpha a besoin de déployer des fichiers sur un bucket S3.
Elle ne doit pas avoir accès aux autres ressources AWS.
Fichier : repo-projet-alpha/terraform/versions.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "mon-tfstate-central"
key = "projets/projet-alpha/terraform.tfstate"
region = "eu-west-3"
}
}
Fichier : repo-projet-alpha/terraform/providers.tf
provider "aws" {
region = "eu-west-3"
}
Fichier : repo-projet-alpha/terraform/role.tf
data "aws_iam_policy_document" "github_assume_role_alpha" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/token.actions.githubusercontent.com"]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:mon-org/repo-projet-alpha:ref:refs/heads/main"]
}
}
}
resource "aws_iam_role" "deploy_alpha" {
name = "role-deploy-projet-alpha"
assume_role_policy = data.aws_iam_policy_document.github_assume_role_alpha.json
}
Fichier : repo-projet-alpha/terraform/policy.tf
variable "aws_account_id" {
type = string
description = "ID du compte AWS"
}
variable "bucket_name" {
type = string
description = "Nom du bucket S3 du projet alpha"
default = "projet-alpha-assets"
}
resource "aws_s3_bucket" "alpha_assets" {
bucket = var.bucket_name
}
resource "aws_iam_policy" "deploy_alpha_policy" {
name = "policy-deploy-projet-alpha"
description = "Permissions de déploiement pour projet-alpha uniquement"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.alpha_assets.arn,
"${aws_s3_bucket.alpha_assets.arn}/*"
]
}
]
})
}
resource "aws_iam_role_policy_attachment" "deploy_alpha_attach" {
role = aws_iam_role.deploy_alpha.name
policy_arn = aws_iam_policy.deploy_alpha_policy.arn
}
Question 3.1 : La politique donne accès à s3:DeleteObject. Est-ce justifié pour un pipeline de déploiement ? Dans quel cas oui, dans quel cas non ?
Question 3.2 : Pourquoi le Resource pointe sur un bucket précis et pas sur * ?
Question 3.3 : Que se passe-t-il si le pipeline du projet-alpha tente d'accéder à un bucket appartenant au projet-beta ?
Fichier : repo-projet-alpha/.github/workflows/deploy.yml
name: Deploy projet-alpha
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Deploy to S3
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/role-deploy-projet-alpha
aws-region: eu-west-3
- name: Deploy fichiers vers S3
run: |
aws s3 sync app/ s3://projet-alpha-assets/ --delete
Question 3.4 : Le flag –delete supprime les fichiers dans S3 qui ne sont plus dans app/. Quel risque cela représente-t-il ? Comment le mitiger ?
Question 3.5 : Pourquoi le rôle assumé ici est différent du rôle utilisé dans le dépôt IAM central ?
Appliquez la modification suivante dans repo-projet-alpha/terraform/policy.tf et observez ce qui se passe.
Remplacez la section Resource par :
Fichier : repo-projet-alpha/terraform/policy.tf (version modifiée)
Resource = ["*"]
Question 4.1 : Que permet cette modification ? Quel est le risque concret ?
Question 4.2 : Le pipeline CI/CD du projet peut-il appliquer cette modification seul ? Pourquoi ?
Question 4.3 : Quel mécanisme AWS permet d'empêcher cette modification même si Terraform l'applique ?
Revertez la modification avant de continuer.
Vous allez maintenant adapter le dépôt projet-alpha pour supporter deux environnements.
cd repo-projet-alpha/terraform terraform workspace new dev terraform workspace new prod terraform workspace select dev
Fichier : repo-projet-alpha/terraform/policy.tf (version workspace)
locals {
environment = terraform.workspace
bucket_name = "${local.environment}-projet-alpha-assets"
}
resource "aws_s3_bucket" "alpha_assets" {
bucket = local.bucket_name
}
resource "aws_iam_policy" "deploy_alpha_policy" {
name = "policy-deploy-projet-alpha-${local.environment}"
description = "Permissions de déploiement pour projet-alpha - ${local.environment}"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.alpha_assets.arn,
"${aws_s3_bucket.alpha_assets.arn}/*"
]
}
]
})
}
Fichier : repo-projet-alpha/.github/workflows/deploy.yml (version multi-env)
name: Deploy projet-alpha
on:
push:
branches:
- main
- develop
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Deploy to S3
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Définir l'environnement
id: env
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "environment=prod" >> $GITHUB_OUTPUT
else
echo "environment=dev" >> $GITHUB_OUTPUT
fi
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/role-deploy-projet-alpha
aws-region: eu-west-3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
working-directory: terraform
run: terraform init
- name: Terraform Workspace
working-directory: terraform
run: terraform workspace select ${{ steps.env.outputs.environment }}
- name: Terraform Apply
working-directory: terraform
run: terraform apply -auto-approve
- name: Deploy fichiers vers S3
run: |
aws s3 sync app/ s3://${{ steps.env.outputs.environment }}-projet-alpha-assets/ --delete
Question 5.1 : Quelle branche déclenche le déploiement en prod ? Quelle branche déclenche le déploiement en dev ?
Question 5.2 : Le même rôle IAM est utilisé pour DEV et PROD. Quel problème cela pose-t-il ? Proposez une solution.
Question 5.3 : Si un développeur pousse accidentellement sur main, que se passe-t-il ? Comment l'empêcher ?
Vous devez implémenter les éléments suivants :
1. Deux rôles IAM distincts dans repo-projet-alpha/terraform/role.tf
role-deploy-projet-alpha-dev : assumé uniquement depuis la branche developrole-deploy-projet-alpha-prod : assumé uniquement depuis la branche main2. Deux politiques distinctes
s3:DeleteObjects3:DeleteObject (déploiement additif uniquement)3. Modifier le pipeline
environment: production dans GitHub Actions)Livrable attendu : les fichiers Terraform et le fichier de pipeline complets et fonctionnels.
En entreprise avec AWS Organizations, on ajoute une couche supplémentaire.
Une SCP (Service Control Policy) est une politique appliquée au niveau du compte AWS. Elle s'applique même si un rôle IAM essaie de faire quelque chose d'interdit.
Exemple : interdire la création de ressources hors eu-west-3
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": "eu-west-3"
}
}
}
]
}
Question extension 1 : Une SCP est-elle une politique IAM comme les autres ? Quelle est la différence fondamentale ?
Question extension 2 : Si une SCP interdit une action et qu'une politique IAM l'autorise, que se passe-t-il ?
Question extension 3 : Dans quel cas une SCP est-elle indispensable en entreprise ?
repo-iam-central └── géré par équipe Cloud Platform └── pipeline protégé par branch protection + review └── rôle CI assumé via OIDC depuis main uniquement └── permissions : IAMFullAccess uniquement repo-projet-alpha └── géré par équipe dev └── pipeline DEV depuis develop └── pipeline PROD depuis main avec validation manuelle └── rôle CI assumé via OIDC - un rôle par environnement └── permissions : S3 sur le bucket du projet uniquement AWS Organizations (optionnel / niveau avancé) └── SCP appliquées au niveau du compte └── garde-fous infranchissables même pour un admin IAM
À l'issue de ce TD, vous devez être capables de :