Fondation cloud sécurisée avec Terraform et DevSecOps

Pourquoi ce guide : en 90 à 120 minutes, obtenez une feuille de route et des artefacts Terraform pour établir une fondation cloud sécurisée et auditable chez Codolie. Difficulté : intermédiaire/avancé. Orientation : production, sécurité par défaut, observabilité et mesures (MTTD/MTTR).

Objectif
Déployer en IaC (Terraform) et vérifier en CI (OPA/Conftest) une infrastructure cloud couvrant : responsabilité partagée, gouvernance et inventaire, contrôle d’accès (RBAC, MFA, ZTNA), chiffrement (repos/transit, KMS cross-comptes), gestion des secrets, journalisation exhaustive, normalisation SIEM (ECS/CEF), plan IR et KPIs (MTTD/MTTR).

Prérequis
- Accès org-level (AWS Orgs/Azure MG/GCP Folders) et compte “sécurité” dédié.
- Terraform >= 1.6, opa/conftest, git, pipeline CI/CD (GH Actions/GitLab CI).
- IdP SSO (Okta/Entra ID/Google Workspace) + MFA.
- SIEM (Splunk/Elastic/Sentinel), bucket/logs immuable.
- Vault ou Secret Manager cloud.
- Domaine et idéalement ZTNA (Cloudflare/Zscaler) pour accès admin.
Mise en œuvre pas à pas
Étape 1 – Gouvernance et inventaire
Standardiser le tagging, imposer des policies org et automatiser l’inventaire.
- Tags obligatoires :
Owner
,Environment
,DataClass
,CostCenter
. - AWS SCP/Policy Azure/GCP org-policy pour refuser sans tags.
- Inventaire automatique via AWS Config/Azure Resource Graph/GCP Asset Inventory vers compte sécurité.
{
"Version":"2012-10-17","Statement":[{
"Sid":"DenyIfMissingTags","Effect":"Deny","Action":"*","Resource":"*",
"Condition":{"Null":{
"aws:RequestTag/Owner":"true","aws:RequestTag/Environment":"true",
"aws:RequestTag/DataClass":"true","aws:RequestTag/CostCenter":"true"
}}
}]
}
package terraform.s3
deny[msg] {
r := input.resource_changes[_]
r.type=="aws_s3_bucket" && r.change.after.acl in ["public-read","public-read-write"]
msg := sprintf("S3 bucket %s ne doit pas être public", [r.change.after.bucket])
}
Étape 2 – Identité & accès (RBAC, MFA, Zero Trust)
SAML/OIDC vers IdP, MFA obligatoire, rôles éphémères, ZTNA pour consoles/APIs.
resource "aws_iam_role" "dev_readonly" {
name = "DeveloperReadOnly"
assume_role_policy = jsonencode({
Version:"2012-10-17",Statement:[{
Effect:"Allow",Principal:{Federated:"arn:aws:iam::123456789012:oidc-provider/idp"},
Action:"sts:AssumeRoleWithWebIdentity",Condition:{StringEquals:{
"idp:aud":"sts.amazonaws.com"
}}
}]
})
}
resource "aws_iam_role_policy_attachment" "ro" {
role = aws_iam_role.dev_readonly.name
policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}
Pro-tip : iam:MaxSessionDuration
réduit la session, ajouter session policies (ex. aws:PrincipalTag/Environment
).

