TP 17 : Réponse Automatisée aux Incidents (Auto-Remédiation)

Module : 5 – NetSecDevOps et Sécurité Automatisée Durée indicative : 2h Prérequis : le OPNsense du TP 7 accessible, Python 3, pip install fastapi uvicorn requests pydantic python-dotenv

Objectifs

  • Construire un mini-outil SOAR : réception d’alertes, décision, action.
  • Automatiser le blocage d’une adresse IP compromise via l’API REST d’OPNsense.
  • Mettre en œuvre les garde-fous vus en théorie (portée et durée limitées, journalisation).

Contexte

Un IDS/IPS enverrait normalement une alerte via un webhook lors de la détection d’un comportement suspect. Vous simulez cette alerte pour déclencher automatiquement un blocage temporaire sur le pare-feu.

Boucle SOAR concrète avec OPNsense Boucle SOAR concrète avec OPNsense De l’alerte simulée au blocage effectif sur OPNsense, avec garde-fou de déblocage automatique après une durée limitée.

Étape 1 – Serveur d’écoute des alertes (webhook)

soar_listener.py :

from fastapi import FastAPI
from pydantic import BaseModel
import logging
import uvicorn

app = FastAPI(title="SOAR Listener")
logging.basicConfig(filename="soar_actions.log", level=logging.INFO)

class Alert(BaseModel):
    source_ip: str
    severity: str = "low"
    type: str | None = None

@app.post("/webhook/alert")
def receive_alert(alert: Alert):
    logging.info(f"Alerte reçue : {alert.model_dump()}")

    if alert.severity in ("high", "critical"):
        block_ip(alert.source_ip)
        return {"status": "blocked", "ip": alert.source_ip}
    return {"status": "ignored"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=5000)

FastAPI valide automatiquement le corps JSON reçu grâce au modèle Pydantic Alert (types vérifiés, severity par défaut à "low" si absente), et expose une documentation interactive sur http://localhost:5000/docs.

Étape 2 – Fonction de blocage via l’API OPNsense

import os, requests
from dotenv import load_dotenv

load_dotenv()
BASE = f"https://{os.getenv('OPNSENSE_HOST')}/api"
AUTH = (os.getenv('OPNSENSE_KEY'), os.getenv('OPNSENSE_SECRET'))

def block_ip(ip):
    payload = {"alias": {"name": "soar_blocklist", "address": ip}}
    resp = requests.post(f"{BASE}/firewall/alias/addItem", json=payload, auth=AUTH, verify=False)
    resp.raise_for_status()
    requests.post(f"{BASE}/firewall/alias/reconfigure", auth=AUTH, verify=False)
    logging.info(f"IP bloquée : {ip}")

Prérequis OPNsense : créez au préalable un alias vide soar_blocklist (Firewall → Aliases) et une règle de blocage référençant cet alias, en amont des autres règles.

Étape 3 – Simulation d’une alerte

curl -X POST http://localhost:5000/webhook/alert \
  -H "Content-Type: application/json" \
  -d '{"source_ip": "203.0.113.50", "severity": "high", "type": "bruteforce_ssh"}'

Vérifiez dans l’interface OPNsense que l’IP a été ajoutée à l’alias et que le trafic est bloqué.

Étape 4 – Garde-fous : durée limitée et liste blanche

Complétez block_ip pour :

  1. refuser de bloquer une IP présente dans une liste blanche codée en dur (ex. IP du poste hôte, de l’administrateur),
  2. programmer un déblocage automatique après une durée définie (ex. 1h), via un job planifié ou un thread différé (threading.Timer à des fins pédagogiques).

Étape 5 – Journalisation et audit

Vérifiez que soar_actions.log trace bien : l’alerte reçue, la décision prise (bloqué/ignoré), l’IP concernée, l’horodatage.

Rendu attendu

  • soar_listener.py et ses dépendances, versionnés (.env exclu).
  • Le fichier soar_actions.log illustrant plusieurs scénarios (alerte bloquée, alerte ignorée, IP en liste blanche).
  • Une capture de l’alias soar_blocklist peuplé sur OPNsense.

Points de vérification

  • Une alerte severity: high déclenche bien un blocage effectif sur OPNsense.
  • Une alerte severity: low est journalisée mais n’entraîne aucune action.
  • Une IP en liste blanche n’est jamais bloquée, même en cas d’alerte critique.