08 — IDS/IPS, Zero Trust et VPN#

Les architectures modernes abandonnent le modèle périmétrique (« castle and moat ») au profit du Zero Trust : aucune confiance implicite n’est accordée à quiconque, interne ou externe. Ce chapitre explore les systèmes de détection d’intrusion, le modèle Zero Trust (NIST SP 800-207), et les technologies VPN modernes qui en sont les briques d’implémentation.

Prérequis

Ce chapitre suppose la maîtrise des chapitres précédents sur la cryptographie, PKI, segmentation réseau, ainsi que des concepts réseau avancés (IPsec, routage, tunneling).


IDS vs IPS#

Positionnement et mode de déploiement#

Caractéristique

IDS

IPS

Acronyme

Intrusion Detection System

Intrusion Prevention System

Mode

Passif (copie du trafic)

Inline (dans le chemin du trafic)

Action

Alerte uniquement

Alerte + blocage en temps réel

Impact réseau

Nul (hors bande)

Latence ajoutée (microseconds)

Faux positifs

Visibilité seulement

Blocage de trafic légitime

IDS passif (TAP/SPAN port) : reçoit une copie du trafic via un port miroir de switch. Ne peut pas bloquer, mais ne peut pas non plus être une cible directe ou causer d’interruption réseau.

IPS inline : positionné entre deux segments réseau, inspecte chaque paquet avant de le laisser passer. Un bug logiciel ou une surcharge peut provoquer une interruption réseau (fail-open ou fail-closed selon la configuration).

IDS réseau vs hôte#

NIDS (Network IDS) — analyse le trafic réseau en transit :

  • Suricata : multi-thread, EVE JSON, support des règles Snort et Suricata, capable d’extraction de fichiers, TLS fingerprinting (JA3/JA4).

  • Snort 3 : référence historique, très grand écosystème de règles communautaires (Talos).

HIDS (Host IDS) — analyse les événements sur un hôte :

  • Wazuh (fork d’OSSEC) : intégrité des fichiers (FIM), audit des syscalls, conformité PCI-DSS/CIS, corrélation SIEM intégrée.

  • Auditd + Falco : monitoring des syscalls Linux en temps réel (particulièrement adapté aux conteneurs).


Détection par signature vs anomalie#

Détection par signature#

Fonctionne sur la comparaison avec une base de signatures connues (CVE, patterns d’exploit, indicateurs de compromission).

# Exemple de règle Suricata
alert http any any -> $HTTP_SERVERS any (
    msg:"SQL Injection tentative — UNION SELECT";
    flow:to_server,established;
    content:"UNION"; nocase; http_uri;
    content:"SELECT"; nocase; http_uri; distance:0;
    classtype:web-application-attack;
    sid:1000001; rev:1;
)

Avantages :

  • Faible taux de faux positifs sur les menaces connues.

  • Résultat déterministe et explicable.

  • Performances élevées (matching Aho-Corasick, PCRE JIT).

Limites :

  • Aveugle aux attaques zero-day et aux variantes de signatures.

  • La base de signatures doit être mise à jour constamment.

  • Contournements par obfuscation, encodage, fragmentation.

Détection par anomalie#

Modélise un comportement normal (baseline) et alerte sur les déviances statistiques.

Approches :

  • Statistique : seuils sur débit, nombre de connexions, ratio port/IP.

  • Machine learning : autoencoders pour détection d’anomalies réseau, isolation forest.

  • Comportemental : déviation des patterns applicatifs (ex. accès inhabituel à une base de données).

Avantages :

  • Peut détecter des attaques inconnues (zero-day) si leur comportement est anormal.

  • Détecte les menaces internes (insider threat) qui n’ont pas de signature.

Limites :

  • Taux de faux positifs plus élevé, surtout lors des phases d’apprentissage.

  • Difficile à expliquer (pourquoi cette alerte ?).

  • Le comportement « normal » dérive avec le temps, nécessitant un ré-entraînement.

Courbes ROC et AUC#

La performance d’un détecteur se mesure par sa courbe ROC (Receiver Operating Characteristic) : taux de vrais positifs (sensibilité/rappel) en fonction du taux de faux positifs, pour chaque valeur du seuil de décision.

L”AUC (Area Under the Curve) synthétise la performance : AUC = 1.0 est parfait, AUC = 0.5 est aléatoire.

Compromis opérationnel