Étape 3 – Backend Terraform
Centraliser l’état, verrouiller, chiffrer et lier versions providers/modules.
terraform {
required_version = ">=1.6"
required_providers {
aws = { source="hashicorp/aws", version="~>5.0" }
vault = { source="hashicorp/vault", version="~>3.0" }
}
backend "s3" {
bucket = "codolie-terraform-state"
key = "env/terraform.tfstate"
region = "eu-west-1"
encrypt = true
kms_key_id = aws_kms_key.backend.arn
dynamodb_table = "codolie-tfstate-lock"
}
}
Étape 4 – Chiffrement & KMS cross-comptes
Séparer gestion clés/données, rotation, policies strictes avec accès inter-comptes explicites.
data "aws_iam_policy_document" "data_key_policy" {
statement {
sid = "AllowUsageFromAppAccount"
effect = "Allow"
principals{type="AWS" identifiers=["arn:aws:iam::111122223333:role/SecurityRole"]}
actions = ["kms:Decrypt","kms:Encrypt","kms:GenerateDataKey*"]
resources = ["*"]
}
statement {
sid = "FullAdmin"
effect = "Allow"
principals{type="AWS" identifiers=["arn:aws:iam::444455556666:root"]}
actions = ["kms:*"]
resources = ["*"]
}
}
resource "aws_kms_key" "data" {
description = "Clé data Codolie"
enable_key_rotation = true
policy = data.aws_iam_policy_document.data_key_policy.json
}
Vérifiez toujours les kms:Decrypt
pour chaque principal cross-account.
Étape 5 – Gestion des secrets & données sensibles
Jamais de plaintext en IaC : utiliser Vault/Secret Manager et marquer sensitive=true
.
provider "vault" { address = "https://vault.codolie.local" }
resource "vault_generic_secret" "db_creds" {
path = "database/creds/readonly"
}
resource "module" "app" {
source = "./modules/app"
db_pass = vault_generic_secret.db_creds.data["password"]
}
output "db_password" {
value = vault_generic_secret.db_creds.data["password"]
sensitive = true
}
Étape 6 – Journalisation, normalisation & SIEM
Centraliser CloudTrail/Logs, valider, normaliser en ECS/CEF, gérer rétention vs coût.
- AWS CloudTrail org-trail vers bucket chiffré, validation activée.
- Azure Activity Logs/Log Analytics, GCP Cloud Audit + Pub/Sub.
- Firehose/EventHub pour Splunk/Elastic/Sentinel avec mapping ECS (event.action, source.ip) ou CEF.
- Rétention : logs critiques 90 jours hot, 365 jours cold (S3 IA/Glacier).
resource "aws_cloudtrail" "org" {
name = "codolie-org-trail"
is_organization_trail = true
s3_bucket_name = aws_s3_bucket.logs.id
kms_key_id = aws_kms_key.data.arn
include_global_service_events = true
is_multi_region_trail = true
enable_log_file_validation = true
}
// KQL détection bruteforce Azure Sentinel
SecurityEvent
| where EventID==4625
| extend Reason=tostring(parse_json(AdditionalFields).FailureReason)
| where Reason contains "Bad password"
| summarize cnt=count() by Computer, bin(TimeGenerated,5m)
| where cnt>=5
// SPL Splunk
index=security EventCode=4625
| stats count by src_ip,user
| where count>10
Étape 7 – Préventif en CI/CD (Policy-as-Code)
name: security-checks
on: [pull_request]
jobs:
tf_plan:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init && terraform plan -out=tfplan
- run: terraform show -json tfplan > tfplan.json
- uses: instrumenta/conftest-action@v0.3.0
with:
files: tfplan.json
Interdire IP publiques, disques non chiffrés, SG 0.0.0.0/0, etc.
Étape 8 – Réponse à incident & conformité
- Runbooks versionnés (
docs/ir/
) : triage, containment, recovery, postmortem. - Tabletop trimestriel (clé exposée, bucket public, ransomware VM).
- Contrôles continus : Prowler/ScoutSuite/Steampipe pour CIS/GDPR/HIPAA.
# Scan CIS AWS critique
prowler aws --compliance cis_aws --only-critical --output-dir=reports/
Vérification
- Inventaire : 100 % des comptes avec logs et >95 % ressources taggées.
- CI : PR créant S3 public => échec Conftest avec message clair.
- IAM : connexion SSO/MFA, pas de clefs longues (>90 jours).
- Chiffrement : objet créé vérifie SSE=aws:kms et KMS key ARN.
- SIEM : alerte test root sans MFA, MTTD < 5 min, MTTR < 60 min.
Dépannage (pitfalls courants)
- MFA/SSO partiel : forcer
iam:RequireMFA
ou Conditional Access. - SCP trop strict : exceptions ciblées pour services sans tag-on-create.
- CloudTrail bloqué : vérifier BucketPolicy et KMS grants pour
logs.delivery
. - KMS inter-comptes : ajouter
kms:Decrypt/Encrypt
pour principals source. - SIEM overload : filtrer ingeste, retentions différenciées par criticité.
- OPA faux positifs : ajuster règles sur tfplan réel, messages actionnables.
Prochaines étapes
- Classification avancée (Macie, DLP sur
DataClass
). - Rotation dynamique des secrets, transition complète vers Vault/Secret Manager.
- Durcissement réseau : egress control, VPC endpoints, inspection TLS, ZTNA étendu.
- KPI mensuels : MTTD/MTTR, couverture chiffrement, MFA/RBAC, score CIS.
- ROI : prioriser IAM, chiffrement, logs, SIEM, mesurer coûts évités.
- Tests de sécurité dans CI/CD et validation continue en production.
Damien Larquey
Author at Codolie
Passionate about technology, innovation, and sharing knowledge with the developer community.