eadl:bloc4:fm4:td3

TD3 – Gestion des secrets, chiffrement et traçabilité AWS

  • Supprimer les secrets en clair dans une infrastructure Terraform
  • Utiliser AWS Secrets Manager pour stocker des identifiants sensibles
  • Créer et utiliser une clé KMS Customer Managed Key
  • Activer le chiffrement des données au repos sur RDS et S3 avec KMS
  • Comprendre la différence entre chiffrement au repos et en transit
  • Mettre en place une traçabilité avec CloudTrail
  • Identifier une action suspecte via les logs
  • Récupérer des ressources existantes via des data sources AWS

Suite au TD2, l'infrastructure réseau est opérationnelle.

Le VPC est segmenté, l'ALB filtre le trafic entrant, les Security Groups isolent chaque couche.

Un nouvel audit de sécurité interne est réalisé.

Quatre failles critiques sont identifiées malgré les corrections du TD2 :

  • le mot de passe de la base de données est stocké en clair dans `instances.tf`
  • la base de données RDS n'est pas chiffrée au repos
  • le bucket S3 applicatif n'est pas chiffré
  • aucune trace des actions réalisées sur le compte AWS n'est conservée

Ces points constituent des non-conformités bloquantes pour toute certification de sécurité (ISO 27001, SOC 2, RGPD).

Votre mission est de corriger ces failles en prolongeant l'infrastructure du TD2.

  • 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
  • Accès AWS avec droits suffisants : IAM, Secrets Manager, KMS, S3, RDS, CloudTrail
  • Terraform en version 1.5 ou supérieure

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`

tp-aws/
├── td1/                  # IAM – roles, policies
├── td2/                  # Réseau – VPC, subnets, ALB, Security Groups
└── td3/                  # Secrets, KMS, chiffrement, logs
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── modules/
        ├── secrets/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── outputs.tf
        ├── kms/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── outputs.tf
        ├── storage/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── outputs.tf
        └── logging/
            ├── main.tf
            ├── variables.tf
            └── outputs.tf

Objectif : comprendre comment un projet Terraform peut interroger une infrastructure existante sans accéder à son state.

TD3 utilise des data sources AWS natifs.

Ces data sources envoient des requêtes directement à l'API AWS et filtrent les ressources par tags.

Cela suppose que les ressources TD2 ont été correctement taguées.

Pourquoi TD3 ne peut-il pas utiliser directement les outputs du TD2 sans configuration supplémentaire ?

Quelle est la différence entre un data source AWS et un `terraform_remote_state` ?

Fichier : `td3/main.tf`

provider "aws" {
  region = "eu-west-3"
}

# Récupération du VPC créé dans TD2
data "aws_vpc" "main" {
  tags = {
    Name = "td2-vpc"
  }
}

# Récupération du subnet privé créé dans TD2
data "aws_subnet" "private" {
  tags = {
    Name = "td2-private"
  }
}

# Récupération du subnet privé secondaire créé dans TD2
data "aws_subnet" "private_b" {
  tags = {
    Name = "td2-private-b"
  }
}

# Récupération du Security Group RDS créé dans TD2
data "aws_security_group" "rds" {
  tags = {
    Name = "td2-sg-rds"
  }
}

# Récupération du Security Group de l'application créé dans TD2
data "aws_security_group" "app" {
  tags = {
    Name = "td2-sg-app"
  }
}

Vérifier dans le code TD2 que chaque ressource est bien taguée avec les valeurs attendues ci-dessus.

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.

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 ?

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

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
}

Identifier au moins trois problèmes de sécurité dans ce bloc.

Pour chaque problème, expliquer la conséquence concrète en cas de fuite du fichier `.tf` ou du state Terraform.

Ne pas appliquer ce fichier tel quel.

Ne pas modifier le TD2 non plus.

La correction se fait uniquement dans TD3.

Objectif : stocker les identifiants de la base de données dans AWS Secrets Manager.

Fichier : `td3/modules/secrets/variables.tf`

variable "secret_name" {
  type        = string
  description = "Nom du secret dans Secrets Manager"
  default     = "td3/db/credentials"
}

variable "db_username" {
  type        = string
  description = "Nom d'utilisateur de la base de données"
  default     = "td3admin"
}

variable "db_password" {
  type        = string
  description = "Mot de passe de la base de données"
  sensitive   = true
}

Fichier : `td3/modules/secrets/main.tf`

resource "aws_secretsmanager_secret" "db_credentials" {
  name                    = var.secret_name
  recovery_window_in_days = 7

  tags = {
    Name = "td3-secret-db"
    TD   = "td3"
  }
}

resource "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id

  secret_string = jsonencode({
    username = var.db_username
    password = var.db_password
  })
}

Fichier : `td3/modules/secrets/outputs.tf`

output "secret_arn" {
  value       = aws_secretsmanager_secret.db_credentials.arn
  description = "ARN du secret contenant les identifiants de la base de données"
}

output "secret_name" {
  value       = aws_secretsmanager_secret.db_credentials.name
  description = "Nom du secret dans Secrets Manager"
}

Le paramètre `sensitive = true` sur la variable `db_password` masque la valeur dans les logs Terraform.

Cela signifie-t-il que le mot de passe est chiffré dans le state Terraform ?

Où le mot de passe est-il stocké en clair malgré ce paramètre ?

Le paramètre `recovery_window_in_days = 7` signifie que le secret n'est pas supprimé immédiatement.

Pourquoi AWS impose-t-il cette fenêtre de récupération par défaut ?

Comment forcer la suppression immédiate si nécessaire, et dans quel cas cela est-il risqué ?

Objectif : comprendre ce qui se passe quand une application tente de lire un secret inexistant.

Voici un data source intentionnellement incorrect.

Fichier : `td3/debug/wrong_secret.tf` — ne pas appliquer

data "aws_secretsmanager_secret" "db_credentials" {
  name = "td3/db/credential"
}

Lire le code ci-dessus sans l'appliquer.

Identifier l'erreur sans regarder la réponse ci-dessous.

Écrire la correction avant de lire la suite.

Le nom du secret dans Secrets Manager est `td3/db/credentials` avec un `s` final.

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`