Un opérateur SOC doit choisir le seuil de décision adapté à sa tolérance : seuil bas = plus de détections mais fatigue d’alerte (alert fatigue) ; seuil haut = moins d’alertes mais risque de manquer des attaques réelles.


Zero Trust Architecture (ZTA)#

Principes NIST SP 800-207#

La publication NIST SP 800-207 (2020) définit le Zero Trust selon sept principes fondamentaux :

  1. Toutes les ressources sont des sources de données et de services : aucune distinction réseau interne/externe.

  2. Toutes les communications sont sécurisées : TLS mutuel (mTLS) sur tout, y compris en réseau interne.

  3. L’accès à chaque ressource est accordé par session : pas de confiance persistante ; chaque accès est réévalué.

  4. L’accès est déterminé par une politique dynamique : identité, état du device, contexte comportemental.

  5. L’intégrité et la sécurité de tous les devices sont surveillées : posture device (endpoint compliance).

  6. Authentification et autorisation strictement appliquées : never trust, always verify.

  7. Données collectées sur tout le trafic : visibilité complète pour amélioration continue de la posture.

Composants d’une architecture Zero Trust#

Sujet (utilisateur / workload)
    │
    ▼
[Policy Enforcement Point (PEP)]  ←──── Décision d'accès ────►  [Policy Engine (PE)]
    │                                                                      │
    ▼                                                              [Policy Administrator (PA)]
Ressource (app / API / données)
  • Policy Engine (PE) : évalue la demande d’accès selon la politique (identité, device, contexte, risque).

  • Policy Administrator (PA) : configure et communique les décisions du PE au PEP.

  • Policy Enforcement Point (PEP) : applique concrètement la décision (autoriser/bloquer/rediriger).


BeyondCorp — le modèle Google#

BeyondCorp (Google, 2014) est la première implémentation à grande échelle d’une architecture Zero Trust. Elle repose sur l’idée que le réseau d’entreprise est aussi dangereux qu’internet — un employé sur VPN n’obtient pas plus de confiance qu’un employé depuis son domicile.

Piliers du modèle BeyondCorp#

  • Inventaire des devices : chaque device est connu et doté d’un certificat unique.

  • Trust score du device : niveau de conformité (patch OS, chiffrement disque, EDR actif…) combiné à l’identité utilisateur.

  • Access proxy : tout accès aux applications transite par un proxy qui vérifie identité + posture device en temps réel.

  • Remplacement du VPN : les employés accèdent directement aux applications via l’Access proxy, sans VPN.

ZTNA — Zero Trust Network Access#

Le marché a standardisé l’approche BeyondCorp sous le terme ZTNA (Zero Trust Network Access) ou SDP (Software-Defined Perimeter) :

  • Identity-Aware Proxy (IAP) : Google Cloud IAP, Cloudflare Access, Pomerium.

  • ZTNA as-a-service : Zscaler Private Access, Netskope, Cloudflare WARP.

ZTNA vs VPN traditionnel

Le VPN traditionnel accorde un accès réseau large une fois authentifié (« network-centric »). Le ZTNA accorde un accès à une application spécifique seulement (« application-centric »), avec réévaluation continue. Compromis d’un device en ZTNA ≠ accès à tout le réseau.


VPN : IPsec et WireGuard#

IPsec — IKEv2 / ESP#

IPsec opère en mode tunnel (encapsulation complète du paquet IP) ou transport (protection de la payload uniquement).

IKEv2 (Internet Key Exchange v2) établit les SAs (Security Associations) :

Phase 1 (IKE_SA_INIT) :
  - Négociation des algorithmes (DH group, chiffrement, PRF, intégrité)
  - Échange Diffie-Hellman
  - Établissement d'un canal chiffré

Phase 2 (IKE_AUTH + CREATE_CHILD_SA) :
  - Authentification mutuelle (certificats X.509 ou PSK)
  - Création des Child SAs (ESP) pour les données

Configuration Strongswan (IKEv2 / ESP) :

# /etc/swanctl/swanctl.conf
connections {
    site-to-site {
        version = 2
        proposals = aes256gcm128-prfsha384-ecp384
        local_addrs  = 10.0.0.1
        remote_addrs = 10.0.0.2

        local {
            auth = pubkey
            certs = /etc/swanctl/x509/server.crt
            id    = "CN=vpn-gateway-a"
        }
        remote {
            auth = pubkey
            id   = "CN=vpn-gateway-b"
        }
        children {
            net {
                local_ts  = 10.1.0.0/24
                remote_ts = 10.2.0.0/24
                esp_proposals = aes256gcm128-esn
                mode = tunnel
            }
        }
    }
}

