IoT Industriel

Mini-Usine IoT Sécurisée

Simulation entièrement conteneurisée d'une mini-usine IoT industrielle sécurisée — 6 devices streament de la télémétrie en MQTT mutual-TLS vers un stack Grafana de monitoring temps réel.

MQTT/mTLSInfluxDBGrafanaDocker ComposePythonTLS/PKI
View on GitHub
Problème

Les déploiements IoT industriels vivent ou meurent sur trois choses : communication sécurisée device-to-broker, ingestion time-series fiable à grande échelle, et observabilité temps réel avec alerting. La plupart des tutoriels bâclent la sécurité ou ajoutent le monitoring comme après-coup.

Approche

Construire un stack IIoT de référence end-to-end et production-shaped où chaque message device-to-broker est chiffré et mutuellement authentifié (mTLS), les données hétérogènes de capteurs sont normalisées dans un seul store time-series, et les opérateurs ont un dashboard Grafana live auto-provisionné avec alerting par seuils.

Architecture
6 Simulateurs IoT
MQTT/mTLS :8883
Broker Mosquitto
Bridge Python
InfluxDB 2.7
Dashboard Grafana

Tech Stack

MessagingEclipse Mosquitto 2.0MQTTS sur 8883 avec mTLS, plaintext interne sur 1883
IngestionBridge Python (paho-mqtt + requests)Souscrit à iot/plant/#, écrit InfluxDB line protocol
StockageInfluxDB 2.7 (Flux)Base time-series, rétention 30 jours, auth par token
VisualisationGrafana (auto-provisionné)Dashboard 8 panneaux, refresh 10s, alertes SMTP
PKIOpenSSL (generate-certs.sh)CA auto-signé (4096-bit) + certs server/client
OrchestrationDocker Compose8 services, bridge network, 5 volumes nommés

Décisions clés

  • Bridge Python personnalisé plutôt que Node-RED — un subscriber/writer de ~90 lignes a moins de parties mobiles, un container plus petit, et un contrôle programmatique complet sur le filtrage de champs et la construction line-protocol.
  • Design two-listener du broker — mTLS sur 8883 pour les devices, plaintext interne sur 1883 pour les services backend dans le réseau Docker, évitant la redondance TLS sur le fabric interne de confiance.
  • Sécurité d'abord, sécurité forcée — TLS 1.2 obligatoire avec vérification mutuelle des certificats ; une suite de 5 tests de sécurité automatisée prouve que les connexions plaintext et bad-cert sont refusées.
  • Provisioning infrastructure-as-code — la datasource et le dashboard Grafana sont provisionnés depuis YAML/JSON au démarrage, aucune configuration UI manuelle.
  • Durcissement des containers — le simulateur tourne en utilisateur non-root (uid 1000), les clés privées sont chmod 600, certs/ est gitignored.
Deep Dive

Mutual TLS pour chaque device

Le port externe du broker (8883) requiert TLS 1.2 avec vérification mutuelle des certificats (require_certificate true). Une suite de 5 tests de sécurité automatisée valide que les connexions plaintext sont refusées, les connexions bad-cert sont refusées, le mTLS valide réussit, TLS 1.2 est forcé, et la vérification stricte des certificats est active.

conf
listener 8883
certfile /certs/server.crt
keyfile /certs/server.key
cafile /certs/ca.crt
require_certificate true
tls_version tlsv1.2

Bridge Python : MQTT vers InfluxDB

Un service Python de ~90 lignes souscrit à iot/plant/#, parse les payloads JSON, et écrit les points InfluxDB line-protocol via HTTP. Surface d'attaque plus petite que Node-RED, moins de containers, entièrement scriptable.

python
def on_message(client, userdata, msg):
    payload = json.loads(msg.payload.decode())
    device_type = msg.topic.split('/')[2]
    fields = {k: v for k, v in payload.items() if isinstance(v, (int, float))}
    line = f"{device_type},device_id={payload['device_id']} " + \
           ",".join(f"{k}={v}" for k, v in fields.items())
    requests.post(f"{INFLUX_URL}/api/v2/write?bucket=iot-data&precision=s",
                  data=line, headers={"Authorization": f"Token {INFLUX_TOKEN}"})

6 devices simulés avec injection d'anomalies

Chaque simulateur est un processus Python conteneurisé émettant une télémétrie réaliste : 2 machines de production (5% de panne aléatoire), capteur de température (2% de spike, alerte à 80°C), capteur de pression (3% de surpression, alerte à 5 bar), capteur de vibration (4% d'anomalie sur un axe aléatoire), et un compteur d'énergie. Schéma de topics MQTT : iot/plant/{device_type}/{device_id}.

Résultats
< 2s
Latence bout-en-bout
5/5 (100%)
Tests de sécurité
10+ testés
Devices concurrents
8
Services Docker

Fonctionnalités démo

  • Démarrage en une seule commande : docker-compose up -d lance tout le stack de 8 services
  • Dashboard Grafana live sur localhost:3001 avec 8 panneaux auto-provisionnés
  • Injection d'anomalies temps réel visible comme blips sur le dashboard en quelques secondes
  • Suite de tests de sécurité TLS automatisée avec 100% de réussite
  • Horizontalement scalable — ajouter un device = dupliquer un bloc de service compose
Leçons apprises
  • Node-RED a été remplacé par un bridge Python de ~90 lignes — les outils de glue prêts à l'emploi ajoutent du poids opérationnel ; un service purpose-built fin est plus clair pour un pipeline fixe.
  • Les SAN des certificats doivent couvrir à la fois l'alias DNS Docker et localhost pour la validation depuis l'intérieur et l'extérieur du réseau.
  • depends_on attend uniquement le démarrage du container, pas la readiness du service — toujours coder les clients MQTT pour retry avec backoff.
  • Intégrer le least-privilege dans l'image Docker (utilisateur non-root, clés chmod 600), pas au runtime.