Error: reading Secrets Manager Secret: ResourceNotFoundException:
Secrets Manager can't find the specified secret.

Ce type d'erreur peut-il survenir en production alors que le secret existe bien ?

Citer au moins deux situations réelles où cette erreur peut apparaître même si le nom est correct.

Objectif : utiliser le secret Secrets Manager dans la configuration RDS plutôt que des valeurs en clair.

Fichier : `td3/modules/secrets/main.tf` — ajouter après les ressources existantes

data "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id

  depends_on = [aws_secretsmanager_secret_version.db_credentials]
}

locals {
  db_secret = jsondecode(
    data.aws_secretsmanager_secret_version.db_credentials.secret_string
  )
}

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 ?

Objectif : créer une clé de chiffrement gérée par le client (Customer Managed Key) pour chiffrer les données au repos.

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 ?

Fichier : `td3/modules/kms/variables.tf`

variable "key_alias" {
  type        = string
  description = "Alias de la clé KMS"
  default     = "alias/td3-key"
}

variable "account_id" {
  type        = string
  description = "ID du compte AWS courant"
}

Fichier : `td3/modules/kms/main.tf`

data "aws_caller_identity" "current" {}

resource "aws_kms_key" "main" {
  description             = "Clé KMS TD3 – chiffrement RDS et S3"
  deletion_window_in_days = 10
  enable_key_rotation     = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EnableRootAccess"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${var.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      }
    ]
  })

  tags = {
    Name = "td3-kms-key"
    TD   = "td3"
  }
}

resource "aws_kms_alias" "main" {
  name          = var.key_alias
  target_key_id = aws_kms_key.main.key_id
}

Fichier : `td3/modules/kms/outputs.tf`

output "key_arn" {
  value       = aws_kms_key.main.arn
  description = "ARN de la clé KMS"
}

output "key_id" {
  value       = aws_kms_key.main.key_id
  description = "ID de la clé KMS"
}

Le paramètre `enable_key_rotation = true` active la rotation automatique de la clé.

Que signifie concrètement la rotation d'une clé KMS ?

Les données chiffrées avec l'ancienne version de la clé deviennent-elles inaccessibles après rotation ?

La policy KMS donne accès à `arn:aws:iam::ACCOUNT_ID:root`.

Pourquoi cette entrée est-elle obligatoire dans une policy KMS ?

Que se passe-t-il si elle est absente ?

Objectif : déployer une instance RDS chiffrée et un bucket S3 chiffré en utilisant la clé KMS du module précédent.

Fichier : `td3/modules/storage/variables.tf`

variable "kms_key_arn" {
  type        = string
  description = "ARN de la clé KMS utilisée pour le chiffrement"
}

variable "db_username" {
  type        = string
  description = "Nom d'utilisateur transmis depuis Secrets Manager"
}

variable "db_password" {
  type        = string
  description = "Mot de passe transmis depuis Secrets Manager"
  sensitive   = true
}

variable "subnet_ids" {
  type        = list(string)
  description = "Liste des IDs de subnets privés récupérés depuis TD2"
}

variable "security_group_id" {
  type        = string
  description = "ID du Security Group RDS récupéré depuis TD2"
}

variable "bucket_name" {
  type        = string
  description = "Nom du bucket S3 applicatif"
}

Fichier : `td3/modules/storage/main.tf`