WireGuard — protocole Noise#

WireGuard est un VPN moderne (Linux 5.6+, < 4 000 lignes de code) fondé sur le Noise Protocol Framework :

  • Cryptographie : Curve25519 (ECDH), ChaCha20-Poly1305 (chiffrement authentifié), BLAKE2s (hachage), SipHash24 (table de routage).

  • Handshake : 1-RTT (Noise_IKpsk2), authentification par clé publique Curve25519.

  • Pas de gestion d’état : le handshake est sans état côté initiateur, résistant aux DoS.

# Génération de clés WireGuard
wg genkey | tee wg-private.key | wg pubkey > wg-public.key

# /etc/wireguard/wg0.conf (serveur)
[Interface]
PrivateKey = <clé_privée_serveur>
Address    = 10.10.0.1/24
ListenPort = 51820

PostUp   = nft add rule inet firewall forward iifname wg0 accept
PostDown = nft delete rule inet firewall forward iifname wg0 accept

[Peer]
PublicKey  = <clé_publique_client>
AllowedIPs = 10.10.0.2/32

# Démarrage
wg-quick up wg0
wg show       # Afficher l'état, le trafic, le dernier handshake

Hub-and-Spoke vs Mesh#

Topologie

Hub-and-Spoke

Mesh complet

Architecture

Tous les clients via un hub central

Chaque nœud connecté à tous les autres

Complexité

Simple (O(n) tunnels)

Élevée (O(n²) tunnels)

Latence

+1 saut pour le trafic East-West

Latence optimale (chemin direct)

Résilience

Point unique de défaillance

Haute disponibilité intrinsèque

Cas d’usage

Accès distant classique, petit déploiement

Datacenters distribués, SD-WAN

WireGuard s’adapte nativement aux deux topologies ; des outils comme Tailscale (basé sur WireGuard) automatisent la topologie mesh avec NAT traversal via DERP (relais) et coordination via un serveur de coordination (coordination server).

Comparaison WireGuard vs IPsec

WireGuard est significativement plus simple à configurer (pas de négociation de chiffrement, pas de modes IKE) et offre de meilleures performances sur les connexions mobiles (reconnexion instantanée, changement de réseau transparent). IPsec reste le standard pour les interconnexions site-à-site et l’interopérabilité entre équipements réseau hétérogènes.


Comparaison Zero Trust vs périmètre classique#

Aspect

Modèle périmétrique

Zero Trust

Hypothèse fondamentale

Réseau interne = de confiance

Aucun réseau n’est de confiance

Authentification

À l’entrée du périmètre

Permanente, par ressource

Autorisation

Accès réseau large post-auth

Accès minimal par application/session

Chiffrement interne

Optionnel (réseau « sûr »)

Obligatoire (mTLS partout)

Visibilité

Limitée (est-west aveugle)

Complète (tous les flux loggés)

Réponse à la compromission

Pivot latéral non détecté

Mouvement latéral détecté et bloqué

Complexité initiale

Faible

Élevée (PKI, identity store, PEP)

Entité de référence

Réseau (subnet)

Identité (user + device)


Cellules Python#

Courbes ROC — IDS signature vs anomalie#

Hide code cell source

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import seaborn as sns
import pandas as pd
from sklearn.metrics import roc_curve, auc
import networkx as nx
import random
# --- Courbes ROC : IDS par signature vs IDS par anomalie ---
# Simulation de scores de détection sur 5000 flux (500 attaques, 4500 légitimes)

np.random.seed(42)

n_attacks   = 500
n_benign    = 4500
n_total     = n_attacks + n_benign

y_true = np.array([1] * n_attacks + [0] * n_benign)

# IDS par SIGNATURE :
# - Très précis sur les attaques connues : scores élevés pour les attaques connues
# - Aveugle aux attaques zero-day (modélisé comme scores bas pour une fraction)
sig_attacks_known   = 0.65  # 65% des attaques sont dans la base de signatures
sig_attacks_unknown = 1.0 - sig_attacks_known  # 35% = zero-day, non détectés

