15. IAM cloud et posture de sécurité (CSPM)#
La gestion des identités et des accès (IAM) est la pierre angulaire de la sécurité cloud. Une mauvaise configuration IAM est la cause la plus fréquente des incidents cloud majeurs. Ce chapitre explore les modèles IAM des trois grands cloud providers et les outils CSPM (Cloud Security Posture Management) permettant d’évaluer et d’améliorer la posture de sécurité.
IAM AWS : modèle et composants#
Composants fondamentaux#
AWS IAM repose sur quatre entités principales :
Entité |
Description |
Cas d’usage |
|---|---|---|
Utilisateur IAM |
Identité permanente avec credentials |
Comptes humains (à éviter en production) |
Groupe |
Collection d’utilisateurs |
Organisation des permissions humaines |
Rôle IAM |
Identité temporaire assumable |
Applications, services AWS, cross-account |
Politique IAM |
Document JSON définissant les permissions |
Attachée à tous les types d’entités |
Types de politiques#
Identity-based policies : attachées à un utilisateur, groupe ou rôle. Définissent ce que l’identité peut faire.
Resource-based policies : attachées à une ressource (S3 bucket, KMS key, SQS queue). Définissent qui peut accéder à cette ressource.
Service Control Policies (SCP) : appliquées au niveau AWS Organizations. Définissent le périmètre maximum des permissions dans un compte ou une unité organisationnelle. Elles ne donnent pas de permissions, elles les bornent.
Permission Boundaries : périmètre maximum des permissions accordables par un rôle ou utilisateur. Mécanisme de délégation sécurisée.
Structure d’une politique AWS#
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadProduction",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::prod-data-bucket",
"arn:aws:s3:::prod-data-bucket/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "eu-west-1"
},
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
},
{
"Sid": "DenyDeleteInProduction",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::prod-data-bucket/*"
}
]
}
Logique d’évaluation AWS IAM#
L’évaluation des politiques suit un ordre précis :
Deny explicite : tout
Effect: Denyl’emporte sur tout.SCP : si aucun SCP n’autorise l’action, elle est refusée.
Permission Boundary : si défini, restreint les permissions effectives.
Allow explicite : l’action doit être explicitement autorisée.
Deny implicite par défaut : tout ce qui n’est pas autorisé est refusé.
Règle d’or AWS IAM
Un Deny explicite dans n’importe quelle politique applicable annule toujours un Allow, quelle que soit la source. C’est le mécanisme qui rend les SCP incontournables pour les politiques organisationnelles.
IAM GCP : bindings et Workload Identity#
Modèle IAM GCP#
GCP IAM repose sur le triplet : WHO (principal) → WHAT (rôle) → WHICH (ressource).
# Binding IAM GCP
bindings:
- role: roles/storage.objectViewer
members:
- serviceAccount:data-pipeline@myproject.iam.gserviceaccount.com
- user:analyst@company.com
condition:
title: "Accès business hours uniquement"
expression: >
request.time.getDayOfWeek("Europe/Paris") >= 1 &&
request.time.getDayOfWeek("Europe/Paris") <= 5
Service Accounts GCP#
Les Service Accounts GCP sont à la fois une identité (principal) et une ressource. Ils disposent de clés JSON (à éviter) ou utilisent la Workload Identity Federation pour s’authentifier sans credentials statiques.
Workload Identity Federation GCP#
La Workload Identity Federation permet à des workloads externes (GitHub Actions, AWS, Azure AD) d’obtenir des credentials GCP sans clés de service :
# Configurer un pool Workload Identity pour GitHub Actions
gcloud iam workload-identity-pools create "github-pool" \
--project="my-project" \
--location="global" \
--display-name="GitHub Actions Pool"
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="my-project" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
IAM Azure : RBAC et Managed Identity#
Modèle RBAC Azure#
Azure RBAC s’articule autour de scope (hiérarchie de ressources) et de role assignments :
Management Group
└── Subscription
└── Resource Group
└── Resource (VM, Storage, etc.)
Chaque rôle est assigné à un principal (utilisateur, groupe, service principal, Managed Identity) sur un scope donné.
Managed Identity#
Les Managed Identity (System-assigned ou User-assigned) permettent aux services Azure de s’authentifier à d’autres services sans credentials :
# Assigner une Managed Identity à une VM Azure
az vm identity assign --name myVM --resource-group myRG
# Assigner un rôle RBAC à cette identité
az role assignment create \
--assignee <managed-identity-object-id> \
--role "Storage Blob Data Reader" \
--scope /subscriptions/<sub-id>/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/myaccount
Conditional Access (Azure AD)#
Conditional Access applique des politiques d’accès conditionnel (MFA requis, localisation géographique, état de conformité de l’appareil) avant d’accorder l’accès aux ressources.
IRSA et Workload Identity : éliminer les credentials statiques#
IRSA (IAM Roles for Service Accounts) sur AWS EKS#
IRSA permet à des pods Kubernetes d’assumer des rôles IAM AWS via OIDC, sans secrets statiques :
# ServiceAccount annotée avec le rôle IAM
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-reader
namespace: data-pipeline
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/S3ReaderRole
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub":
"system:serviceaccount:data-pipeline:s3-reader"
}
}
}]
}
Principe zero credentials statiques
IRSA, Workload Identity GCP et Managed Identity Azure permettent d’éliminer complètement les credentials statiques (clés d’accès AWS, clés JSON GCP, secrets Azure AD) dans les workloads Kubernetes. Les tokens sont temporaires, automatiquement renouvelés et liés à l’identité Kubernetes du pod.
CSPM : évaluation de la posture de sécurité cloud#
Le CSPM automatise l’audit des configurations cloud contre des référentiels (CIS Benchmarks, SOC 2, ISO 27001, RGPD) et détecte les dérives.
Prowler (AWS/GCP/Azure)#
Prowler est un outil open source d’audit multi-cloud :
# Audit AWS avec Prowler
prowler aws --compliance cis_1.4_aws
# Audit ciblé sur IAM
prowler aws --service iam --severity critical,high
# Sortie HTML
prowler aws --output-formats html --output-directory ./results
ScoutSuite (NCC Group)#
ScoutSuite génère un rapport HTML interactif de la posture de sécurité cloud :
# Audit AWS
scout aws --report-dir ./scout-report
# Audit GCP
scout gcp --project myproject --report-dir ./scout-report
AWS Security Hub et Security Command Center GCP#
Ces services natifs agrègent les findings de multiples sources (Prowler, GuardDuty, Inspector, Macie pour AWS) dans une console unifiée avec scoring de sécurité.
Cloud Threat Detection#
AWS GuardDuty#
GuardDuty analyse les logs CloudTrail, VPC Flow Logs et DNS pour détecter des comportements suspects (credential exfiltration, mining de cryptomonnaies, reconnaissance d’API).
Security Command Center GCP#
SCC détecte les misconfiguration et les menaces actives (Event Threat Detection analyse les logs d’audit GCP en temps réel).
Microsoft Defender for Cloud#
Defender for Cloud unifie CSPM et protection des workloads (VMs, conteneurs, bases de données) avec des recommandations de hardening et un Secure Score.
IAM Access Analyzer et recommandations#
IAM Access Analyzer (AWS)#
IAM Access Analyzer identifie les ressources accessibles depuis l’extérieur du compte (S3, KMS, SQS, Lambda) et les permissions excessives :
# Lister les findings Access Analyzer
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:eu-west-1:123456789:analyzer/myanalyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}'
IAM Recommender (GCP)#
Google Cloud IAM Recommender utilise le machine learning pour identifier les permissions accordées mais non utilisées depuis 90 jours et proposer des rôles moins permissifs.
# Lister les recommandations IAM pour un projet
gcloud recommender recommendations list \
--project=myproject \
--location=global \
--recommender=google.iam.policy.Recommender
Visualisations#
Simulation du moteur d’évaluation de politiques IAM AWS#
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Statement:
effect: str # "Allow" ou "Deny"
actions: list
resources: list
conditions: dict = field(default_factory=dict)
sid: str = ""
@dataclass
class Policy:
name: str
policy_type: str # "identity", "resource", "scp", "boundary"
statements: list
def match_pattern(pattern: str, value: str) -> bool:
"""Correspondance simple avec wildcard *."""
if pattern == "*":
return True
if pattern.endswith("*"):
return value.startswith(pattern[:-1])
return pattern == value
def eval_conditions(conditions: dict, context: dict) -> bool:
"""Évaluation simplifiée des conditions IAM."""
for operator, kv_pairs in conditions.items():
for key, expected in kv_pairs.items():
actual = context.get(key, "")
if operator == "StringEquals" and actual != expected:
return False
if operator == "Bool" and str(actual).lower() != str(expected).lower():
return False
return True
def evaluate_request(action: str, resource: str, context: dict,
policies: list, scps: list = None) -> dict:
"""
Évalue si une requête IAM est autorisée.
Implémente la logique AWS : Deny explicite > SCP > Allow.
"""
result = {"action": action, "resource": resource,
"decision": "DENY (implicite)", "raison": "", "policies_evaluées": []}
# 1. Vérifier les Deny explicites dans toutes les politiques
for policy in policies:
for stmt in policy.statements:
if stmt.effect != "Deny":
continue
action_match = any(match_pattern(a, action) for a in stmt.actions)
resource_match = any(match_pattern(r, resource) for r in stmt.resources)
cond_match = eval_conditions(stmt.conditions, context)
if action_match and resource_match and cond_match:
result["decision"] = "DENY (explicite)"
result["raison"] = f"Denied by {policy.name} / {stmt.sid}"
return result
# 2. Vérifier les SCPs si présents
if scps:
scp_allows = False
for scp in scps:
for stmt in scp.statements:
if stmt.effect != "Allow":
continue
if (any(match_pattern(a, action) for a in stmt.actions) and
any(match_pattern(r, resource) for r in stmt.resources)):
scp_allows = True
break
if not scp_allows:
result["decision"] = "DENY (SCP)"
result["raison"] = "Action non autorisée par les SCPs de l'organisation"
return result
# 3. Chercher un Allow explicite
for policy in policies:
if policy.policy_type == "scp":
continue
for stmt in policy.statements:
if stmt.effect != "Allow":
continue
action_match = any(match_pattern(a, action) for a in stmt.actions)
resource_match = any(match_pattern(r, resource) for r in stmt.resources)
cond_match = eval_conditions(stmt.conditions, context)
if action_match and resource_match and cond_match:
result["decision"] = "ALLOW"
result["raison"] = f"Allowed by {policy.name} / {stmt.sid}"
return result
result["raison"] = "Aucun Allow explicite trouvé"
return result
# Définition des politiques de test
policies = [
Policy("DeveloperPolicy", "identity", [
Statement("Allow", ["s3:GetObject", "s3:ListBucket"],
["arn:aws:s3:::dev-bucket", "arn:aws:s3:::dev-bucket/*"],
sid="AllowS3Dev"),
Statement("Allow", ["ec2:Describe*"],
["*"], sid="AllowEC2Read"),
]),
Policy("ProductionDenyPolicy", "identity", [
Statement("Deny", ["s3:DeleteObject"],
["arn:aws:s3:::prod-bucket/*"],
sid="DenyProdDelete"),
]),
]
scp_prod = [
Policy("SCPRestrictRegions", "scp", [
Statement("Allow", ["s3:*", "ec2:*"],
["*"],
conditions={"StringEquals": {"aws:RequestedRegion": "eu-west-1"}},
sid="AllowEUWest1Only"),
]),
]
# Scénarios de test
requetes = [
("s3:GetObject", "arn:aws:s3:::dev-bucket/data.csv", {"aws:RequestedRegion": "eu-west-1"}, policies, scp_prod),
("s3:DeleteObject", "arn:aws:s3:::prod-bucket/data.csv", {"aws:RequestedRegion": "eu-west-1"}, policies, scp_prod),
("s3:GetObject", "arn:aws:s3:::dev-bucket/data.csv", {"aws:RequestedRegion": "us-east-1"}, policies, scp_prod),
("ec2:DescribeInstances", "*", {"aws:RequestedRegion": "eu-west-1"}, policies, scp_prod),
("iam:CreateUser", "*", {"aws:RequestedRegion": "eu-west-1"}, policies, scp_prod),
]
print(f"{'ACTION':<25} {'RESOURCE':<40} {'RÉGION':<12} {'DÉCISION':<22} RAISON")
print("-" * 120)
for action, resource, ctx, pol, scp in requetes:
res = evaluate_request(action, resource, ctx, pol, scp)
region = ctx.get("aws:RequestedRegion", "—")
resource_short = resource.replace("arn:aws:s3:::", "s3:::").replace("arn:aws:", "")
icon = "✓" if res["decision"] == "ALLOW" else "✗"
print(f"{icon} {action:<23} {resource_short:<40} {region:<12} {res['decision']:<22} {res['raison']}")
ACTION RESOURCE RÉGION DÉCISION RAISON
------------------------------------------------------------------------------------------------------------------------
✓ s3:GetObject s3:::dev-bucket/data.csv eu-west-1 ALLOW Allowed by DeveloperPolicy / AllowS3Dev
✗ s3:DeleteObject s3:::prod-bucket/data.csv eu-west-1 DENY (explicite) Denied by ProductionDenyPolicy / DenyProdDelete
✓ s3:GetObject s3:::dev-bucket/data.csv us-east-1 ALLOW Allowed by DeveloperPolicy / AllowS3Dev
✓ ec2:DescribeInstances * eu-west-1 ALLOW Allowed by DeveloperPolicy / AllowEC2Read
✗ iam:CreateUser * eu-west-1 DENY (SCP) Action non autorisée par les SCPs de l'organisation
Heatmap des risques CSPM#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
controles = [
"Chiffrement\nat rest",
"Accès public\nbloné",
"MFA activé",
"Rotation des\ncredentials",
"Logs activés",
"Network\nisolation",
"Least privilege\nIAM",
]
services = ["S3 / GCS\n/ Blob", "RDS / Cloud\nSQL / Azure DB", "EC2 / GCE\n/ Azure VM", "IAM / Roles", "VPC / VNet", "Lambda /\nCloud Func"]
# Score criticité 1 (OK/faible risque) → 5 (critique)
# Données synthétiques représentant une configuration typique mal sécurisée
data = np.array([
# S3 RDS EC2 IAM VPC Lambda
[1, 1, 2, 3, 2, 2], # Chiffrement at rest
[4, 2, 3, 4, 2, 3], # Accès public bloqué
[5, 4, 4, 2, 3, 4], # MFA activé
[4, 3, 3, 5, 2, 3], # Rotation credentials
[2, 2, 3, 2, 2, 3], # Logs activés
[3, 2, 2, 2, 1, 2], # Network isolation
[4, 3, 3, 5, 2, 4], # Least privilege IAM
])
df_cspm = pd.DataFrame(data, index=controles, columns=services)
fig, ax = plt.subplots(figsize=(11, 6))
sns.heatmap(df_cspm, annot=True, fmt="d", cmap="RdYlGn_r",
vmin=1, vmax=5, linewidths=0.5, linecolor="white",
ax=ax, cbar_kws={"label": "Criticité (1=OK, 5=Critique)"})
ax.set_title("Heatmap CSPM : criticité des contrôles de sécurité par service cloud\n(données synthétiques — configuration typique mal sécurisée)", fontsize=12, pad=15)
ax.set_xticklabels(ax.get_xticklabels(), rotation=0, ha="center", fontsize=9)
ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=9)
plt.savefig("cspm_heatmap.png", dpi=100, bbox_inches="tight")
plt.show()
Radar comparatif IAM AWS vs GCP vs Azure#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
categories = ["Granularité\ndes permissions", "Automatisation\n(recommandations)", "Forensique\n(audit logs)", "Coût\nopérationnel", "Intégration\nKubernetes", "Gestion\ncross-cloud"]
N = len(categories)
# Scores /10 synthétiques (opinion d'experts)
scores = {
"AWS IAM": [9, 8, 8, 6, 8, 7],
"GCP IAM": [8, 9, 7, 7, 9, 6],
"Azure RBAC":[7, 7, 8, 7, 7, 8],
}
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": "polar"})
colors = ["#e74c3c", "#3498db", "#2ecc71"]
for (label, vals), color in zip(scores.items(), colors):
values = vals + vals[:1]
ax.plot(angles, values, "o-", linewidth=2, color=color, label=label)
ax.fill(angles, values, alpha=0.1, color=color)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=9, color="#2c3e50")
ax.set_ylim(0, 10)
ax.set_yticks([2, 4, 6, 8, 10])
ax.set_yticklabels(["2", "4", "6", "8", "10"], size=8, color="#7f8c8d")
ax.set_title("Comparatif IAM : AWS vs GCP vs Azure\n(scores synthétiques /10)", size=13, y=1.1, color="#2c3e50")
ax.legend(loc="upper right", bbox_to_anchor=(1.35, 1.1), fontsize=10)
ax.grid(color="#bdc3c7", linestyle="--", linewidth=0.7, alpha=0.7)
plt.savefig("iam_radar_comparison.png", dpi=100, bbox_inches="tight")
plt.show()
Résumé#
Modèles IAM distincts : AWS (politiques JSON Allow/Deny avec évaluation en cascade), GCP (bindings WHO→WHAT→WHICH avec conditions CEL), Azure (RBAC sur une hiérarchie de scope). Chaque modèle a ses particularités d’escalade et d’anti-patterns.
Logique d’évaluation AWS : Deny explicite > SCP > Permission Boundary > Allow explicite. Les SCP définissent un plafond infranchissable, même pour un
Allowdans une politique d’identité.Credentials statiques à éliminer : IRSA (AWS EKS), Workload Identity Federation (GCP) et Managed Identity (Azure) permettent aux workloads cloud-natifs de s’authentifier avec des tokens temporaires liés à leur identité, sans secrets dans le code ou l’environnement.
Moindre privilège assisté : IAM Access Analyzer (AWS) et IAM Recommender (GCP) identifient automatiquement les permissions excessives non utilisées et proposent des remédiations.
CSPM : Prowler, ScoutSuite et les services natifs (Security Hub, SCC, Defender) automatisent l’audit des configurations cloud contre les benchmarks CIS, détectent les dérives et fournissent un score de posture exploitable.
Threat Detection : GuardDuty (AWS), Security Command Center (GCP) et Microsoft Defender analysent en temps réel les logs cloud pour détecter les compromissions de credentials, la reconnaissance d’API et les comportements anormaux.
Gouvernance organisationnelle : les SCPs (AWS Organizations) et les politiques organisationnelles GCP (Org Policies) permettent d’imposer des garde-fous globaux (régions autorisées, services interdits) indépendamment des politiques individuelles des comptes.