| Les deux révisions précédentes Révision précédente | |
| eadl:bloc4:fm4:td3 [2026/06/24 08:10] – jcheron | eadl:bloc4:fm4:td3 [2026/06/25 03:35] (Version actuelle) – jcheron |
|---|
| * Mettre en place une traçabilité avec CloudTrail | * Mettre en place une traçabilité avec CloudTrail |
| * Identifier une action suspecte via les logs | * Identifier une action suspecte via les logs |
| | * Récupérer des ressources existantes via des data sources AWS |
| |
| ===== Contexte ===== | ===== Contexte ===== |
| |
| * Projet Terraform du TD2 déployé et fonctionnel | * Projet Terraform du TD2 déployé et fonctionnel |
| | * Ressources TD2 taguées avec `Name = "td2-vpc"`, `Name = "td2-private"`, etc. |
| * ALB opérationnel, Security Groups en place | * ALB opérationnel, Security Groups en place |
| * Accès AWS avec droits suffisants : IAM, Secrets Manager, KMS, S3, RDS, CloudTrail | * Accès AWS avec droits suffisants : IAM, Secrets Manager, KMS, S3, RDS, CloudTrail |
| ===== Structure du projet ===== | ===== Structure du projet ===== |
| |
| Le TD3 s'appuie sur les ressources existantes du TD2 et ajoute quatre nouveaux modules. | Le TD3 est un projet Terraform indépendant. |
| | |
| | Il ne modifie pas le code du TD2. |
| | |
| | Il récupère les ressources existantes via des data sources AWS interrogeant l'API par tags. |
| |
| Fichier : `td3/structure.txt` | Fichier : `td3/structure.txt` |
| <sxh bash> | <sxh bash> |
| td3/ | tp-aws/ |
| ├── main.tf | ├── td1/ # IAM – roles, policies |
| ├── variables.tf | ├── td2/ # Réseau – VPC, subnets, ALB, Security Groups |
| ├── outputs.tf | └── td3/ # Secrets, KMS, chiffrement, logs |
| └── modules/ | ├── main.tf |
| ├── secrets/ | ├── variables.tf |
| │ └── main.tf | ├── outputs.tf |
| ├── kms/ | └── modules/ |
| │ └── main.tf | ├── secrets/ |
| ├── storage/ | │ ├── main.tf |
| │ └── main.tf | │ ├── variables.tf |
| └── logging/ | │ └── outputs.tf |
| └── main.tf | ├── kms/ |
| | │ ├── main.tf |
| | │ ├── variables.tf |
| | │ └── outputs.tf |
| | ├── storage/ |
| | │ ├── main.tf |
| | │ ├── variables.tf |
| | │ └── outputs.tf |
| | └── logging/ |
| | ├── main.tf |
| | ├── variables.tf |
| | └── outputs.tf |
| </sxh> | </sxh> |
| |
| Le fichier `td3/main.tf` orchestre les quatre modules et récupère les ressources du TD2 via des data sources. | ===== 1. Récupération des ressources du TD2 ===== |
| |
| ===== 1. Analyse de l'existant ===== | Objectif : comprendre comment un projet Terraform peut interroger une infrastructure existante sans accéder à son state. |
| |
| Lire le fichier `td2/network/instances.tf` avant toute manipulation. | TD3 utilise des data sources AWS natifs. |
| |
| <WRAP round question> | Ces data sources envoient des requêtes directement à l'API AWS et filtrent les ressources par tags. |
| Localiser la ligne où le mot de passe de la base de données est défini. | |
| |
| Répondre aux questions suivantes : | Cela suppose que les ressources TD2 ont été correctement taguées. |
| * Qui peut lire ce mot de passe concrètement ? | |
| * Ce fichier est-il souvent versionné dans Git en entreprise ? | |
| * Quelles sont les conséquences si ce dépôt est public ou partagé avec un prestataire ? | |
| </WRAP> | |
| |
| <WRAP round question> | <WRAP round question> |
| Dans `td2/network/instances.tf`, la ressource `aws_db_instance` ne contient pas le paramètre `storage_encrypted`. | Pourquoi TD3 ne peut-il pas utiliser directement les outputs du TD2 sans configuration supplémentaire ? |
| | |
| Quelle est la valeur par défaut de ce paramètre dans AWS ? | |
| |
| Comment vérifier l'état du chiffrement d'une instance RDS existante dans la console AWS ? | Quelle est la différence entre un data source AWS et un `terraform_remote_state` ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | |
| Aucun service de logging n'est présent dans le TD2. | |
| |
| Citer deux situations concrètes en production où l'absence de logs rend la gestion d'incident impossible. | |
| </WRAP> | |
| |
| ===== 2. Récupération des ressources du TD2 ===== | |
| |
| Le TD3 ne redéploie pas les ressources du TD2. | |
| |
| Il les récupère via des data sources Terraform pour les réutiliser. | |
| |
| Fichier : `td3/main.tf` | Fichier : `td3/main.tf` |
| <sxh js> | <sxh js> |
| terraform { | |
| required_providers { | |
| aws = { | |
| source = "hashicorp/aws" | |
| version = "~> 5.0" | |
| } | |
| random = { | |
| source = "hashicorp/random" | |
| version = "~> 3.0" | |
| } | |
| } | |
| } | |
| |
| provider "aws" { | provider "aws" { |
| region = "eu-west-3" | region = "eu-west-3" |
| } | } |
| |
| # Recuperation du compte AWS courant | # Récupération du VPC créé dans TD2 |
| data "aws_caller_identity" "current" {} | data "aws_vpc" "main" { |
| | tags = { |
| # Recuperation de la region courante | Name = "td2-vpc" |
| data "aws_region" "current" {} | |
| | |
| # Recuperation du VPC cree au TD2 | |
| data "aws_vpc" "td2" { | |
| filter { | |
| name = "tag:Name" | |
| values = ["td2-vpc"] | |
| } | } |
| } | } |
| |
| # Recuperation du Security Group backend du TD2 | # Récupération du subnet privé créé dans TD2 |
| data "aws_security_group" "backend_sg" { | data "aws_subnet" "private" { |
| filter { | tags = { |
| name = "tag:Name" | Name = "td2-private" |
| values = ["backend-sg"] | |
| } | } |
| } | } |
| |
| module "secrets" { | # Récupération du subnet privé secondaire créé dans TD2 |
| source = "./modules/secrets" | data "aws_subnet" "private_b" { |
| | tags = { |
| | Name = "td2-private-b" |
| | } |
| } | } |
| |
| module "kms" { | # Récupération du Security Group RDS créé dans TD2 |
| source = "./modules/kms" | data "aws_security_group" "rds" { |
| account_id = data.aws_caller_identity.current.account_id | tags = { |
| region = data.aws_region.current.name | Name = "td2-sg-rds" |
| | } |
| } | } |
| |
| module "storage" { | # Récupération du Security Group de l'application créé dans TD2 |
| source = "./modules/storage" | data "aws_security_group" "app" { |
| kms_key_arn = module.kms.key_arn | tags = { |
| | Name = "td2-sg-app" |
| | } |
| } | } |
| | </sxh> |
| |
| module "logging" { | <WRAP round todo> |
| source = "./modules/logging" | Vérifier dans le code TD2 que chaque ressource est bien taguée avec les valeurs attendues ci-dessus. |
| account_id = data.aws_caller_identity.current.account_id | |
| kms_key_arn = module.kms.key_arn | Si un tag est absent ou différent, le data source échouera avec une erreur `no matching resource found`. |
| | |
| | Corriger les tags dans TD2 si nécessaire avant de poursuivre. |
| | </WRAP> |
| | |
| | <WRAP round question> |
| | Que se passe-t-il si deux ressources AWS portent le même tag `Name` dans la même région ? |
| | |
| | Quel comportement Terraform adopte-t-il dans ce cas ? |
| | </WRAP> |
| | |
| | ===== 2. Identification du problème ===== |
| | |
| | Objectif : identifier les secrets en clair dans l'infrastructure TD2. |
| | |
| | Voici un extrait du fichier `instances.tf` tel qu'il existe après le TD2. |
| | |
| | Fichier : `td2/instances.tf` — lecture seule, ne pas modifier |
| | <sxh js> |
| | resource "aws_db_instance" "db" { |
| | identifier = "td2-db" |
| | engine = "postgres" |
| | instance_class = "db.t3.micro" |
| | allocated_storage = 20 |
| | |
| | username = "admin" |
| | password = "S3cr3tP@ss!" |
| | |
| | skip_final_snapshot = true |
| } | } |
| </sxh> | </sxh> |
| |
| <WRAP round question> | <WRAP round question> |
| Pourquoi utilise-t-on des data sources plutôt que de redéclarer les ressources du TD2 ? | Identifier au moins trois problèmes de sécurité dans ce bloc. |
| |
| Que se passerait-il si on redéclarait `aws_vpc` avec le même CIDR dans un nouveau fichier Terraform ? | Pour chaque problème, expliquer la conséquence concrète en cas de fuite du fichier `.tf` ou du state Terraform. |
| </WRAP> | </WRAP> |
| |
| ===== 3. Gestion des secrets avec Secrets Manager ===== | <WRAP round todo> |
| | Ne pas appliquer ce fichier tel quel. |
| |
| Objectif : supprimer le mot de passe en clair de `instances.tf`. | Ne pas modifier le TD2 non plus. |
| |
| <WRAP round todo> | La correction se fait uniquement dans TD3. |
| Créer le module secrets. | |
| </WRAP> | </WRAP> |
| |
| Fichier : `td3/modules/secrets/main.tf` | ===== 3. Création du module secrets ===== |
| | |
| | Objectif : stocker les identifiants de la base de données dans AWS Secrets Manager. |
| | |
| | Fichier : `td3/modules/secrets/variables.tf` |
| <sxh js> | <sxh js> |
| | variable "secret_name" { |
| | type = string |
| | description = "Nom du secret dans Secrets Manager" |
| | default = "td3/db/credentials" |
| | } |
| | |
| variable "db_username" { | variable "db_username" { |
| type = string | type = string |
| description = "Nom d'utilisateur de la base de données" | description = "Nom d'utilisateur de la base de données" |
| default = "admin" | default = "td3admin" |
| } | } |
| |
| sensitive = true | sensitive = true |
| } | } |
| | </sxh> |
| |
| | Fichier : `td3/modules/secrets/main.tf` |
| | <sxh js> |
| resource "aws_secretsmanager_secret" "db_credentials" { | resource "aws_secretsmanager_secret" "db_credentials" { |
| name = "td3/db/credentials" | name = var.secret_name |
| description = "Identifiants de la base de donnees RDS TD3" | recovery_window_in_days = 7 |
| | |
| | tags = { |
| | Name = "td3-secret-db" |
| | TD = "td3" |
| | } |
| } | } |
| |
| resource "aws_secretsmanager_secret_version" "db_credentials" { | resource "aws_secretsmanager_secret_version" "db_credentials" { |
| secret_id = aws_secretsmanager_secret.db_credentials.id | secret_id = aws_secretsmanager_secret.db_credentials.id |
| | |
| secret_string = jsonencode({ | secret_string = jsonencode({ |
| username = var.db_username | username = var.db_username |
| }) | }) |
| } | } |
| | </sxh> |
| |
| | Fichier : `td3/modules/secrets/outputs.tf` |
| | <sxh js> |
| output "secret_arn" { | output "secret_arn" { |
| value = aws_secretsmanager_secret.db_credentials.arn | value = aws_secretsmanager_secret.db_credentials.arn |
| description = "ARN du secret contenant les identifiants RDS" | description = "ARN du secret contenant les identifiants de la base de données" |
| } | } |
| </sxh> | |
| |
| Fichier : `td3/variables.tf` | output "secret_name" { |
| <sxh js> | value = aws_secretsmanager_secret.db_credentials.name |
| variable "db_password" { | description = "Nom du secret dans Secrets Manager" |
| type = string | |
| description = "Mot de passe initial de la base de donnees" | |
| sensitive = true | |
| } | } |
| </sxh> | </sxh> |
| |
| Le mot de passe est transmis via une variable sensible, jamais écrit en dur. | <WRAP round question> |
| | Le paramètre `sensitive = true` sur la variable `db_password` masque la valeur dans les logs Terraform. |
| |
| Pour appliquer, Terraform demandera la valeur au moment de l'exécution : | Cela signifie-t-il que le mot de passe est chiffré dans le state Terraform ? |
| |
| Fichier : `td3/commandes/terraform.txt` | Où le mot de passe est-il stocké en clair malgré ce paramètre ? |
| <sxh bash> | </WRAP> |
| # Appliquer en fournissant le mot de passe de maniere interactive | |
| terraform apply -var="db_password=MonMotDePasse123!" | |
| | |
| # Ou via une variable d'environnement sans valeur dans le code | |
| export TF_VAR_db_password="MonMotDePasse123!" | |
| terraform apply | |
| </sxh> | |
| |
| <WRAP round question> | <WRAP round question> |
| Pourquoi utilise-t-on `sensitive = true` sur la variable `db_password` ? | Le paramètre `recovery_window_in_days = 7` signifie que le secret n'est pas supprimé immédiatement. |
| |
| Que change ce paramètre dans l'affichage Terraform ? | Pourquoi AWS impose-t-il cette fenêtre de récupération par défaut ? |
| |
| Est-ce suffisant pour garantir que le mot de passe ne sera jamais visible ? | Comment forcer la suppression immédiate si nécessaire, et dans quel cas cela est-il risqué ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | ===== 4. Erreur volontaire – secret introuvable ===== |
| Pourquoi utilise-t-on un JSON pour stocker le secret plutôt qu'une simple chaîne ? | |
| |
| Quel avantage cela apporte-t-il si l'application doit récupérer le secret programmatiquement ? | Objectif : comprendre ce qui se passe quand une application tente de lire un secret inexistant. |
| </WRAP> | |
| |
| ===== 4. Problème volontaire – dépendance entre modules ===== | Voici un data source intentionnellement incorrect. |
| | |
| | Fichier : `td3/debug/wrong_secret.tf` — ne pas appliquer |
| | <sxh js> |
| | data "aws_secretsmanager_secret" "db_credentials" { |
| | name = "td3/db/credential" |
| | } |
| | </sxh> |
| |
| <WRAP round todo> | <WRAP round todo> |
| Tenter d'appliquer uniquement le module secrets : | Lire le code ci-dessus sans l'appliquer. |
| |
| terraform apply -target=module.secrets | Identifier l'erreur sans regarder la réponse ci-dessous. |
| |
| Puis observer ce qui se passe si on tente de lire le secret immédiatement dans le module storage sans `depends_on`. | Écrire la correction avant de lire la suite. |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | Le nom du secret dans Secrets Manager est `td3/db/credentials` avec un `s` final. |
| Terraform gère les dépendances implicites entre ressources du même fichier. | |
| |
| Pourquoi cette dépendance implicite ne fonctionne-t-elle pas toujours entre modules différents ? | Le data source ci-dessus cherche `td3/db/credential` sans `s`. |
| | |
| | L'erreur retournée par Terraform sera : |
| | |
| | Fichier : `td3/debug/wrong_secret_error.txt` |
| | <sxh bash> |
| | Error: reading Secrets Manager Secret: ResourceNotFoundException: |
| | Secrets Manager can't find the specified secret. |
| | </sxh> |
| | |
| | <WRAP round question> |
| | Ce type d'erreur peut-il survenir en production alors que le secret existe bien ? |
| |
| Dans quels cas faut-il utiliser `depends_on` explicitement ? | Citer au moins deux situations réelles où cette erreur peut apparaître même si le nom est correct. |
| </WRAP> | </WRAP> |
| |
| ===== 5. Création d'une clé KMS ===== | ===== 5. Récupération du secret dans RDS ===== |
| |
| Objectif : créer une clé de chiffrement gérée par le client (CMK). | Objectif : utiliser le secret Secrets Manager dans la configuration RDS plutôt que des valeurs en clair. |
| |
| <WRAP round question> | Fichier : `td3/modules/secrets/main.tf` — ajouter après les ressources existantes |
| AWS propose deux types de clés KMS : | <sxh js> |
| * les clés AWS managées (aws/rds, aws/s3...) | data "aws_secretsmanager_secret_version" "db_credentials" { |
| * les clés Customer Managed Keys (CMK) | secret_id = aws_secretsmanager_secret.db_credentials.id |
| |
| Quelle est la différence concrète entre les deux en termes de contrôle ? | depends_on = [aws_secretsmanager_secret_version.db_credentials] |
| | } |
| |
| Qui peut utiliser une clé AWS managée ? | locals { |
| | db_secret = jsondecode( |
| | data.aws_secretsmanager_secret_version.db_credentials.secret_string |
| | ) |
| | } |
| | </sxh> |
| |
| Qui décide des droits d'accès sur une CMK ? | <WRAP round question> |
| | Pourquoi utilise-t-on `depends_on` ici ? |
| | |
| | Que se passerait-il si Terraform tentait de lire le secret avant que la version soit créée ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round todo> | ===== 6. Création du module KMS ===== |
| Créer le module KMS. | |
| | Objectif : créer une clé de chiffrement gérée par le client (Customer Managed Key) pour chiffrer les données au repos. |
| | |
| | <WRAP round question> |
| | Avant de créer la clé, répondre aux questions suivantes : |
| | |
| | Quelle est la différence entre une clé AWS managée et une CMK (Customer Managed Key) ? |
| | |
| | Dans quel cas une CMK est-elle obligatoire plutôt que recommandée ? |
| </WRAP> | </WRAP> |
| |
| Fichier : `td3/modules/kms/main.tf` | Fichier : `td3/modules/kms/variables.tf` |
| <sxh js> | <sxh js> |
| variable "account_id" { | variable "key_alias" { |
| type = string | type = string |
| description = "ID du compte AWS courant" | description = "Alias de la clé KMS" |
| | default = "alias/td3-key" |
| } | } |
| |
| variable "region" { | variable "account_id" { |
| type = string | type = string |
| description = "Region AWS courante" | description = "ID du compte AWS courant" |
| } | } |
| | </sxh> |
| | |
| | Fichier : `td3/modules/kms/main.tf` |
| | <sxh js> |
| | data "aws_caller_identity" "current" {} |
| |
| resource "aws_kms_key" "td3" { | resource "aws_kms_key" "main" { |
| description = "Cle KMS TD3 – chiffrement RDS et S3" | description = "Clé KMS TD3 – chiffrement RDS et S3" |
| deletion_window_in_days = 7 | deletion_window_in_days = 10 |
| enable_key_rotation = true | enable_key_rotation = true |
| |
| Statement = [ | Statement = [ |
| { | { |
| Sid = "Acces administrateur au compte" | Sid = "EnableRootAccess" |
| Effect = "Allow" | Effect = "Allow" |
| Principal = { | Principal = { |
| ] | ] |
| }) | }) |
| | |
| | tags = { |
| | Name = "td3-kms-key" |
| | TD = "td3" |
| | } |
| } | } |
| |
| resource "aws_kms_alias" "td3" { | resource "aws_kms_alias" "main" { |
| name = "alias/td3-key" | name = var.key_alias |
| target_key_id = aws_kms_key.td3.id | target_key_id = aws_kms_key.main.key_id |
| } | } |
| | </sxh> |
| |
| | Fichier : `td3/modules/kms/outputs.tf` |
| | <sxh js> |
| output "key_arn" { | output "key_arn" { |
| value = aws_kms_key.td3.arn | value = aws_kms_key.main.arn |
| description = "ARN de la cle KMS TD3" | description = "ARN de la clé KMS" |
| } | } |
| |
| output "key_id" { | output "key_id" { |
| value = aws_kms_key.td3.id | value = aws_kms_key.main.key_id |
| description = "ID de la cle KMS TD3" | description = "ID de la clé KMS" |
| } | } |
| </sxh> | </sxh> |
| |
| <WRAP round question> | <WRAP round question> |
| Le paramètre `deletion_window_in_days = 7` est défini sur la clé KMS. | Le paramètre `enable_key_rotation = true` active la rotation automatique de la clé. |
| |
| Que se passe-t-il si on supprime cette clé alors que des données RDS ou S3 sont chiffrées avec elle ? | Que signifie concrètement la rotation d'une clé KMS ? |
| |
| Pourquoi AWS impose-t-il un délai minimum avant la suppression effective d'une clé ? | Les données chiffrées avec l'ancienne version de la clé deviennent-elles inaccessibles après rotation ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | <WRAP round question> |
| Le paramètre `enable_key_rotation = true` est activé. | La policy KMS donne accès à `arn:aws:iam::ACCOUNT_ID:root`. |
| |
| Que signifie la rotation d'une clé KMS concrètement ? | Pourquoi cette entrée est-elle obligatoire dans une policy KMS ? |
| |
| La rotation de la clé KMS invalide-t-elle les données déjà chiffrées avec l'ancienne version ? | Que se passe-t-il si elle est absente ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | ===== 7. Création du module storage ===== |
| La key policy autorise `kms:*` pour le root du compte. | |
| |
| Pourquoi cette règle est-elle nécessaire dans une key policy ? | Objectif : déployer une instance RDS chiffrée et un bucket S3 chiffré en utilisant la clé KMS du module précédent. |
| |
| Que se passerait-il si on supprimait cette règle et qu'aucun autre principal n'était autorisé ? | Fichier : `td3/modules/storage/variables.tf` |
| </WRAP> | |
| | |
| ===== 6. Chiffrement de la base de données RDS ===== | |
| | |
| Objectif : activer le chiffrement au repos sur RDS avec la CMK. | |
| | |
| <WRAP round todo> | |
| Modifier `td2/network/instances.tf` pour activer le chiffrement et utiliser le secret Secrets Manager. | |
| </WRAP> | |
| | |
| Fichier : `td2/network/instances.tf` | |
| <sxh js> | <sxh js> |
| # Recuperation du secret cree dans le module TD3 | variable "kms_key_arn" { |
| data "aws_secretsmanager_secret_version" "db_credentials" { | type = string |
| secret_id = "td3/db/credentials" | description = "ARN de la clé KMS utilisée pour le chiffrement" |
| | } |
| |
| depends_on = [ | variable "db_username" { |
| aws_secretsmanager_secret_version.db_credentials | type = string |
| ] | description = "Nom d'utilisateur transmis depuis Secrets Manager" |
| } | } |
| |
| locals { | variable "db_password" { |
| db_secret = jsondecode( | type = string |
| data.aws_secretsmanager_secret_version.db_credentials.secret_string | description = "Mot de passe transmis depuis Secrets Manager" |
| ) | sensitive = true |
| } | } |
| |
| resource "aws_instance" "backend" { | variable "subnet_ids" { |
| ami = "ami-0f61de2873e29e866" | type = list(string) |
| instance_type = "t2.micro" | description = "Liste des IDs de subnets privés récupérés depuis TD2" |
| subnet_id = aws_subnet.private.id | } |
| vpc_security_group_ids = [aws_security_group.backend_sg.id] | |
| |
| user_data = <<-EOF | variable "security_group_id" { |
| #!/bin/bash | type = string |
| yum install -y python3 | description = "ID du Security Group RDS récupéré depuis TD2" |
| python3 -m http.server 80 & | |
| EOF | |
| | |
| tags = { | |
| Name = "td2-backend" | |
| } | |
| } | } |
| |
| resource "aws_db_instance" "db" { | variable "bucket_name" { |
| identifier = "td3-db" | type = string |
| engine = "postgres" | description = "Nom du bucket S3 applicatif" |
| engine_version = "15" | |
| instance_class = "db.t3.micro" | |
| allocated_storage = 20 | |
| | |
| username = local.db_secret.username | |
| password = local.db_secret.password | |
| | |
| storage_encrypted = true | |
| kms_key_id = var.kms_key_arn | |
| | |
| db_subnet_group_name = aws_db_subnet_group.main.name | |
| vpc_security_group_ids = [aws_security_group.db_sg.id] | |
| skip_final_snapshot = true | |
| | |
| tags = { | |
| Name = "td3-db" | |
| } | |
| } | } |
| | </sxh> |
| |
| | Fichier : `td3/modules/storage/main.tf` |
| | <sxh js> |
| resource "aws_db_subnet_group" "main" { | resource "aws_db_subnet_group" "main" { |
| name = "td3-db-subnet-group" | name = "td3-db-subnet-group" |
| subnet_ids = [aws_subnet.private.id, aws_subnet.public_b.id] | subnet_ids = var.subnet_ids |
| |
| tags = { | tags = { |
| Name = "td3-db-subnet-group" | Name = "td3-db-subnet-group" |
| | TD = "td3" |
| } | } |
| } | } |
| |
| variable "kms_key_arn" { | resource "aws_db_instance" "db" { |
| type = string | identifier = "td3-db" |
| description = "ARN de la cle KMS utilisee pour le chiffrement RDS" | engine = "postgres" |
| } | engine_version = "15" |
| </sxh> | instance_class = "db.t3.micro" |
| | allocated_storage = 20 |
| |
| <WRAP round question> | username = var.db_username |
| Le chiffrement `storage_encrypted = true` protège les données au repos. | password = var.db_password |
| |
| Quel mécanisme protège les données en transit entre le backend et RDS ? | db_subnet_group_name = aws_db_subnet_group.main.name |
| | vpc_security_group_ids = [var.security_group_id] |
| |
| Le chiffrement au repos protège-t-il contre un accès non autorisé via SQL si les credentials sont compromis ? | storage_encrypted = true |
| </WRAP> | kms_key_id = var.kms_key_arn |
| |
| <WRAP round question> | skip_final_snapshot = true |
| L'identifiant de la base est passé de `td2-db` à `td3-db`. | |
| |
| Que se passe-t-il si on tente de modifier `storage_encrypted` sur une instance RDS existante ? | tags = { |
| | Name = "td3-db" |
| Pourquoi AWS impose-t-il cette contrainte ? | TD = "td3" |
| </WRAP> | } |
| | |
| ===== 7. Problème volontaire – chiffrement RDS immuable ===== | |
| | |
| <WRAP round todo> | |
| Tenter de modifier une instance RDS existante non chiffrée pour activer le chiffrement : | |
| | |
| terraform apply | |
| | |
| Observer le message d'erreur Terraform. | |
| </WRAP> | |
| | |
| <WRAP round question> | |
| Terraform indique qu'il doit détruire et recréer l'instance RDS. | |
| | |
| Pourquoi le chiffrement d'une instance RDS ne peut-il pas être activé à chaud ? | |
| | |
| Quelles précautions prendre en production avant de forcer cette opération ? | |
| </WRAP> | |
| | |
| ===== 8. Création d'un bucket S3 sécurisé ===== | |
| | |
| Objectif : créer un bucket S3 avec chiffrement SSE-KMS et blocage d'accès public. | |
| | |
| <WRAP round todo> | |
| Créer le module storage. | |
| </WRAP> | |
| | |
| Fichier : `td3/modules/storage/main.tf` | |
| <sxh js> | |
| variable "kms_key_arn" { | |
| type = string | |
| description = "ARN de la cle KMS utilisee pour le chiffrement S3" | |
| } | } |
| |
| resource "random_id" "bucket_suffix" { | resource "aws_s3_bucket" "app" { |
| byte_length = 4 | bucket = var.bucket_name |
| } | |
| | |
| resource "aws_s3_bucket" "app_data" { | |
| bucket = "td3-app-data-${random_id.bucket_suffix.hex}" | |
| |
| tags = { | tags = { |
| Name = "td3-app-data" | Name = "td3-s3-app" |
| | TD = "td3" |
| } | } |
| } | } |
| |
| resource "aws_s3_bucket_public_access_block" "app_data" { | resource "aws_s3_bucket_server_side_encryption_configuration" "app" { |
| bucket = aws_s3_bucket.app_data.id | bucket = aws_s3_bucket.app.id |
| | |
| block_public_acls = true | |
| block_public_policy = true | |
| ignore_public_acls = true | |
| restrict_public_buckets = true | |
| } | |
| | |
| resource "aws_s3_bucket_server_side_encryption_configuration" "app_data" { | |
| bucket = aws_s3_bucket.app_data.id | |
| |
| rule { | rule { |
| } | } |
| |
| output "bucket_name" { | resource "aws_s3_bucket_public_access_block" "app" { |
| value = aws_s3_bucket.app_data.id | bucket = aws_s3_bucket.app.id |
| description = "Nom du bucket S3 applicatif" | |
| | block_public_acls = true |
| | block_public_policy = true |
| | ignore_public_acls = true |
| | restrict_public_buckets = true |
| } | } |
| </sxh> | </sxh> |
| |
| <WRAP round question> | <WRAP round question> |
| Dans ce TD la valeur de `sse_algorithm` est `aws:kms`. | Le chiffrement `storage_encrypted = true` sur RDS protège-t-il les données en transit ou au repos ? |
| |
| Dans le TD précédent la valeur était `AES256`. | Quel mécanisme protège les données en transit entre l'application et RDS ? |
| | |
| Quelle est la différence concrète entre ces deux modes de chiffrement S3 ? | |
| | |
| Quel mode offre le plus de contrôle sur la clé de chiffrement ? | |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | <WRAP round question> |
| Le paramètre `bucket_key_enabled = true` est activé. | Le paramètre `bucket_key_enabled = true` est activé sur le bucket S3. |
| |
| Rechercher ce que fait ce paramètre. | Rechercher dans la documentation AWS ce que cette option apporte. |
| |
| Quel impact a-t-il sur les coûts liés à KMS ? | Quel est l'impact sur les coûts et les performances ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | ===== 8. Erreur volontaire – chiffrement impossible ===== |
| Les quatre paramètres de `aws_s3_bucket_public_access_block` sont tous à `true`. | |
| |
| Expliquer ce que bloque concrètement chacun des quatre paramètres. | Objectif : comprendre pourquoi RDS ne peut pas chiffrer une instance existante non chiffrée. |
| |
| Pourquoi ce bloc est-il indépendant du chiffrement ? | Imaginer le scénario suivant : une instance RDS existe déjà en production sans chiffrement, et un ingénieur ajoute `storage_encrypted = true` et `kms_key_id` dans le fichier Terraform, puis applique le changement. |
| </WRAP> | |
| |
| ===== 9. Mise en place du logging avec CloudTrail ===== | <WRAP round question> |
| | Avant de chercher la réponse, réfléchir à ce qui va se passer. |
| |
| Objectif : tracer toutes les actions réalisées sur le compte AWS. | Est-ce que Terraform va modifier l'instance en place ? |
| | |
| | Est-ce que le chiffrement peut être activé sur une instance RDS existante sans interruption ? |
| | |
| | Quelle est la procédure correcte dans ce cas ? |
| | </WRAP> |
| |
| <WRAP round todo> | <WRAP round todo> |
| Créer le module logging. | Rechercher dans la documentation AWS RDS la section relative au chiffrement d'une instance existante. |
| | |
| | Décrire en cinq étapes la procédure à suivre pour migrer une instance non chiffrée vers une instance chiffrée. |
| </WRAP> | </WRAP> |
| |
| Fichier : `td3/modules/logging/main.tf` | ===== 9. Création du module logging ===== |
| | |
| | Objectif : activer CloudTrail pour conserver une trace de toutes les actions réalisées sur le compte AWS. |
| | |
| | Fichier : `td3/modules/logging/variables.tf` |
| <sxh js> | <sxh js> |
| variable "account_id" { | variable "bucket_name" { |
| type = string | type = string |
| description = "ID du compte AWS courant" | description = "Nom du bucket S3 destiné à recevoir les logs CloudTrail" |
| } | } |
| |
| variable "kms_key_arn" { | variable "kms_key_arn" { |
| type = string | type = string |
| description = "ARN de la cle KMS utilisee pour le chiffrement des logs" | description = "ARN de la clé KMS pour chiffrer les logs CloudTrail" |
| } | } |
| |
| resource "random_id" "bucket_suffix" { | variable "account_id" { |
| byte_length = 4 | type = string |
| | description = "ID du compte AWS courant" |
| } | } |
| | </sxh> |
| |
| resource "aws_s3_bucket" "cloudtrail_logs" { | Fichier : `td3/modules/logging/main.tf` |
| bucket = "td3-cloudtrail-${var.account_id}-${random_id.bucket_suffix.hex}" | <sxh js> |
| | resource "aws_s3_bucket" "cloudtrail" { |
| | bucket = var.bucket_name |
| |
| tags = { | tags = { |
| Name = "td3-cloudtrail-logs" | Name = "td3-s3-cloudtrail" |
| | TD = "td3" |
| } | } |
| } | } |
| |
| resource "aws_s3_bucket_public_access_block" "cloudtrail_logs" { | resource "aws_s3_bucket_public_access_block" "cloudtrail" { |
| bucket = aws_s3_bucket.cloudtrail_logs.id | bucket = aws_s3_bucket.cloudtrail.id |
| |
| block_public_acls = true | block_public_acls = true |
| } | } |
| |
| resource "aws_s3_bucket_policy" "cloudtrail_logs" { | resource "aws_s3_bucket_policy" "cloudtrail" { |
| bucket = aws_s3_bucket.cloudtrail_logs.id | bucket = aws_s3_bucket.cloudtrail.id |
| |
| policy = jsonencode({ | policy = jsonencode({ |
| } | } |
| Action = "s3:GetBucketAcl" | Action = "s3:GetBucketAcl" |
| Resource = aws_s3_bucket.cloudtrail_logs.arn | Resource = aws_s3_bucket.cloudtrail.arn |
| }, | }, |
| { | { |
| } | } |
| Action = "s3:PutObject" | Action = "s3:PutObject" |
| Resource = "${aws_s3_bucket.cloudtrail_logs.arn}/AWSLogs/${var.account_id}/*" | Resource = "${aws_s3_bucket.cloudtrail.arn}/AWSLogs/${var.account_id}/*" |
| Condition = { | Condition = { |
| StringEquals = { | StringEquals = { |
| resource "aws_cloudtrail" "main" { | resource "aws_cloudtrail" "main" { |
| name = "td3-cloudtrail" | name = "td3-cloudtrail" |
| s3_bucket_name = aws_s3_bucket.cloudtrail_logs.id | s3_bucket_name = aws_s3_bucket.cloudtrail.id |
| include_global_service_events = true | include_global_service_events = true |
| is_multi_region_trail = false | is_multi_region_trail = false |
| kms_key_id = var.kms_key_arn | kms_key_id = var.kms_key_arn |
| |
| depends_on = [ | tags = { |
| aws_s3_bucket_policy.cloudtrail_logs | Name = "td3-cloudtrail" |
| ] | TD = "td3" |
| | } |
| | |
| | depends_on = [aws_s3_bucket_policy.cloudtrail] |
| } | } |
| | </sxh> |
| |
| | Fichier : `td3/modules/logging/outputs.tf` |
| | <sxh js> |
| output "cloudtrail_arn" { | output "cloudtrail_arn" { |
| value = aws_cloudtrail.main.arn | value = aws_cloudtrail.main.arn |
| description = "ARN du trail CloudTrail" | description = "ARN du trail CloudTrail" |
| | } |
| | |
| | output "cloudtrail_bucket" { |
| | value = aws_s3_bucket.cloudtrail.bucket |
| | description = "Nom du bucket contenant les logs CloudTrail" |
| } | } |
| </sxh> | </sxh> |
| |
| <WRAP round question> | <WRAP round question> |
| La bucket policy pour CloudTrail contient deux statements distincts. | Le paramètre `enable_log_file_validation = true` active la validation de l'intégrité des fichiers de logs. |
| |
| Expliquer le rôle de chacun : | Quel problème cela permet-il de détecter ? |
| * `AWSCloudTrailAclCheck` | |
| * `AWSCloudTrailWrite` | |
| |
| Que se passerait-il si cette policy était absente ? | Quel mécanisme technique AWS utilise-t-il pour garantir cette intégrité ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | <WRAP round question> |
| Le paramètre `enable_log_file_validation = true` est activé. | CloudTrail trace les appels API AWS. |
| |
| Que permet concrètement la validation des fichiers de log ? | Citer trois exemples d'actions tracées par CloudTrail dans le cadre de ce TD. |
| |
| Dans quel scénario cette fonctionnalité est-elle particulièrement utile ? | Citer deux exemples d'actions qui ne sont pas tracées par CloudTrail. |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | ===== 10. Assemblage dans main.tf ===== |
| CloudTrail enregistre les appels API réalisés sur le compte AWS. | |
| |
| Cocher parmi ces actions celles qui sont tracées par CloudTrail : | Objectif : relier tous les modules entre eux et injecter les data sources TD2 dans les modules TD3. |
| * création d'un Security Group | |
| * modification d'une règle IAM | |
| * upload d'un fichier dans S3 | |
| * requête SQL vers RDS | |
| * connexion SSH vers une instance EC2 | |
| * suppression d'un secret Secrets Manager | |
| |
| Justifier les réponses non évidentes. | Fichier : `td3/variables.tf` |
| </WRAP> | <sxh js> |
| | variable "db_password" { |
| | type = string |
| | description = "Mot de passe de la base de données – ne pas committer" |
| | sensitive = true |
| | } |
| |
| ===== 10. Simulation d'un incident ===== | variable "s3_app_bucket_name" { |
| | type = string |
| | description = "Nom du bucket S3 applicatif" |
| | default = "td3-app-bucket-demo" |
| | } |
| |
| Objectif : utiliser CloudTrail pour retracer une action suspecte. | variable "s3_logs_bucket_name" { |
| | type = string |
| | description = "Nom du bucket S3 pour les logs CloudTrail" |
| | default = "td3-cloudtrail-logs-demo" |
| | } |
| | </sxh> |
| |
| <WRAP round todo> | Fichier : `td3/main.tf` — compléter après les data sources |
| Réaliser une action volontairement suspecte dans AWS. | <sxh js> |
| | data "aws_caller_identity" "current" {} |
| |
| Modifier un Security Group existant du TD2 pour ouvrir temporairement le port 22 depuis `0.0.0.0/0` : | module "kms" { |
| | source = "./modules/kms" |
| | account_id = data.aws_caller_identity.current.account_id |
| | } |
| |
| terraform apply -target=module.network | module "secrets" { |
| | source = "./modules/secrets" |
| | db_username = "td3admin" |
| | db_password = var.db_password |
| | } |
| |
| Attendre deux minutes, puis supprimer cette règle : | module "storage" { |
| | source = "./modules/storage" |
| |
| terraform apply | kms_key_arn = module.kms.key_arn |
| </WRAP> | |
| |
| <WRAP round todo> | db_username = module.secrets.db_username |
| Aller dans la console AWS. | db_password = module.secrets.db_password |
| |
| Naviguer vers : CloudTrail > Event history. | subnet_ids = [ |
| | data.aws_subnet.private.id, |
| | data.aws_subnet.private_b.id |
| | ] |
| |
| Rechercher l'événement `AuthorizeSecurityGroupIngress`. | security_group_id = data.aws_security_group.rds.id |
| |
| Identifier : | bucket_name = var.s3_app_bucket_name |
| * l'heure de l'action | } |
| * l'identité qui a réalisé l'action | |
| * les paramètres de la règle ajoutée | module "logging" { |
| </WRAP> | source = "./modules/logging" |
| | bucket_name = var.s3_logs_bucket_name |
| | kms_key_arn = module.kms.key_arn |
| | account_id = data.aws_caller_identity.current.account_id |
| | } |
| | </sxh> |
| |
| <WRAP round question> | <WRAP round question> |
| CloudTrail enregistre l'identité qui a réalisé chaque action. | Le module `secrets` expose `db_username` et `db_password` via ses outputs. |
| |
| Si plusieurs développeurs partagent le même utilisateur IAM, que perd-on en termes de traçabilité ? | Regarder la définition des outputs dans `td3/modules/secrets/outputs.tf`. |
| |
| Quel principe de gestion des identités du TD1 est directement lié à cette problématique ? | Que faut-il ajouter sur l'output `db_password` pour que la valeur ne s'affiche pas dans le terminal lors d'un `terraform output` ? |
| </WRAP> | </WRAP> |
| |
| <WRAP round question> | Fichier : `td3/modules/secrets/outputs.tf` — ajouter les outputs manquants |
| Les logs CloudTrail sont stockés dans S3. | <sxh js> |
| | output "db_username" { |
| | value = var.db_username |
| | description = "Nom d'utilisateur de la base de données" |
| | } |
| |
| Par défaut, combien de temps sont-ils conservés dans CloudTrail Event History ? | output "db_password" { |
| | value = var.db_password |
| | description = "Mot de passe de la base de données" |
| | sensitive = true |
| | } |
| | </sxh> |
| |
| Pourquoi stocker les logs dans S3 permet-il une conservation plus longue ? | ===== 11. Déploiement et vérification ===== |
| </WRAP> | |
| |
| ===== 11. Vérification globale ===== | Fichier : `td3/commandes/terraform.txt` |
| | <sxh bash> |
| | # Se placer dans le dossier td3 |
| | cd tp-aws/td3 |
| |
| <WRAP round todo> | # Initialiser le projet |
| Vérifier les points suivants dans le code Terraform et dans la console AWS : | terraform init |
| |
| * le mot de passe n'apparaît plus dans aucun fichier `.tf` | # Vérifier le plan – inspecter les ressources créées et les data sources résolus |
| * la variable `db_password` est marquée `sensitive = true` | terraform plan -var="db_password=VotreMotDePasse123!" |
| * la base RDS a `storage_encrypted = true` et un `kms_key_id` renseigné | |
| * le bucket S3 applicatif a `sse_algorithm = "aws:kms"` | |
| * le bucket S3 applicatif a les quatre paramètres `public_access_block` à `true` | |
| * CloudTrail est actif et écrit dans son bucket dédié | |
| * la clé KMS a `enable_key_rotation = true` | |
| </WRAP> | |
| |
| ===== Challenge final ===== | # Appliquer |
| | terraform apply -var="db_password=VotreMotDePasse123!" |
| |
| **Axe secrets** | # Vérifier les outputs |
| | terraform output |
| | |
| | # Vérifier le secret dans AWS CLI |
| | aws secretsmanager get-secret-value --secret-id td3/db/credentials --region eu-west-3 |
| | |
| | # Vérifier que le bucket S3 est bien chiffré |
| | aws s3api get-bucket-encryption --bucket td3-app-bucket-demo --region eu-west-3 |
| | |
| | # Vérifier que CloudTrail est actif |
| | aws cloudtrail get-trail-status --name td3-cloudtrail --region eu-west-3 |
| | </sxh> |
| |
| <WRAP round todo> | <WRAP round todo> |
| Répondre aux questions suivantes par écrit : | Après le déploiement, vérifier manuellement dans la console AWS : |
| |
| * Où est stocké physiquement le secret de la base après le TD3 ? | * Secrets Manager : le secret `td3/db/credentials` est visible et déchiffrable |
| * Qui peut lire ce secret dans AWS ? | * KMS : la clé `alias/td3-key` est active avec rotation activée |
| * Que se passe-t-il si le secret Secrets Manager est lui-même chiffré avec la clé KMS TD3 et que cette clé est supprimée ? | * RDS : l'instance `td3-db` indique `Encrypted: Yes` dans ses propriétés |
| * Quelles seraient les trois premières actions à réaliser si le mot de passe de la base était compromis ? | * S3 : le bucket applicatif indique `SSE-KMS` dans ses propriétés de chiffrement |
| | * CloudTrail : le trail `td3-cloudtrail` est actif et envoie des logs dans le bucket |
| </WRAP> | </WRAP> |
| |
| **Axe KMS** | ===== 12. Lecture des logs CloudTrail ===== |
| |
| <WRAP round question> | Objectif : naviguer dans les logs CloudTrail pour retrouver une action spécifique. |
| La clé KMS est utilisée à la fois pour RDS, S3 et CloudTrail. | |
| |
| Quels sont les avantages de centraliser sur une seule clé ? | <WRAP round todo> |
| | Dans la console AWS, ouvrir CloudTrail puis Event History. |
| |
| Quels sont les risques de cette approche ? | Filtrer les événements par type `CreateSecret`. |
| |
| Comment organiserait-on les clés KMS dans un environnement de production avec plusieurs équipes et plusieurs applications ? | Retrouver l'événement correspondant à la création du secret TD3. |
| </WRAP> | |
| |
| **Axe chiffrement** | Identifier les champs suivants dans l'événement : |
| | |
| | * `userIdentity` : qui a réalisé l'action |
| | * `eventTime` : quand |
| | * `sourceIPAddress` : depuis quelle IP |
| | * `requestParameters` : quel secret a été créé |
| | </WRAP> |
| |
| <WRAP round question> | <WRAP round question> |
| Dresser un tableau récapitulatif avec les colonnes suivantes : | Un collègue vous signale qu'une clé KMS a été désactivée à 3h du matin sans explication. |
| |
| * Ressource (RDS, S3, Secrets Manager, CloudTrail) | Quelle requête effectuer dans CloudTrail pour retrouver cet événement ? |
| * Type de chiffrement (au repos, en transit, les deux) | |
| * Algorithme ou mécanisme utilisé | |
| * Clé utilisée (AWS managée ou CMK TD3) | |
| |
| Dans quel cas le chiffrement AES256 de S3 ne suffit-il pas à garantir la confidentialité des données ? | Quels champs de l'événement permettent d'identifier l'auteur de l'action ? |
| </WRAP> | </WRAP> |
| |
| **Axe traçabilité** | ===== Challenge final ===== |
| |
| <WRAP round question> | L'audit de sécurité est terminé. |
| Un stagiaire supprime accidentellement la ressource CloudTrail via `terraform destroy` sur le module logging. | |
| |
| Quelles sont les conséquences immédiates pour la traçabilité du compte ? | Rédiger un rapport de remédiation qui couvre les quatre axes suivants. |
| |
| Comment aurait-on pu protéger cette ressource contre une suppression accidentelle ? | **Axe 1 – Secrets** |
| |
| Rechercher le paramètre Terraform qui permet de protéger une ressource contre la destruction. | Expliquer comment les identifiants RDS sont maintenant gérés. |
| </WRAP> | |
| | Comparer l'état avant TD3 et l'état après TD3. |
| | |
| | Indiquer ce qui resterait à améliorer si l'application backend devait récupérer elle-même le secret. |
| | |
| | **Axe 2 – Clés KMS** |
| | |
| | Décrire le cycle de vie de la clé créée dans ce TD. |
| | |
| | Expliquer ce qui se passe lors de la rotation automatique. |
| | |
| | Indiquer le délai de suppression et pourquoi il est important. |
| | |
| | **Axe 3 – Chiffrement des données** |
| | |
| | Compléter le tableau suivant : |
| | |
| | Fichier : `td3/rapport/chiffrement.md` |
| | <sxh bash> |
| | | Ressource | Type de chiffrement | Clé utilisée | Données protégées | |
| | |------------|---------------------|---------------------|---------------------------| |
| | | RDS | SSE-KMS | CMK alias/td3-key | Données au repos | |
| | | S3 app | SSE-KMS | CMK alias/td3-key | Objets stockés | |
| | | S3 logs | ? | ? | ? | |
| | | Transit RDS| ? | ? | ? | |
| | </sxh> |
| | |
| | **Axe 4 – Traçabilité** |
| | |
| | Décrire ce que CloudTrail trace dans le contexte de ce TD. |
| | |
| | Expliquer pourquoi le bucket de logs est lui-même protégé par une policy stricte. |
| |
| **Axe architecture** | Indiquer comment détecter une tentative de suppression des logs CloudTrail. |
| |
| <WRAP round todo> | <WRAP round todo> |
| Produire un schéma de l'architecture complète en prolongement du schéma réalisé au TD2. | Produire un schéma de l'infrastructure TD3 complète incluant : |
| |
| Le schéma doit inclure : | * toutes les ressources déployées dans TD3 |
| * les composants du TD2 : ALB, backend, Security Groups, subnets | * les ressources récupérées en data sources depuis TD2 |
| * les ajouts du TD3 : Secrets Manager, KMS, RDS chiffré, S3 applicatif, CloudTrail et son bucket | |
| * les flux de données entre chaque composant avec les ports concernés | * les flux de données entre chaque composant avec les ports concernés |
| * une légende distinguant les flux chiffrés et les flux non chiffrés | * une légende distinguant les flux chiffrés et les flux non chiffrés |