scores_sig_attacks  = np.concatenate([
    np.random.beta(9, 1,  int(n_attacks * sig_attacks_known)),   # Connues : scores hauts
    np.random.beta(1, 8,  int(n_attacks * sig_attacks_unknown)),  # Inconnues : scores bas
])
scores_sig_benign   = np.random.beta(1, 9, n_benign)              # Légitimes : scores bas
scores_sig = np.concatenate([scores_sig_attacks, scores_sig_benign])

# IDS par ANOMALIE :
# - Détecte mieux les attaques inconnues (comportement anormal)
# - Mais plus de faux positifs (trafic légitime inhabituel)
scores_ano_attacks = np.random.beta(6, 2, n_attacks)   # Attaques : scores moyens-hauts
scores_ano_benign  = np.random.beta(2, 5, n_benign)    # Légitimes : scores moyens-bas
scores_ano = np.concatenate([scores_ano_attacks, scores_ano_benign])

# Courbes ROC
fpr_sig, tpr_sig, _ = roc_curve(y_true, scores_sig)
fpr_ano, tpr_ano, _ = roc_curve(y_true, scores_ano)
auc_sig = auc(fpr_sig, tpr_sig)
auc_ano = auc(fpr_ano, tpr_ano)

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# --- Courbe ROC ---
ax1 = axes[0]
ax1.plot(fpr_sig, tpr_sig, color="#4878d0", linewidth=2.5,
         label=f"Signature — AUC = {auc_sig:.3f}")
ax1.plot(fpr_ano, tpr_ano, color="#ee854a", linewidth=2.5,
         label=f"Anomalie — AUC = {auc_ano:.3f}")
ax1.plot([0, 1], [0, 1], color="gray", linewidth=1, linestyle="--", label="Aléatoire (AUC=0.5)")
ax1.set_xlabel("Taux de faux positifs (FPR)", fontsize=11)
ax1.set_ylabel("Taux de vrais positifs (TPR / Rappel)", fontsize=11)
ax1.set_title("Courbes ROC — IDS signature vs anomalie\n"
              "Le modèle par anomalie compense sur les attaques zero-day",
              fontsize=11, fontweight="bold")
ax1.legend(fontsize=9)
ax1.set_xlim(-0.02, 1.02)
ax1.set_ylim(-0.02, 1.02)

# Annotations points de fonctionnement
for fpr_thr, tpr_thr, label, color in [
    (0.05, None, "FPR=5%\n(ops)", "#4878d0"),
]:
    idx_sig = np.argmin(np.abs(fpr_sig - fpr_thr))
    idx_ano = np.argmin(np.abs(fpr_ano - fpr_thr))
    ax1.scatter([fpr_sig[idx_sig]], [tpr_sig[idx_sig]], color="#4878d0", s=100, zorder=5)
    ax1.scatter([fpr_ano[idx_ano]], [tpr_ano[idx_ano]], color="#ee854a", s=100, zorder=5)
    ax1.axvline(x=fpr_thr, color="gray", linestyle=":", linewidth=1)
    ax1.text(fpr_thr + 0.01, 0.08, f"FPR={fpr_thr:.0%}", fontsize=8, color="gray")

# --- Distribution des scores ---
ax2 = axes[1]
bins = np.linspace(0, 1, 40)
ax2.hist(scores_sig[y_true == 1], bins=bins, alpha=0.55, color="#4878d0",
         density=True, label="Signature — Attaques")
ax2.hist(scores_sig[y_true == 0], bins=bins, alpha=0.35, color="#4878d0",
         density=True, linestyle="--", histtype="step", linewidth=2, label="Signature — Légitimes")
ax2.hist(scores_ano[y_true == 1], bins=bins, alpha=0.55, color="#ee854a",
         density=True, label="Anomalie — Attaques")
ax2.hist(scores_ano[y_true == 0], bins=bins, alpha=0.35, color="#ee854a",
         density=True, linestyle="--", histtype="step", linewidth=2, label="Anomalie — Légitimes")
ax2.set_xlabel("Score de détection", fontsize=11)
ax2.set_ylabel("Densité", fontsize=11)
ax2.set_title("Distribution des scores de détection\nAtt. zero-day → scores faibles côté signature",
              fontsize=11, fontweight="bold")
ax2.legend(fontsize=8, loc="upper center")

plt.savefig("roc_ids.png", dpi=120, bbox_inches="tight")
plt.show()
print(f"AUC Signature : {auc_sig:.4f}")
print(f"AUC Anomalie  : {auc_ano:.4f}")
_images/9d35423b1afdbb6018ce985b55f84396f1a44b60b69dfefc44f38207ef5db9ac.png
AUC Signature : 0.8402
AUC Anomalie  : 0.9768

