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 :

  1. Deny explicite : tout Effect: Deny l’emporte sur tout.

  2. SCP : si aucun SCP n’autorise l’action, elle est refusée.

  3. Permission Boundary : si défini, restreint les permissions effectives.

  4. Allow explicite : l’action doit être explicitement autorisée.

  5. 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#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import pandas as pd
import numpy as np

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()
_images/02db8eadc076c173c8eba5d11f5b1b1e99ac569135ca8675652a7a42d191392b.png

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()
_images/476fdf96505c19b98ce78a3cd8aa3373df92add909350bd251640af3d1837341.png

Résumé#

  1. 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.

  2. Logique d’évaluation AWS : Deny explicite > SCP > Permission Boundary > Allow explicite. Les SCP définissent un plafond infranchissable, même pour un Allow dans une politique d’identité.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

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