resource "aws_db_subnet_group" "main" {
  name       = "td3-db-subnet-group"
  subnet_ids = var.subnet_ids

  tags = {
    Name = "td3-db-subnet-group"
    TD   = "td3"
  }
}

resource "aws_db_instance" "db" {
  identifier        = "td3-db"
  engine            = "postgres"
  engine_version    = "15"
  instance_class    = "db.t3.micro"
  allocated_storage = 20

  username = var.db_username
  password = var.db_password

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [var.security_group_id]

  storage_encrypted = true
  kms_key_id        = var.kms_key_arn

  skip_final_snapshot = true

  tags = {
    Name = "td3-db"
    TD   = "td3"
  }
}

resource "aws_s3_bucket" "app" {
  bucket = var.bucket_name

  tags = {
    Name = "td3-s3-app"
    TD   = "td3"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "app" {
  bucket = aws_s3_bucket.app.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = var.kms_key_arn
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_public_access_block" "app" {
  bucket = aws_s3_bucket.app.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Le chiffrement `storage_encrypted = true` sur RDS protège-t-il les données en transit ou au repos ?

Quel mécanisme protège les données en transit entre l'application et RDS ?

Le paramètre `bucket_key_enabled = true` est activé sur le bucket S3.

Rechercher dans la documentation AWS ce que cette option apporte.

Quel est l'impact sur les coûts et les performances ?

Objectif : comprendre pourquoi RDS ne peut pas chiffrer une instance existante non chiffrée.

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.

Avant de chercher la réponse, réfléchir à ce qui va se passer.

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 ?

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.

Objectif : activer CloudTrail pour conserver une trace de toutes les actions réalisées sur le compte AWS.

Fichier : `td3/modules/logging/variables.tf`

variable "bucket_name" {
  type        = string
  description = "Nom du bucket S3 destiné à recevoir les logs CloudTrail"
}

variable "kms_key_arn" {
  type        = string
  description = "ARN de la clé KMS pour chiffrer les logs CloudTrail"
}

variable "account_id" {
  type        = string
  description = "ID du compte AWS courant"
}

Fichier : `td3/modules/logging/main.tf`

resource "aws_s3_bucket" "cloudtrail" {
  bucket = var.bucket_name

  tags = {
    Name = "td3-s3-cloudtrail"
    TD   = "td3"
  }
}

resource "aws_s3_bucket_public_access_block" "cloudtrail" {
  bucket = aws_s3_bucket.cloudtrail.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_policy" "cloudtrail" {
  bucket = aws_s3_bucket.cloudtrail.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AWSCloudTrailAclCheck"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:GetBucketAcl"
        Resource = aws_s3_bucket.cloudtrail.arn
      },
      {
        Sid    = "AWSCloudTrailWrite"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:PutObject"
        Resource = "${aws_s3_bucket.cloudtrail.arn}/AWSLogs/${var.account_id}/*"
        Condition = {
          StringEquals = {
            "s3:x-amz-acl" = "bucket-owner-full-control"
          }
        }
      }
    ]
  })
}

resource "aws_cloudtrail" "main" {
  name                          = "td3-cloudtrail"
  s3_bucket_name                = aws_s3_bucket.cloudtrail.id
  include_global_service_events = true
  is_multi_region_trail         = false
  enable_log_file_validation    = true
  kms_key_id                    = var.kms_key_arn

  tags = {
    Name = "td3-cloudtrail"
    TD   = "td3"
  }

  depends_on = [aws_s3_bucket_policy.cloudtrail]
}

Fichier : `td3/modules/logging/outputs.tf`

output "cloudtrail_arn" {
  value       = aws_cloudtrail.main.arn
  description = "ARN du trail CloudTrail"
}

output "cloudtrail_bucket" {
  value       = aws_s3_bucket.cloudtrail.bucket
  description = "Nom du bucket contenant les logs CloudTrail"
}

Le paramètre `enable_log_file_validation = true` active la validation de l'intégrité des fichiers de logs.

Quel problème cela permet-il de détecter ?

Quel mécanisme technique AWS utilise-t-il pour garantir cette intégrité ?

CloudTrail trace les appels API AWS.

Citer trois exemples d'actions tracées par CloudTrail dans le cadre de ce TD.

Citer deux exemples d'actions qui ne sont pas tracées par CloudTrail.

Objectif : relier tous les modules entre eux et injecter les data sources TD2 dans les modules TD3.

Fichier : `td3/variables.tf`

variable "db_password" {
  type        = string
  description = "Mot de passe de la base de données – ne pas committer"
  sensitive   = true
}

variable "s3_app_bucket_name" {
  type        = string
  description = "Nom du bucket S3 applicatif"
  default     = "td3-app-bucket-demo"
}