Simulation scoring d’anomalie réseau#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

np.random.seed(7)
n_flows = 300

# Flux légitimes : débit modéré, ports standards, fréquence normale
n_legit = 240
debit_legit  = np.random.lognormal(mean=5.5, sigma=0.8, size=n_legit)   # Ko/s
port_legit   = np.random.choice([80, 443, 22, 8080, 3306], size=n_legit)
freq_legit   = np.random.exponential(scale=5, size=n_legit)              # req/s

# Flux anormaux : débit élevé ou ports inhabituels ou fréquence élevée
n_anom = 60
debit_anom   = np.random.lognormal(mean=8.0, sigma=1.0, size=n_anom)
port_anom    = np.random.choice([4444, 1337, 9001, 31337, 65500], size=n_anom)
freq_anom    = np.random.exponential(scale=50, size=n_anom)

# Score d'anomalie : combinaison normalisée des trois dimensions
def compute_score(debit, port, freq, port_known):
    score_debit = np.log1p(debit) / 12.0
    score_port  = 0.0 if port in [80, 443, 22, 8080, 3306] else 0.6
    score_freq  = np.log1p(freq) / 8.0
    return np.clip(0.4 * score_debit + 0.3 * score_port + 0.3 * score_freq, 0, 1)

scores_legit = np.array([compute_score(d, p, f, True)
                          for d, p, f in zip(debit_legit, port_legit, freq_legit)])
scores_anom  = np.array([compute_score(d, p, f, False)
                          for d, p, f in zip(debit_anom, port_anom, freq_anom)])

all_scores = np.concatenate([scores_legit, scores_anom])
all_labels = np.array([0] * n_legit + [1] * n_anom)  # 0=légitime, 1=anomalie

# Seuil de détection
threshold = 0.55
predicted = (all_scores >= threshold).astype(int)

TP = ((predicted == 1) & (all_labels == 1)).sum()
FP = ((predicted == 1) & (all_labels == 0)).sum()
TN = ((predicted == 0) & (all_labels == 0)).sum()
FN = ((predicted == 0) & (all_labels == 1)).sum()

print(f"Seuil = {threshold} | TP={TP} FP={FP} TN={TN} FN={FN}")
print(f"Précision : {TP/(TP+FP):.3f} | Rappel : {TP/(TP+FN):.3f}")

# Visualisation
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# --- Scatter débit vs fréquence, coloré par score ---
ax1 = axes[0]
scatter = ax1.scatter(
    np.concatenate([debit_legit, debit_anom]),
    np.concatenate([freq_legit, freq_anom]),
    c=all_scores, cmap="RdYlGn_r",
    s=60, alpha=0.75, edgecolors="white", linewidths=0.5,
)
cbar = plt.colorbar(scatter, ax=ax1)
cbar.set_label("Score d'anomalie", fontsize=9)

# Marquer les alarmes
alarm_idx_legit = np.where(scores_legit >= threshold)[0]
alarm_idx_anom  = np.where(scores_anom >= threshold)[0]
ax1.scatter(debit_legit[alarm_idx_legit], freq_legit[alarm_idx_legit],
            marker="x", color="crimson", s=120, linewidths=2, label=f"Faux positifs ({FP})")
ax1.scatter(debit_anom[alarm_idx_anom], freq_anom[alarm_idx_anom],
            marker="*", color="darkred", s=150, linewidths=1.5, label=f"Vraies alarmes ({TP})")

ax1.set_xlabel("Débit (Ko/s)", fontsize=11)
ax1.set_ylabel("Fréquence (req/s)", fontsize=11)
ax1.set_xscale("log")
ax1.set_yscale("log")
ax1.set_title(f"Détection d'anomalies réseau (seuil={threshold})\nRouge = alarme déclenchée",
              fontsize=11, fontweight="bold")
ax1.legend(fontsize=9)

# --- Distribution des scores ---
ax2 = axes[1]
bins = np.linspace(0, 1, 35)
ax2.hist(scores_legit, bins=bins, alpha=0.65, color="#5cb85c", density=True,
         label=f"Trafic légitime (n={n_legit})")
ax2.hist(scores_anom,  bins=bins, alpha=0.65, color="#d9534f", density=True,
         label=f"Trafic anormal (n={n_anom})")
ax2.axvline(x=threshold, color="black", linestyle="--", linewidth=2)
ax2.text(threshold + 0.01, ax2.get_ylim()[1] * 0.9 if ax2.get_ylim()[1] > 0 else 5,
         f"Seuil = {threshold}", fontsize=9, color="black")
ax2.set_xlabel("Score d'anomalie", fontsize=11)
ax2.set_ylabel("Densité", fontsize=11)
ax2.set_title("Distribution des scores d'anomalie\nZone de recouvrement = incertitude",
              fontsize=11, fontweight="bold")
ax2.legend(fontsize=9)

plt.savefig("anomaly_scoring.png", dpi=120, bbox_inches="tight")
plt.show()
Seuil = 0.55 | TP=48 FP=0 TN=240 FN=12
Précision : 1.000 | Rappel : 0.800
_images/4204c098efad8b7ffa72e308f46f414b32c79f875a66fa42baa4064675431f6f.png

Graphe réseau Zero Trust avec networkx#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# --- Graphe Zero Trust : Policy Engine central, segments, flux ---

G = nx.DiGraph()

# Nœuds : Policy Engine, segments applicatifs, sources
nodes = {
    "Policy\nEngine":      {"type": "pe",      "trust": 1.0},
    "Identity\nProvider":  {"type": "infra",   "trust": 0.9},
    "Device\nInventory":   {"type": "infra",   "trust": 0.9},
    "Utilisateur\nRH":     {"type": "user",    "trust": 0.8},
    "Utilisateur\nDevOps": {"type": "user",    "trust": 0.85},
    "App RH":              {"type": "app",     "trust": 0.7},
    "App CI/CD":           {"type": "app",     "trust": 0.75},
    "Base de\ndonnées RH": {"type": "data",    "trust": 0.6},
    "Secrets\n(Vault)":    {"type": "data",    "trust": 0.6},
    "Attaquant\n(externe)":{"type": "threat",  "trust": 0.0},
}

for node, attrs in nodes.items():
    G.add_node(node, **attrs)

# Arêtes : (source, dest, autorisé)
edges = [
    # Flux légitimes autorisés
    ("Utilisateur\nRH",     "Policy\nEngine",     True),
    ("Utilisateur\nDevOps", "Policy\nEngine",     True),
    ("Policy\nEngine",      "Identity\nProvider", True),
    ("Policy\nEngine",      "Device\nInventory",  True),
    ("Policy\nEngine",      "App RH",             True),
    ("Policy\nEngine",      "App CI/CD",          True),
    ("App RH",              "Base de\ndonnées RH",True),
    ("App CI/CD",           "Secrets\n(Vault)",   True),

    # Flux bloqués (attaquant)
    ("Attaquant\n(externe)","App RH",             False),
    ("Attaquant\n(externe)","Base de\ndonnées RH",False),
    ("Attaquant\n(externe)","Secrets\n(Vault)",   False),
    # Accès latéral entre apps (bloqué par Zero Trust)
    ("App RH",              "Secrets\n(Vault)",   False),
]

for src, dst, allowed in edges:
    G.add_edge(src, dst, allowed=allowed)

# Layout manuel pour clarté
pos = {
    "Policy\nEngine":       (0,  0),
    "Identity\nProvider":   (-2.5, 1.5),
    "Device\nInventory":    (2.5,  1.5),
    "Utilisateur\nRH":      (-3.5, 0),
    "Utilisateur\nDevOps":  (-3.5, -1.5),
    "App RH":               (-1.5, -2.5),
    "App CI/CD":            (1.5,  -2.5),
    "Base de\ndonnées RH":  (-2.5, -4.5),
    "Secrets\n(Vault)":     (2.5,  -4.5),
    "Attaquant\n(externe)": (4.5,  0),
}

# Couleurs par type
type_colors = {
    "pe":     "#2c7bb6",
    "infra":  "#7fbfff",
    "user":   "#5cb85c",
    "app":    "#f0ad4e",
    "data":   "#9b59b6",
    "threat": "#d9534f",
}

node_colors  = [type_colors[G.nodes[n]["type"]] for n in G.nodes()]
node_sizes   = [3000 if G.nodes[n]["type"] == "pe" else
                1800 if G.nodes[n]["type"] in ("infra", "data") else
                1500 for n in G.nodes()]