variable "s3_logs_bucket_name" {
  type        = string
  description = "Nom du bucket S3 pour les logs CloudTrail"
  default     = "td3-cloudtrail-logs-demo"
}

Fichier : `td3/main.tf` — compléter après les data sources

data "aws_caller_identity" "current" {}

module "kms" {
  source     = "./modules/kms"
  account_id = data.aws_caller_identity.current.account_id
}

module "secrets" {
  source      = "./modules/secrets"
  db_username = "td3admin"
  db_password = var.db_password
}

module "storage" {
  source = "./modules/storage"

  kms_key_arn = module.kms.key_arn

  db_username = module.secrets.db_username
  db_password = module.secrets.db_password

  subnet_ids = [
    data.aws_subnet.private.id,
    data.aws_subnet.private_b.id
  ]

  security_group_id = data.aws_security_group.rds.id

  bucket_name = var.s3_app_bucket_name
}

module "logging" {
  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
}

Le module `secrets` expose `db_username` et `db_password` via ses outputs.

Regarder la définition des outputs dans `td3/modules/secrets/outputs.tf`.

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` ?

Fichier : `td3/modules/secrets/outputs.tf` — ajouter les outputs manquants

output "db_username" {
  value       = var.db_username
  description = "Nom d'utilisateur de la base de données"
}

output "db_password" {
  value       = var.db_password
  description = "Mot de passe de la base de données"
  sensitive   = true
}

Fichier : `td3/commandes/terraform.txt`

# Se placer dans le dossier td3
cd tp-aws/td3

# Initialiser le projet
terraform init

# Vérifier le plan – inspecter les ressources créées et les data sources résolus
terraform plan -var="db_password=VotreMotDePasse123!"

# Appliquer
terraform apply -var="db_password=VotreMotDePasse123!"

# 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

Après le déploiement, vérifier manuellement dans la console AWS :

  • Secrets Manager : le secret `td3/db/credentials` est visible et déchiffrable
  • KMS : la clé `alias/td3-key` est active avec rotation activée
  • RDS : l'instance `td3-db` indique `Encrypted: Yes` dans ses propriétés
  • 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

Objectif : naviguer dans les logs CloudTrail pour retrouver une action spécifique.

Dans la console AWS, ouvrir CloudTrail puis Event History.

Filtrer les événements par type `CreateSecret`.

Retrouver l'événement correspondant à la création du secret TD3.

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éé

Un collègue vous signale qu'une clé KMS a été désactivée à 3h du matin sans explication.

Quelle requête effectuer dans CloudTrail pour retrouver cet événement ?

Quels champs de l'événement permettent d'identifier l'auteur de l'action ?

L'audit de sécurité est terminé.

Rédiger un rapport de remédiation qui couvre les quatre axes suivants.

Axe 1 – Secrets

Expliquer comment les identifiants RDS sont maintenant gérés.

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`

| 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| ?                   | ?                   | ?                         |

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.

Indiquer comment détecter une tentative de suppression des logs CloudTrail.

Produire un schéma de l'infrastructure TD3 complète incluant :

  • toutes les ressources déployées dans TD3
  • les ressources récupérées en data sources depuis TD2
  • 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

Rotation automatique des secrets

Rechercher dans la documentation AWS comment activer la rotation automatique d'un secret Secrets Manager pour une base PostgreSQL.

Identifier l'ARN de la Lambda de rotation fournie par AWS pour PostgreSQL dans la région `eu-west-3`.

Ajouter la rotation dans le module secrets.

Fichier : `td3/modules/secrets/main.tf` — ajouter après la ressource `aws_secretsmanager_secret_version`

variable "rotation_lambda_arn" {
  type        = string
  description = "ARN de la Lambda de rotation fournie par AWS pour PostgreSQL"
  default     = ""
}

resource "aws_secretsmanager_secret_rotation" "db_credentials" {
  count               = var.rotation_lambda_arn != "" ? 1 : 0
  secret_id           = aws_secretsmanager_secret.db_credentials.id
  rotation_lambda_arn = var.rotation_lambda_arn

  rotation_rules {
    automatically_after_days = 30
  }
}

Le bloc `count = var.rotation_lambda_arn != “” ? 1 : 0` est utilisé ici.

Que signifie ce pattern dans Terraform ?

Pourquoi est-il utile de rendre la rotation optionnelle plutôt qu'obligatoire dans ce module ?

Après une rotation automatique, l'application backend doit récupérer le nouveau mot de passe.

Si l'application a mis le mot de passe en cache au démarrage, que se passe-t-il après une rotation ?

Comment une application bien conçue doit-elle récupérer les secrets pour éviter ce problème ?

  • eadl/bloc4/fm4/td3.txt
  • Dernière modification : il y a 4 jours
  • de jcheron