edge_colors  = ["#5cb85c" if G.edges[e]["allowed"] else "#d9534f" for e in G.edges()]
edge_styles  = ["solid"   if G.edges[e]["allowed"] else "dashed"  for e in G.edges()]
edge_widths  = [2.5       if G.edges[e]["allowed"] else 2.0        for e in G.edges()]

fig, ax = plt.subplots(figsize=(13, 9))
ax.set_title(
    "Architecture Zero Trust — Policy Engine central\n"
    "Flux autorisés (vert) et bloqués (rouge) — Never Trust, Always Verify",
    fontsize=13, fontweight="bold",
)

nx.draw_networkx_nodes(G, pos, ax=ax, node_color=node_colors,
                       node_size=node_sizes, alpha=0.9)
nx.draw_networkx_labels(G, pos, ax=ax, font_size=7.5, font_weight="bold", font_color="white")

# Dessiner les arêtes par style
for (u, v), color, style, width in zip(G.edges(), edge_colors, edge_styles, edge_widths):
    nx.draw_networkx_edges(
        G, pos, edgelist=[(u, v)], ax=ax,
        edge_color=[color], style=style, width=width,
        arrows=True, arrowsize=20,
        connectionstyle="arc3,rad=0.08",
    )

# Légende
legend_handles = [
    mpatches.Patch(color=type_colors["pe"],     label="Policy Engine"),
    mpatches.Patch(color=type_colors["infra"],  label="Infrastructure (IdP, Inventory)"),
    mpatches.Patch(color=type_colors["user"],   label="Utilisateurs"),
    mpatches.Patch(color=type_colors["app"],    label="Applications"),
    mpatches.Patch(color=type_colors["data"],   label="Données / Secrets"),
    mpatches.Patch(color=type_colors["threat"], label="Menace externe"),
    mpatches.Patch(color="#5cb85c",             label="Flux autorisé"),
    mpatches.Patch(color="#d9534f",             label="Flux bloqué"),
]
ax.legend(handles=legend_handles, loc="lower left", fontsize=8,
          bbox_to_anchor=(0, 0), framealpha=0.9)
ax.axis("off")

plt.savefig("zero_trust_graph.png", dpi=120, bbox_inches="tight")
plt.show()
print("Graphe Zero Trust généré.")
print(f"Flux autorisés : {sum(1 for _, _, d in G.edges(data=True) if d['allowed'])}")
print(f"Flux bloqués   : {sum(1 for _, _, d in G.edges(data=True) if not d['allowed'])}")
_images/070211db3cc58d2a99e39e85dbff5bd66915bdabc513b9962989077a51be010c.png
Graphe Zero Trust généré.
Flux autorisés : 8
Flux bloqués   : 4

Résumé#

  1. IDS vs IPS : l’IDS passif observe sans risque d’interruption ; l’IPS inline bloque mais introduit une latence et un point de défaillance potentiel. Les deux se complètent : IDS pour la visibilité East-West, IPS en inline sur les points d’entrée critiques.

  2. Signature vs anomalie : la détection par signature offre une précision élevée sur les menaces connues mais est aveugle aux zero-days. La détection par anomalie couvre les inconnues mais génère plus de faux positifs. Les SIEM/SOC modernes combinent les deux, évaluant les courbes ROC pour calibrer les seuils opérationnels.

  3. Zero Trust Architecture (NIST SP 800-207) : renverse le modèle de confiance. Aucune entité — interne ou externe — n’est implicitement fiable. Chaque accès est accordé par session, après vérification de l’identité (utilisateur + device) et évaluation de la posture.

  4. BeyondCorp / ZTNA : l’access proxy remplace le VPN en accordant un accès applicatif précis plutôt qu’un accès réseau large. La compromission d’un device est contenue à son accès applicatif autorisé, pas à l’ensemble du réseau.

  5. IPsec (IKEv2) : standard interopérable pour les interconnexions site-à-site et les VPN d’entreprise. Complexe mais universel, supporté par tous les équipements réseau.

  6. WireGuard : protocol moderne (< 4 000 lignes, Noise Framework, Curve25519) offrant des performances supérieures et une configuration radicalement simplifiée. Idéal pour les déploiements cloud-native et les accès distants.

  7. Topologie : le hub-and-spoke centralise le contrôle et la surveillance mais crée un goulot d’étranglement ; le mesh distribue la latence et la résilience au prix d’une complexité de configuration croissante en O(n²).