Lafine Systems Design · Documentation technique

Livre blanc sur l'architecture et la sécurité de RoamSwitch Server Edition

Suite autonome de sécurité réseau et de protection de l'intégrité, sans tête, pour les infrastructures Linux cloud et sur site. Aucune formulation promotionnelle n'est utilisée : le contenu peut être vérifié à partir du comportement réel du paquet de distribution officiel et de l'auto-vérification du §6.

Version v1.11.1 Champ d'application RoamSwitch Server Edition 1.11.1 (détection de tunnellisation DNS, honeytokens, capture de preuves forensiques, cartographie NIST CSF / CIS Controls, mise à jour de la carte CVE du noyau signée) Prérequis systemd + nftables Publié 2026-10-08

§1Aperçu et principes fondamentaux

RoamSwitch Server Edition est une suite de sécurité et de protection de l'intégrité du réseau autonome et résidente, spécialement conçue pour les infrastructures de serveurs Linux des fournisseurs de cloud (AWS, GCP, Azure, VPS nus) et des centres de données sur site.

Contrairement aux éditions de postes de travail clients axées sur la mobilité et l'adaptation du périmètre Wi-Fi, l'édition serveur repose sur cinq principes sans compromis : Inbound Default Drop, Critical Path File Integrity Monitoring (FIM), Preemptive Kernel LPE & Privilege Escalation Hardening, eBPF Runtime Threat Telemetry et Isolement autonome de l'hôte Air-Gap avec alertes d'urgence avant la séparation.

Le principe Zero Telemetry – aucune bibliothèque de communication externe dans le démon lui-même – est appliqué au niveau du code (par défaut, seule la réception de données signées (les données CVE du noyau, les règles web et, sur les serveurs Ubuntu, les données CVE des paquets Ubuntu utilisées par la vérification CVE des paquets du système ; manifeste vérifié chaque jour, données CVE elles-mêmes environ tous les 30 jours) sort de l'hôte, et elle peut aussi être désactivée ; toute autre exception est une fonction que l'opérateur active explicitement ; voir les principes de conception du §1), et les secrets (jetons de bot et configuration) sont protégés par les permissions 0600.

Comparaison 1.1 : édition client et édition serveur

Dimensions Édition client (ordinateur de bureau/mobile) Édition serveur (sans tête/infrastructure)
Déploiement Ordinateurs portables, postes de travail de développement mobiles Machines virtuelles cloud, nœuds nus, hôtes de conteneurs
Comportement du réseau Commutation de profil dynamique basée sur le SSID Wi-Fi / Gateway MAC Suppression par défaut entrante statique. Seuls les ports explicitement autorisés sont exposés
Interface utilisateur Interface graphique du bureau GTK3 + indicateur de plateau CUI sans tête pur. Commandes CLI + assistant interactif de configuration + interface en terminal (roamswitch-server-tui)
Notifications d'incidents Notifications sur le bureau (libnotify) Transmission directe vers Telegram Bot, l'API de messagerie LINE et les Webhooks (Slack/Discord)
Défenses primaires Usurpation ARP Wi-Fi public, BadUSB, cryptage de documents par ransomware Largage de reconnaissance externe, détection de falsification FIM, fuites de conteneurs, LPE du noyau (CVE-2026-53362)

1.2 Principes architecturaux fondamentaux

  • Zero Telemetry : par défaut, aucune information sur l'utilisateur ou l'environnement n'est envoyée à l'extérieur. Le démon lui-même ne contient aucune bibliothèque de communication HTTP/TLS ; ce n'est que pour les notifications et transferts configurés par l'opérateur qu'il exécute, en sous-processus, le curl du système (Telegram / LINE / webhook ; les secrets comme jetons et URL sont transmis dans une configuration sur l'entrée standard, jamais en ligne de commande) et openssl (transfert syslog TLS). La seule communication externe activée par défaut, hors mises à jour du gestionnaire de paquets, est la suivante. (1) Tant que cve_kernel_map_updates_enabled (true par défaut) est activé, roamswitch-updater reçoit anonymement depuis lafine.net des données signées. Il s'agit des données CVE du noyau pour l'audit d'isolation des conteneurs, des règles de détection de la couche applicative web (règles web) et, sur les serveurs Ubuntu, des données CVE des paquets Ubuntu utilisées par la vérification CVE des paquets du système. Le manifeste signé et sa signature sont vérifiés chaque jour (deux GET) ; les données CVE du noyau et les données CVE des paquets Ubuntu ne sont récupérées que si environ 30 jours se sont écoulés depuis la dernière mise à jour (un serveur qui n'a pas encore les données Ubuntu les récupère à la prochaine vérification quotidienne), et les règles web seulement si le manifeste a une version plus récente. Il s'agit d'un simple HTTPS GET sans chaîne de requête ni cookie, dont le seul en-tête qui nomme le logiciel est un User-Agent: RoamSwitch-Updater fixe ; seules les données dont la signature Ed25519 est valide sont retenues, et rien sur cet hôte n'est envoyé. L'opérateur peut la désactiver avec roamswitch server config set cve_kernel_map_updates_enabled false, l'assistant de configuration ou la TUI du serveur ; un false explicite dans un server.conf existant est conservé tel quel, et l'audit fonctionne toujours avec la base de référence intégrée lorsqu'elle est désactivée ; en revanche, la vérification CVE des paquets du système sur un serveur Ubuntu indique alors « aucune donnée CVE locale » et ne renvoie aucun résultat. Toute autre exception est activée explicitement par l'opérateur. (2) Le transfert d'événements (forward_enabled, false par défaut) n'envoie qu'aux destinations syslog / fichier JSON Lines / webhook signé configurées par l'opérateur. (3) Seulement si npm_audit_signatures_enabled (false par défaut) est activé et que l'opérateur exécute roamswitch audit-npm-signatures <DIR> --confirm, npm lui-même contacte registry.npmjs.org. (4) Seulement si sensor_pairing_enabled (false par défaut) est activé, il se connecte, et uniquement à l'adresse du Sensor saisie par l'opérateur (le Sensor est un produit distinct, non couvert par Zero Telemetry). (5) Fleet (niveau Business) n'est que du code côté appareil : un appareil non enrôlé n'envoie rien et ne résout aucun nom ; le service de gestion n'est ni déployé ni publié, et il n'existe par conception aucun canal de commande à distance. (6) L'intégration LLM de l'outil distinct fourni accessscope (diagnostic de sécurité des journaux d'accès) est désactivée par défaut. Seulement si elle est activée (en ligne de commande : lorsque --llm est indiqué), un résumé du diagnostic, des lignes de journal servant de preuve et des informations sur l'environnement (pour la fonction de recherche de journaux) peuvent être envoyés à un service externe par la commande LLM choisie par l'opérateur. Le démon (roamswitch-server) ne prend aucune part à cette communication, AccessScope peut exécuter son analyse par LLM aussi en tant que root (le LLM s'exécute alors avec les droits root ; un utilisateur sans privilèges est recommandé), et elle est hors du champ de Zero Telemetry. L'outil distinct fourni personalsoc (vérification des journaux et des défenses) fonctionne de la même façon : uniquement si --llm est indiqué, un résumé du diagnostic peut être envoyé à un service externe via la commande LLM choisie par l'opérateur (hors du champ de Zero Telemetry). La commande pathscope n'appelle aucun LLM. (7) Seulement si clamav_enabled (false par défaut) est activé, le démon exécute freshclam pour recevoir les signatures ClamAV (une fois lorsqu'il n'existe pas encore de base de signatures, puis, tant que clamav_scan_dirs est défini, toutes les clamav_freshclam_interval_secs, 7200 secondes par défaut).
  • Inbound Default Drop (refus entrant par défaut) : toute nouvelle connexion entrante vers un port (TCP) autre que ceux qui sont autorisés est rejetée par nftables, sans même renvoyer de RST. Un scan de ports ne voit que les ports autorisés (le bouclage, les connexions établies, ICMP et ICMPv6 sont acceptés).
  • Fermeture en cas de défaillance (fail-closed) et auto-réparation : les règles de refus par défaut et d'isolement restent dans le noyau et survivent donc à l'arrêt ou au redémarrage du démon (Restart=always). La protection des ports publiés de Docker est revérifiée toutes les 60 secondes, et la liste de blocage Egress ainsi que le blocage des scans de ports toutes les 300 secondes ; les règles disparues sont réinstallées. Les règles d'isolement de l'état DEGRADED sont contrôlées à chaque évaluation de 15 secondes et réinstallées si elles ont disparu. Si les règles de refus par défaut elles-mêmes sont supprimées de l'extérieur, elles ne sont pas réappliquées automatiquement ; vérifiez-le avec le diagnostic « Refus entrant par défaut » (roamswitch server status).
  • Protection par les permissions 0600 : le fichier de configuration /etc/roamswitch/server.conf se voit automatiquement imposer les permissions 0600 à sa création et à sa mise à jour. Les secrets comme les jetons et les URL de webhook sont aussi transmis à curl dans une configuration sur l'entrée standard et ne figurent jamais en ligne de commande (visible des autres utilisateurs via /proc/<pid>/cmdline).

§2Paysage des menaces et modèle de l’adversaire

Les serveurs modernes accessibles sur Internet sont confrontés à des analyses automatisées incessantes, à des vecteurs de rupture de conteneurs et à des élévations de privilèges du noyau Zero Day.

Principaux vecteurs de menaces abordés
  1. Reconnaissance automatisée et exposition de la base de données : analyseurs à l'échelle Internet localisant les ports de base de données internes involontairement exposés (MySQL 3306, Redis 6379, Postgres 5432).
  2. falsification du chemin critique et insertion de porte dérobée : les intrus établissent la persistance en modifiant les binaires d'authentification de base (/bin/login, /usr/bin/sudo, /etc/pam.d/*, /etc/shadow).
  3. Élévation des privilèges du noyau (CVE-2026-53362 Frag Gap) : des attaquants non privilégiés utilisent les espaces de noms utilisateur (unshare -U -r) pour déclencher une corruption de la mémoire du noyau et passer à la racine.
  4. Container Socket Breakout : conteneurs mal configurés avec des indicateurs /var/run/docker.sock ou --privileged montés exécutant des actions root sur le système d'exploitation hôte.
  5. Surveillance de la mémoire des processus : les attaquants attachent ptrace ou lisent /proc/$pid/mem à travers les processus pour extraire des clés privées ou des informations d'identification.

§3Architecture du système

RoamSwitch Server Edition fonctionne comme des binaires Rust natifs conçus pour une mémoire minimale et une surcharge CPU nulle.

┌─────────────────────────────────────────────────────────────┐
│  Espace utilisateur et administrateur                       │
│                                                             │
│   roamswitch (CLI)             roamswitch-mcp (AI Agent)    │
│   · server status (34 points)   · outils d'audit sécurité   │
│   · server config / setup      · stdio JSON-RPC             │
│   · fim verify / update                                     │
│        │                               │                    │
└────────┼───────────────────────────────┼────────────────────┘
         │ (commandes CLI et config)     │ (stdio)
┌────────▼───────────────────────────────▼────────────────────┐
│  Fondation système (systemd + privilège root)               │
│                                                             │
│   roamswitch-server-daemon                                  │
│   ├── CriticalPathFim (balayage SHA-256 en arrière-plan)    │
│   ├── module d'intégration eBPF / Falco                     │
│   ├── Notifier (alertes Telegram / LINE / Webhook)          │
│   └── Air-Gap Controller (règles d'urgence nftables)        │
│                                                             │
│   Config : /etc/roamswitch/server.conf (permission 0600)    │
│   BD d'intégrité : /var/lib/roamswitch/fim.db               │
└─────────────────────────────────────────────────────────────┘

§4Mécanismes de défense de base

4.1 Sécurité des limites (abandon par défaut entrant)

nftables applique policy drop à la chaîne input de la table inet roamswitch_server. Seules les nouvelles connexions TCP entrantes vers les ports indiqués par l'administrateur (ssh_ports, allowed_ports) sont autorisées, et les communications vers les ports non autorisés expirent (paquets abandonnés). Le bouclage, les connexions établies, ICMP et ICMPv6 sont autorisés.

Même si un Air-Gap (isolement d'urgence) est déclenché, les chemins d'administration – les ports SSH de maintenance (ssh_ports) et les IP sources de maintenance enregistrées (whitelist_ips), connexions nouvelles comme établies – restent dans le jeu de règles, ce qui évite une situation où la récupération à distance devient impossible (preserve_ssh_on_isolation, true par défaut). Toute autre connexion établie (par exemple un C2 ou un reverse shell déjà ouvert par l'attaquant) est coupée par les chaînes default-drop. Si aucune source d'administration n'est indiquée (whitelist_ips et les adresses autorisées de guard.yaml sont vides), les ports SSH de maintenance continuent d'accepter de nouvelles connexions de toute source pendant l'isolement, et le serveur émet un avertissement au démarrage. Avec preserve_ssh_on_isolation=false, l'isolement coupe aussi le SSH. L'édition serveur jusqu'à la 1.10.4 présentait un défaut. Les règles d'isolement abandonnaient les réponses SSH, de sorte que le SSH de l'opérateur ne fonctionnait pas pendant l'isolement. Il est corrigé dans la 1.10.5.

4.2 Surveillance de l'intégrité des fichiers du chemin critique (FIM)

Surveille l'intégrité par hachage SHA-256 des fichiers les plus critiques pour le démarrage de l'OS, l'authentification, le réseau et l'administration système. Le périmètre comprend 23 chemins fixes (/etc/shadow, /etc/passwd, /etc/sudoers, /etc/ssh/sshd_config, /bin/login, /usr/bin/sudo, /usr/sbin/sshd, /usr/lib/systemd/systemd, etc.) ; les fichiers situés directement dans 12 répertoires (/etc/pam.d, /etc/sudoers.d, /etc/ssh/sshd_config.d, /etc/cron.d et /etc/cron.{hourly,daily,weekly,monthly}, /etc/systemd/system, /etc/ld.so.conf.d, /etc/profile.d, /root/.ssh), où l'apparition de nouveaux fichiers est aussi détectée (dans /root/.ssh, known_hosts et known_hosts.old sont exclus, car ils sont réécrits à chaque connexion SSH) ; les binaires de RoamSwitch ; le authorized_keys de chaque utilisateur pouvant se connecter ; et /etc/ld.so.preload, qui ne doit pas exister (son apparition est détectée). Après une mise à jour légitime de paquets, la référence saine est actualisée automatiquement : sur les systèmes de type Debian, par le hook /etc/apt/apt.conf.d/99roamswitch-fim (apt upgrade, etc.), et sur les systèmes de type RHEL et openSUSE, par une unité systemd.path qui surveille /var/lib/rpm (roamswitch-fim-update.path) ; les deux exécutent roamswitch fim update.

Identification du chemin d'intrusion (qui a fait la modification, et par quelle connexion SSH)

Les notifications d'une modification de fichier (FIM des chemins critiques et FIM des fichiers de verrouillage) indiquent désormais le processus à l'origine de la modification (compte, exécutable, commande) et la connexion SSH à laquelle il appartient (IP et port source, heure de connexion, méthode d'authentification et empreinte de la clé publique). C'est activé par défaut (fim_intrusion_context_enabled). Dès qu'un événement est lu, le démon prend un instantané de /proc/<pid> du processus écrivain (exécutable, utilisateur, loginuid, sessionid, cgroup, chaîne des parents), car un processus comme tee ou sed a disparu une milliseconde plus tard. Un processus déjà terminé est complété par l'enregistreur d'exécutions (s'il est activé). Le rattachement à une connexion utilise d'abord la table de sessions de la garde SSH, sinon les lignes Accepted de logind et de journald. Cela fonctionne même si la garde SSH est désactivée. Rien n'est envoyé vers l'extérieur.

Seules les valeurs rapportées par le noyau sont crues : l'exécutable vient de /proc/<pid>/exe (et non d'argv[0]), la connexion de loginuid (fixé une seule fois à la connexion et hérité à travers sudo et su) et du session-N.scope du cgroup, le reste de la chaîne des parents. Les valeurs qu'un processus peut modifier lui-même, comme comm, la ligne de commande et l'environnement, sont affichées mais ne servent jamais à décider ; un processus qui se contente de s'appeler sshd n'est pas pris pour une connexion SSH. Une notification distingue trois réponses : trouvée (source, heure, méthode, clé) ; « Aucune connexion SSH n'a été trouvée » (la vérification a montré un service systemd, un conteneur, une session de connexion locale ou un processus sans session de connexion) ; et « Le chemin de connexion n'a pas pu être déterminé » (le processus avait déjà disparu et n'est pas dans l'enregistreur d'exécutions, logind n'a pas répondu, la modification a été trouvée par l'analyse périodique et le processus est donc inconnu, etc.). Ce qui n'a pas pu être vérifié n'est jamais présenté comme « pas une connexion ». La notification part même si la recherche échoue.

/etc/ssh/sshd_config: modified
  Processus : root (connecté en tant que admin) / /usr/bin/tee / commande : tee /etc/ssh/sshd_config (pid 4121)
  Connexion SSH : la session ouverte par admin depuis 203.0.113.25:51544 à 2026-10-17 03:12:08 (authentification : publickey, clé SHA256:AbCdEf0123456789)

/etc/sudoers: modified
  Le chemin de connexion n'a pas pu être déterminé (le changement a été trouvé par l'analyse périodique ; le processus responsable est inconnu)

Les modifications qui placent un nouveau fichier sous le même nom sont désormais détectées elles aussi : install, sed -i, et cp suivi de mv dans le même répertoire. Un marquage par fichier est posé sur l'inode, de sorte que ce genre de modification ne produisait auparavant aucun événement. Le démon marque maintenant aussi les répertoires qui contiennent des fichiers surveillés, et lorsqu'un fichier d'un autre nom est fermé dans le même répertoire, il recherche à nouveau le nom quelques millisecondes plus tard. Limite : un fichier écrit dans un autre répertoire puis déplacé avec mv n'est trouvé que par l'analyse périodique (fim_check_interval_secs). Le processus écrivain est alors inconnu, et la notification le dit.

Les mesures ont été faites sur une VM Ubuntu 24.04 où la source de l'opérateur se trouvait dans un espace de noms réseau (netns) distinct. Avec sudo tee, avec un processus détaché par nohup (son IP source a encore été identifiée après la déconnexion de l'opérateur, avec la mention « Cette session est déjà terminée »), avec une chaîne sudo -i et avec un cat de très courte durée, la notification a montré l'IP, l'utilisateur et la méthode d'authentification. Une connexion locale a donné « Aucune connexion SSH n'a été trouvée ». Un processus ayant déguisé son nom par exec -a "sshd: …" n'a pas été pris pour du SSH.

Outre la notification, le constat est conservé dans la chronologie des incidents (type fim) et dans /var/lib/roamswitch/fim_intrusions.json (root uniquement, les 100 derniers). L'outil MCP verify_fim le renvoie dans un champ ajouté, recentIntrusionContext (seulement s'il est lancé en root). Le verdict sur l'altération ou non d'un fichier ne change pas. Limites : avec l'enregistreur d'exécutions désactivé et un processus écrivain qui ne vit qu'un instant, l'exécutable est inconnu ; on affiche alors l'exécutable et la ligne de commande du processus parent. Les notifications de la surveillance de persistance (persistence_watch) ne portent aucune information de processus et n'ont donc pas de chemin d'intrusion. Le chemin montre le processus à l'origine de la modification et sa connexion, pas davantage : la porte d'entrée elle-même (la vulnérabilité exploitée, ou la façon dont une clé a été volée) n'est pas montrée. Pour le désactiver, mettez fim_intrusion_context_enabled=false.

4.3 Durcissement des noyaux et des conteneurs

Frag Gap (CVE-2026-53362) Atténuation : applique user.max_user_namespaces = 0 via sysctl, neutralisant les vecteurs LPE. Yama LSM Protection applique kernel.yama.ptrace_scope = 2, empêchant toute inspection non autorisée de la mémoire. Les charges de travail des conteneurs sont auditées pour une exposition docker.sock. Dans l'édition serveur, cette interdiction des espaces de noms non privilégiés est activée par défaut (harden_userns_enabled). Elle empêche Podman rootless et similaires ; mettez-la à false si vous en dépendez.

Les ports publiés de Docker (-p) contournent, on le sait, le pare-feu standard de l'hôte. Avec protect_docker_ports=true (par défaut), le trafic arrivant de l'interface externe dont le port de destination avant DNAT ne figure pas parmi les ports autorisés (ssh_ports, allowed_ports) est donc rejeté (le trafic de retour, le loopback et le trafic entre conteneurs ne sont pas concernés). Avec le backend iptables / iptables-nft, les règles vont dans la chaîne DOCKER-USER (IPv4, et aussi via ip6tables quand l'IPv6 de Docker est activé) ; avec le backend nftables natif de Docker (à partir de Docker 29, configuration sans DOCKER-USER), dans une table dédiée inet roamswitch_docker_guard (hook forward, priorité -10), et ce seul jeu de règles couvre IPv4 et IPv6. Le démon revérifie périodiquement et réinstalle les règles disparues. En outre, un garde toujours actif détecte en temps réel, via docker events, le démarrage d'un conteneur --privileged ou le montage de /var/run/docker.sock et le signale immédiatement comme risque d'évasion de conteneur (aucune configuration ; notification seule, sans blocage automatique). Pour les hôtes qui manipulent des fichiers destinés à d'autres (relais de messagerie, dossiers partagés), le File Scan Guard, activable en option (YARA intégré + ClamAV optionnel), assure l'analyse périodique et la mise en quarantaine automatique.

Container Exec Guard (container_exec_guard_enabled, true par défaut) : pose fanotify FAN_OPEN_EXEC (classe notification) sur la racine overlay2 de chaque conteneur Docker en cours d'exécution et inspecte le contenu de chaque fichier exécuté avec les règles YARA intégrées, en notifiant en cas de correspondance. Comme il s'abonne à docker events puis énumère les conteneurs en cours avec docker ps, les conteneurs déjà en cours d'exécution au démarrage du démon sont eux aussi couverts. Le contenu du fichier exécuté est lu depuis le descripteur de fichier que fanotify fournit ; il peut donc être inspecté même si le fichier s'auto-supprime juste après son exécution. Il se borne à notifier et n'arrête ni ne refuse l'exécution (l'édition Server n'a aucun fanotify de classe permission). Docker 29 a retiré Status, devenu obsolète, des messages de docker events ; jusqu'à la 1.11.0, l'abonnement échouait donc aussitôt et Container Exec Guard se reconnectait toutes les 5 secondes sans jamais obtenir d'abonnement. Depuis la 1.11.1, il utilise Action, qui a la même valeur sur les Docker plus anciens. Quand l'abonnement continue d'échouer, l'intervalle de reconnexion double de 5 secondes jusqu'à 5 minutes au plus, et l'avertissement pour la même cause n'est journalisé que la première fois.

4.4 Télémétrie des menaces et réglage anti-ballonnement pendant l'exécution de l'eBPF

Utilise des sondes eBPF modernes (CO-RE / BTF) pour intercepter les élévations de privilèges et les tentatives d'évasion de conteneurs en quelques millisecondes. Pour éliminer les problèmes standard de Falco — gonflement des journaux , épuisement du disque et surcharge du processeur — RoamSwitch standardise un profil optimisé (/etc/falco/config.d/99-roamswitch-optimized.yaml) :

  • Severity Filtering (priority: warning) : supprime de 95 % la télémétrie opérationnelle bruyante (informations/avis), faisant apparaître des incidents de sécurité strictement exploitables.
  • Kernel-Level Pre-Drop (drop_failed_exit: true) : rejette les sorties d'appel système ayant échoué directement dans la sonde du noyau, réduisant ainsi les changements de contexte du processeur de plus de 50 %.
  • Memory Footprint Optimization (cpus_for_each_buffer: 2) : compresse les tampons en anneau de 8 Mo à 2 Mo, limitant strictement l'utilisation de la mémoire à 30 - 50 Mo.
  • Direct UNIX Domain Socket IPC : coordination immédiate via un socket en mémoire (/run/roamswitch/events.sock), sans passer par des E/S disque.
  • Plafonnement automatique des journaux (/etc/logrotate.d/roamswitch-falco) : une rotation quotidienne avec maxsize 50M et 7 cycles compressés limite la croissance des journaux de Falco.

Précision et rapidité de l'isolement eBPF (sans s'en remettre aux seules règles de Falco)

La précision et la rapidité de l'isolement eBPF dépendent des règles du capteur (Falco ou Tetragon). De fait, les règles standard de Falco 0.45.0 (29 au total) n'en comptent aucune qui détecte un reverse shell sur l'hôte : les règles de reverse shell ne valent que pour les conteneurs, et certaines sont écartées par la condition priority: warning de RoamSwitch. RoamSwitch ne présume pas de la qualité des règles ; il compense de trois façons : il mesure la durée de l'isolement, il vérifie régulièrement que le chemin d'alerte est vivant, et il détecte lui-même les reverse shells.

Mesure de la latence d'isolement : pour chaque incident eBPF, le démon enregistre le temps écoulé entre la détection par Falco et la lecture de l'alerte (detect_to_receive_ms), entre la lecture et la fin de la réponse (receive_to_action_ms, mesuré une fois l'appel d'isolement revenu ; pour l'Air-Gap de l'hôte, jusqu'au chargement du jeu de règles), ainsi que leur somme (detect_to_action_ms). Les valeurs apparaissent comme champs ajoutés dans ebpf_incidents.json et dans l'outil MCP get_ebpf_incidents. La notification d'un processus isolé porte une ligne « de la détection à la réponse : N ms ». Mesures sur une VM Ubuntu 24.04 (Falco 0.45.0, modern eBPF) : 1 à 2 ms de la détection à la réception, 41 à 62 ms de la réception à l'isolement du processus plus SIGKILL (42 à 64 ms au total), et 109 ms au total pour l'Air-Gap de l'hôte (dans l'objectif de 150 ms). Ces chiffres sont ceux de cette VM. Tetragon horodate à la réception et n'a donc pas de detect_to_receive_ms.

Heartbeat (ebpf_heartbeat_interval_secs ; 600 secondes par défaut, 0 le désactive, sinon 60 à 86400) : le paquet fournit une règle Falco propre à RoamSwitch, 96-roamswitch-heartbeat.yaml. À chaque intervalle, le démon ouvre un chemin jetable que seul root peut utiliser (un nom aléatoire sous /run/roamswitch/heartbeat/) et mesure le temps que met l'alerte de cette règle à revenir par la sortie de Falco. Un aller-retour montre à la fois que la règle est chargée, que Falco tourne et que le chemin de sortie est vivant. Ce n'est que lorsque deux vérifications de suite ne reviennent pas (la seconde une minute après la première) qu'il avertit : « les règles ne sont pas chargées ou la sortie n'arrive pas », au plus une fois par 24 heures. Une ligne de heartbeat est écartée sans atteindre l'isolement, la notification, la liste d'autorisation ni le journal des incidents. Rien n'est envoyé vers l'extérieur. Les allers-retours mesurés étaient de 2 à 6 ms ; avec la règle retirée, l'avertissement est venu après environ 164 secondes, et une fois la règle rétablie l'état est redevenu normal en environ 75 secondes.

L'état est conservé dans /var/lib/roamswitch/ebpf_heartbeat.json et lu par le champ heartbeat de l'outil MCP get_ebpf_incidents et par le diagnostic K-04 (agent d'exécution eBPF ; le nombre de points de diagnostic reste 34). Même si Falco tourne, K-04 échoue lorsque le heartbeat ne revient pas. Limites : on vérifie un aller-retour d'une seule règle (les appels système de la famille open) ; cela ne permet pas de savoir si une autre règle est cassée pour son compte. Ce qui casse entre deux vérifications (10 minutes par défaut) n'est vu qu'à la suivante. Cela n'est pas fait pour Tetragon. Sur un hôte où Falco ne tourne pas ou dont le fichier de règle est absent, rien n'est vérifié et rien n'est signalé (la notification existante « garde arrêtée » s'en charge). Falco écrit une ligne d'avertissement dans syslog à chaque vérification (144 lignes par jour par défaut).

Détection de reverse shell (propre à RoamSwitch)

Sans s'appuyer sur les règles de Falco, RoamSwitch lit lui-même /proc toutes les 2 secondes et cherche un shell ou un interpréteur (sh, bash, python, perl, ruby, php, node et similaires) dont l'entrée et la sortie standard sont une même connexion TCP que cet hôte a ouverte vers une adresse extérieure. Sont couverts bash -i >& /dev/tcp/…, python, perl, nc -e et socat exec:. Seul ce qui est vu deux fois de suite est signalé, une fois par processus. Par défaut, seules les adresses extérieures publiques comptent ; les adresses privées (RFC 1918, CGNAT, lien local) ne comptent qu'avec reverse_shell_watch_include_private=true, car le ProxyCommand de ssh, les conteneurs et le développement local les rendent courantes et légitimes. Le loopback n'est jamais surveillé. Les connexions acceptées par l'hôte (par inetd ou socat TCP-LISTEN) ne sont pas couvertes non plus. Mesuré sur une VM Ubuntu 24.04, bash /dev/tcp, python, perl, nc -e et socat exec: ont chacun été détectés environ 2,4 secondes après la connexion. ssh, ssh avec un ProxyCommand, git over ssh et un client nc ne sont pas détectés.

Une détection emprunte le même chemin qu'une alerte Falco (liste d'autorisation, délai de répit des notifications, actions par gravité de guard.yaml, journal des incidents) sous la forme d'un événement eBPF Critical nommé RoamSwitch Reverse Shell. Par défaut, le processus est isolé puis tué (kill). Un reverse shell appartenant à root ne peut pas être isolé par processus ; comme pour tout événement Critical, on passe donc à l'Air-Gap de tout l'hôte. Il y a deux issues si l'on craint qu'un programme légitime soit détecté à tort : reverse_shell_watch_notify_only=true (un Warning avec notification seulement ; rien n'est isolé ni tué ; false par défaut) et une entrée dans la liste d'autorisation, sudo roamswitch server allowlist add "RoamSwitch Reverse Shell" <nom du processus>. Les trois clés de réglage sont reverse_shell_watch_enabled (true par défaut), reverse_shell_watch_include_private (false) et reverse_shell_watch_notify_only (false) .

Limites : le filtrage se fait par nom (nom du processus et de l'exécutable) ; un binaire maison portant un autre nom que celui d'un shell ou d'un interpréteur et reliant son entrée et sa sortie standard à un socket n'est donc pas trouvé. Les connexions par loopback (comme le retour de ssh -R) et celles de moins de 2 secondes ne sont pas vues, pas plus que les processus invisibles depuis l'espace de processus de l'hôte. Cette détection n'existe que dans la Server Edition ; le Mac et le client Linux ne l'ont pas.

4.5 Isolation de l'entrefer d'urgence avec alertes préalables à la séparation

Immédiatement avant de couper les interfaces réseau en cas de falsification critique, le démon envoie de manière synchrone des alertes d'urgence à Telegram, LINE et Webhooks. Les alertes ajoutent automatiquement le serveur Hostname et Primary IP Address.

Format d'alerte d'urgence (JA/EN)
🚨 [RoamSwitch IMMINENT AIR-GAP] CRITICAL: IMMINENT AIR-GAP HOST ISOLATION (cve_2026_53362_lpe)
🚨 CRITICAL SECURITY INCIDENT DETECTED: cve_2026_53362_lpe
Host: production-node-01 (IP: 192.168.1.10)
Process: exploit_payload (PID: 4921)
ACTION: Initiating host Air-Gap network isolation (SSH preservation active).
WARNING: Outbound internet connectivity will be cut immediately after this message.
Timestamp: 2026-09-06 18:00:00
⚠️ Network connectivity will be severed immediately.

Les conditions de levée dépendent de la confiance du déclencheur. Les déclencheurs à faible confiance (par ex. un événement eBPF de niveau Error ou inférieur) sont entièrement rétablis après une courte minuterie (server_safeguards.safety_timer_secs dans guard.yaml, 300 secondes par défaut). Les déclencheurs à forte confiance (eBPF Critical/Emergency/Alert, escalade vers l'isolement de l'hôte par une règle qui se répète, altération d'un canari, etc.) ne rétablissent jamais en silence. Après la courte minuterie, la cause est revérifiée (le PID gelé est-il toujours vivant, la règle s'est-elle redéclenchée, etc.) et l'isolement n'est levé, avec notification, que lorsque la disparition de la cause est vérifiée. Si elle ne peut l'être et sans ACK à l'atteinte du plafond (isolation_hard_cap_secs, 3600 secondes par défaut ; 0 = pas de plafond), l'isolement passe en DEGRADED. DEGRADED conserve le chemin d'administration (ports SSH de maintenance et IP sources de maintenance, nouvelles connexions comme établies), n'autorise de nouveau trafic sortant que vers les IP sources de maintenance et ne se rouvre jamais sur minuterie. La levée se fait uniquement par une revérification qui atteste la disparition de la cause, par roamswitch server ack (un ACK pendant DEGRADED vaut levée) ou par roamswitch emergency-restore. Un isolement acquitté en état FULL est considéré comme repris par l'opérateur : il n'est ni rétabli par la minuterie, ni levé automatiquement même lorsque la disparition de la cause est vérifiée, ni basculé en DEGRADED, et reste en place jusqu'à ce que emergency-restore le lève. Chaque transition est consignée dans la chronologie des incidents et notifiée.

Enregistreur d'exécutions de processus & transfert d'événements

Un enregistreur strictement local qui consigne exec via le connecteur de processus netlink fonctionne activé par défaut (commun avec l'édition Client). Il stocke le chemin de l'exécutable, les arguments de la ligne de commande (longueur limitée, caractères de contrôle échappés), la filiation des processus parents, l'uid, l'ID du conteneur, le SHA-256 de l'exécutable, etc. dans /var/lib/roamswitch/exec_log, en JSON Lines avec chaîne de hachage (0600 ; 200 Mo / 14 jours par défaut). La chaîne de hachage rend l'altération détectable mais non empêchable (un attaquant ayant obtenu root peut réécrire toute la chaîne). Les règles de corrélation à notification seule EXR-001 à EXR-010 (shell lancé par un service web, exécution depuis /tmp ou memfd, chaînes d'outils d'attaque, etc.) n'isolent jamais seules. Seulement si un opérateur active explicitement exec_rules_isolation_optin (false par défaut), une règle de gravité élevée est traitée comme un déclencheur à forte confiance d'isolement de l'hôte.

Le transfert d'événements (forward_enabled, false par défaut) n'envoie qu'aux destinations configurées par l'opérateur : syslog (RFC 5424 ou CEF, en UDP / TCP / TLS), un fichier JSON Lines ou un webhook signé HMAC-SHA256. Le syslog TLS passe par le openssl s_client du système et le webhook par le curl du système (les secrets sont transmis dans une configuration sur l'entrée standard, jamais en ligne de commande) ; le démon ne comporte aucun crate TLS/HTTP. Ce qui est envoyé se limite aux champs nom d'hôte, ID d'événement, horodatage, source, gravité, règle, action, PID, nom du processus et résumé – pour les événements de détection qui figurent dans la chronologie des incidents et l'historique des notifications (eBPF, transitions d'état d'isolement, FIM, canaris, etc.) et pour les notifications des règles d'exécution – ainsi que les empreintes de scellement des segments du journal d'exécution. Le journal brut d'exécution n'est pas inclus.

Détection de tunnellisation DNS / d'exfiltration

L'activation de dns_tunnel_detect_enabled (valeur par défaut : true) inspecte les requêtes DNS sortantes propres à cet hôte (port 53 UDP/TCP uniquement) via un consommateur NFQUEUE dédié. Une requête est comptée comme « de forme suspecte » (le motif caractéristique des outils de tunnellisation DNS tels qu'iodine ou dnscat2) lorsque son étiquette dépasse 40 octets ET que son entropie de Shannon dépasse 3,8, ou lorsque son type d'enregistrement est TXT/NULL (les requêtes TXT vers clamav.net, utilisées par les mises à jour de signatures ClamAV, ne sont pas comptées) ; une notification n'est envoyée que si 10 requêtes de ce type ou plus surviennent dans une fenêtre glissante de 60 secondes (une nouvelle notification pour le même domaine attend 300 secondes, puis 600 et 1200 secondes tant que la rafale se poursuit, jusqu'à un intervalle maximal de 1800 secondes). Seul le DNS (port 53 UDP/TCP) est inspecté ; aucun autre trafic n'est examiné. La fonction se contente de notifier et ne bloque jamais automatiquement. La règle nft sous-jacente porte le drapeau bypass : les requêtes DNS continuent de passer sans être affectées même si le thread de détection est arrêté ou ne répond pas, de sorte que la résolution de noms n'est jamais bloquée par une défaillance de cette fonction. Une modification de ce paramètre ne prend effet qu'après un roamswitch server restart (pas de rechargement à chaud).

Limite connue — les noms d'hôte longs séparés par des tirets (par exemple prod-us-east-1-web-server-node-0042) ainsi qu'une activité légitime particulièrement réaliste côté serveur — comme un serveur de messagerie vérifiant des enregistrements TXT SPF/DKIM pour de nombreux domaines expéditeurs différents en peu de temps — généraient ici, comme les mesures l'ont confirmé, des faux positifs, car les rafales TXT/NULL étaient comptées par un compteur global unique ne distinguant pas les domaines de destination. Les deux ont été corrigés le 23/09/2026. La correction consiste en une liste blanche d'entropie configurable et un compteur de rafales désormais réparti par domaine apex (une approximation simple basée sur les deux derniers labels du QNAME, et non une implémentation stricte de la Public Suffix List). La liste blanche intègre par défaut 16 suffixes de CDN/cloud/PaaS courants (cloudfront.net, akamaiedge.net, akamaized.net, edgekey.net, edgesuite.net, fastly.net, azureedge.net, googleusercontent.com, amazonaws.com, r2.dev, workers.dev, vercel.app, netlify.app, supabase.co, firebaseapp.com, web.app), et les administrateurs peuvent y ajouter leurs propres domaines internes via ServerConfig.dns_tunnel_entropy_allowlist (une clé séparée par des virgules dans server.conf, également modifiable depuis le TUI). La liste blanche ne dispense que du contrôle combiné longueur d'étiquette + entropie ; la détection par type d'enregistrement TXT/NULL s'applique toujours, indépendamment de la liste blanche, ce qui empêche un attaquant de dissimuler un tunnel sous un domaine autorisé (la seule exception est le TXT vers clamav.net, utilisé par les mises à jour de signatures ClamAV, qui n'est pas compté, car un tiers ne peut pas créer de sous-domaine sous ce domaine). Le compteur de rafales est désormais réparti par domaine apex, ce qui élimine les faux positifs dus à un compteur global unique ignorant le domaine de destination (la logique de fenêtre glissante de 60 secondes et de seuil de 10 requêtes n'est pas modifiée par cette correction ; l'intervalle de renotification a ensuite été modifié : tant que la rafale se poursuit, il passe de 300 secondes à un maximum de 1800 secondes). Le trafic malveillant réel d'outils comme iodine ou dnscat2 fait toujours tourner ses étiquettes et types d'enregistrement sous un seul domaine contrôlé par l'attaquant, si bien que restreindre le comptage par domaine apex ne réduit pas l'efficacité de la détection.

Détection et blocage de la force brute SSH (ssh_guard)

Une défense contre la force brute propre à RoamSwitch, activée par ssh_guard_enabled (true par défaut). Elle suit le journal de sshd dans journald avec journalctl -f (elle ne suit auth.log ou secure qu'en l'absence de journald), compte les échecs par adresse source et, pour une source qui atteint le seuil (par défaut 6 en 600 secondes), rejette uniquement les nouvelles connexions (ct state new) vers les ports SSH dans la table inet roamswitch_sshguard. Au moment du bannissement, les connexions que cette source avait déjà ouvertes vers SSH sont aussi coupées (les entrées conntrack sont supprimées et une règle rejette les paquets du sens réponse). Seule une source sans session authentifiée, sans connexion par clé publique enregistrée et sans exemption est bannie, et cela est revérifié juste avant la coupure : une session authentifiée et une source exemptée ne sont donc jamais coupées. Un bannissement commence à 1 heure, est multiplié par 4 à chaque récidive et plafonne à 7 jours ; l'expiration est gérée par le délai d'expiration de nftables. Elle ne déclenche pas d'Air-Gap et n'envoie rien vers l'extérieur (seules des commandes comme journalctl, nft et loginctl sont exécutées localement). Lorsque fail2ban (avec une jail sshd activée) ou CrowdSec (avec un firewall bouncer en marche) protège déjà sshd, elle lui laisse le bannissement et se contente de détecter et de notifier.

Les lignes du journal contiennent une chaîne que l'attaquant choisit (le nom d'utilisateur). L'adresse source n'est donc lue qu'à la fin de la ligne, dans from <IP> port <N>, et une fausse adresse source placée dans le nom d'utilisateur ne fait pas bannir un tiers. Depuis journald, la garde ne lit que les lignes dont les identifiants d'émetteur (_UID, _COMM) sont ceux d'un sshd de root, et ignore les lignes écrites avec logger -t sshd. Le suivi d'un fichier (auth.log) n'est pas la valeur par défaut, car le fichier n'a aucun champ qui garantisse l'émetteur et que tout utilisateur local autorisé à utiliser syslog peut y falsifier des lignes. Pour ne pas enfermer dehors les opérateurs, la passerelle, le DNS, l'hôte lui-même, la boucle locale, whitelist_ips, ssh_guard_allow, la source d'une session SSH authentifiée, toute adresse connectée par clé publique dans les 30 derniers jours et un Sensor appairé ne sont jamais bannis. Les adresses privées ne sont pas exemptées. Un échec de clé publique (Failed publickey) n'est pas compté.

Mesures (une VM Ubuntu 24.04 avec un vrai journald et systemd) : entre le 6e échec et le bannissement, il s'est écoulé environ 0,2 seconde (0,17 à 0,24 seconde). L'adresse source de l'opérateur n'a pas été bannie, et une tentative avec une fausse adresse source dans le nom d'utilisateur n'a pas fait bannir un tiers. Un vrai hydra (4 en parallèle) a été arrêté après 6 échecs. Ont aussi été vérifiés : la restauration d'un bannissement avec sa durée restante après un redémarrage du serveur, la levée d'un bannissement et le repli sur la simple détection avec notification lorsque fail2ban protège sshd. Ces chiffres proviennent de cet environnement de VM. Mesure après l'ajout de la coupure des connexions : les échecs encore passés après un bannissement étaient de 16 à 24 avant, et de 0 à 4 après. Supprimer seulement les entrées conntrack ne coupait pas les connexions ; cela n'a fonctionné qu'une fois ajoutée une règle rejetant le sens réponse.

Règles de la couche applicative Web (web shells et code injecté)

L'édition Serveur n'avait aucune analyse déclenchée par l'écriture, seulement l'analyse périodique de clamav_scan_dirs. Elle surveille désormais les racines web trouvées dans la configuration du serveur web (Apache, nginx, Caddy) avec fanotify en notification seule (FAN_CLASS_NOTIF, qui ne peut jamais bloquer un autre processus) et analyse les fichiers PHP, JavaScript, JSP et ASP dès qu'ils sont écrits et fermés, avec les règles de la couche applicative Web (web shells, code obfusqué injecté, PHP caché dans des images, abus de .htaccess). Une analyse périodique toutes les 6 heures couvre les événements perdus et les fichiers présents avant le démarrage. Les règles se répartissent en trois couches (intégrées, installées par la mise à jour quotidienne après vérification de la signature, vos fichiers dans /etc/roamswitch/rules.d/*.json) ; les 18 règles ne contiennent aucune signature tirée d'un échantillon réel et décrivent uniquement des techniques publiquement documentées.

Dans l'édition Serveur, la quarantaine automatique se limite à critical par défaut. Un résultat high ne fait que notifier et déclencher le hook post-détection ; le fichier d'un site en production n'est pas touché. Les notifications (Telegram, LINE, webhook) sont en 10 langues et indiquent la règle et si le fichier a été mis en quarantaine ou laissé. La récupération des règles s'intègre à la même récupération unique que la mise à jour quotidienne et n'ajoute aucune destination. Avec cve_kernel_map_updates_enabled désactivé, il n'y a aucun contact réseau et les règles intégrées sont utilisées. Avec un vrai fanotify, un web shell écrit a été mis en quarantaine en moins de 2 secondes et un fichier inoffensif a été laissé.

Après une détection, un exécutable placé dans /etc/roamswitch/hooks/post-detection reçoit les résultats en JSON sur l'entrée standard (seul un fichier appartenant à root, non modifiable et placé dans un répertoire sûr est exécuté ; pas de shell, environnement minimal, arrêt après 30 secondes). La réponse optionnelle sur les comptes (désactivée par défaut) révoque le shell de connexion du propriétaire d'un fichier mis en quarantaine. Elle ne fait rien pour root, les comptes dont l'UID est inférieur à account_min_uid (1000 par défaut), les utilisateurs connectés et l'utilisateur du démon ; l'ancien shell est enregistré et roamswitch web-rules unlock-account le rétablit. Faux positifs mesurés : 0 sur le cœur de WordPress (2 409 fichiers), phpMyAdmin (3 356 fichiers) et des dépendances npm (14 743 fichiers). Limites : seuls les 256 premiers Ko (à l'écriture/exécution) et 4 Mo (analyse) sont lus, les noms fragmentés comme 'ev'.'al' ne sont pas suivis, et aucune signature de malware connu n'est fournie faute d'échantillons réels.

Honeytokens (leurres d'identifiants)

Au démarrage du démon, RoamSwitch déploie de façon idempotente des fichiers d'identifiants leurres à des emplacements réels connus, réellement explorés par les attaquants et les outils automatisés de vol d'identifiants (/root/.aws/credentials, /root/.ssh/id_rsa, /root/.docker/config.json, /root/.pypirc et /root/.env.backup, sous /root, le démon s'exécutant en tant que root), chacun contenant un marqueur ROAMSWITCH_HONEYTOKEN (activé par défaut ; honeytokens_enabled l'arrête et honeytokens_home change l'emplacement des leurres). Un fichier réel déjà présent à l'un de ces emplacements n'est jamais écrasé (le déploiement vérifie toujours d'abord son existence). Un accès réel à un honeytoken peut également déclencher, dans une limite fixée, le déploiement en cascade de leurres supplémentaires associés (profil AWS additionnel, config SSH, kubeconfig, etc.). Les paquets jusqu'à la 1.10.4 ne contenaient pas roamswitch-honeytokens, nécessaire à cette fonction, qui ne s'exécutait donc pas sur les installations depuis les paquets. Il est inclus depuis la 1.10.5, et le problème du bac à sable systemd qui bloquait les écritures dans /root sur l'édition serveur a lui aussi été corrigé dans la 1.10.5. Depuis la 1.11.1, chaque commande docker que RoamSwitch exécute lui-même est lancée avec un répertoire privé vide comme DOCKER_CONFIG ; elle ne déclenche donc pas le leurre /root/.docker/config.json. Une lecture par tout autre programme est toujours détectée comme avant.

Un observateur en arrière-plan (roamswitch-honeytokens) surveille la lecture et l'ouverture de chaque leurre au moyen de fanotify de classe notification (il ne bloque rien). L'événement transportant le PID de l'accédant, l'exécutable à l'origine de l'accès est identifié sur-le-champ (là où fanotify n'est pas disponible, l'observateur bascule sur inotify et fuser). Aucun processus légitime n'ayant de raison de lire ces fichiers, tout accès, à l'exception de ceux des binaires de RoamSwitch lui-même et de ceux des clients OpenSSH (ssh, scp, sftp, ssh-add, ssh-agent, etc. ; ssh-keygen n'en fait pas partie) sur les leurres SSH, est considéré comme un signal fort de reconnaissance post-compromission par un intrus (MITRE ATT&CK T1552, Unsecured Credentials) et donne lieu à une notification. Un accès dont l'exécutable ne peut pas être identifié n'est pas toléré : il est traité comme une détection. Cette fonction se contente de notifier ; elle ne fige ni ne bloque elle-même le processus à l'origine de l'accès.

Limite connue — le contenu du leurre /root/.ssh/id_rsa comprend du texte lisible et reste, en tant que corps base64, un format invalide pour une véritable clé privée OpenSSH. Cependant, la plupart des scanners de secrets signalent les fichiers uniquement par correspondance de motif sur l'en-tête -----BEGIN ... PRIVATE KEY-----, ce qui limite selon nous l'impact réel sur le taux de détection. Par ailleurs, dans les environnements où fanotify n'est pas disponible et où l'observateur bascule sur inotify, l'identification du processus accédant au fichier (via un appel à fuser) peut échouer à obtenir un PID ou un nom de processus pour un processus extrêmement éphémère qui se termine immédiatement après la lecture ; dans ce cas, il est simplement traité comme inconnu, ce qui n'affecte pas la détection de l'accès lui-même (l'événement se déclenche et la notification est envoyée).

Paquets de preuves (collecte forensique automatique)

La capture de preuves est lancée dans les cas suivants. Un événement du capteur de menaces eBPF en temps réel aboutit à une isolation réseau ciblée sur un seul processus réussie ; cette isolation échoue et passe en Air-Gap à l'échelle de l'hôte ; ou un isolement Air-Gap de tout l'hôte est exécuté. Elle l'est aussi sur le chemin du canari anti-rançongiciel, lorsqu'une altération est détectée, que le processus est figé et l'hôte isolé. Pour une coupure de tout l'hôte, elle est lancée avant la coupure et ne la retarde jamais. Jusqu'à la 1.10.4, elle ne s'exécutait qu'après une isolation ciblée réussie ; la 1.10.5 a élargi ce périmètre. Si aucun PID cible n'est connu, l'instantané de /proc est omis. roamswitch-incident-capture est lancé et enregistre un paquet de preuves dans <répertoire d'état>/incident-evidence/<horodatage>-<identifiant court>/. Ce qui est collecté : une capture de paquets bornée à 30 secondes via tshark sur l'interface de routage par défaut (consignée comme ignorée si tshark n'est pas installé), ainsi qu'un instantané /proc/<pid> du processus visé (status, cmdline, maps, un environ dont les valeurs ressemblant à des secrets sont remplacées par [REDACTED], une liste des cibles des descripteurs de fichiers ouverts — leur contenu n'est pas collecté — et la chaîne des processus ancêtres). L'échec de collecte d'un artefact isolé n'est pas traité comme fatal ; il est simplement consigné, et le reste de la collecte se poursuit. Les paquets jusqu'à la 1.10.4 ne contenaient pas roamswitch-incident-capture, nécessaire à cette fonction, qui ne s'exécutait donc pas sur les installations depuis les paquets. Il est inclus depuis la 1.10.5.

Les résultats sont consignés dans manifest.json, qui intègre également l'empreinte SHA-256 de chacun des autres fichiers du paquet (afin de pouvoir détecter une altération ultérieure). Les paquets ne sont jamais supprimés automatiquement — les anciens paquets ne sont retirés que lorsqu'un opérateur exécute explicitement sudo roamswitch-incident-capture prune [--keep-days N] (rétention par défaut : 30 jours). Une capture manuelle est également disponible à tout moment via sudo roamswitch-incident-capture snapshot [--pid PID] [--reason TEXT] [--json].

Les preuves collectées ne sont pas vérifiées au moment de la capture. Pour détecter une altération après coup, exécutez sudo roamswitch-incident-capture verify <répertoire du paquet> [--json]. La commande recalcule le hachage de chaque fichier actuellement présent dans le paquet et le compare aux valeurs enregistrées dans manifest.json, en signalant individuellement chaque écart comme modifié, manquant ou non répertorié (un fichier présent que le manifeste n'a jamais enregistré).

Limite connue — cette vérification compare le SHA-256 de manifest.json à des enregistrements conservés hors du lot, à deux endroits : une chaîne de hachages hors du lot (evidence_anchor.log, chaque ligne s'appuie sur la précédente, en ajout seul lorsque le système de fichiers le permet) et le journal système (étiquette roamswitch-evidence). Réécrire manifest.json après la capture fait échouer verify ; modifier ou supprimer une ligne de la chaîne aussi, car la chaîne se rompt. Cela n'arrête pas un attaquant root capable de réécrire à la fois la chaîne et la destination de transfert du journal système ; il est recommandé de transférer le journal système vers une autre machine. La signature n'est pas implémentée. Un lot sans enregistrement (capturé par une version plus ancienne) est signalé « aucun enregistrement » : il n'a pas de confirmation extérieure.

§5Suite de diagnostics de sécurité à 34 vérifications

L'édition Server exécute 34 audits d'état de sécurité spécialisés :

# Catégorie Vérifier le titre Norme et logique
1Authentification et accèsConnexion racine SSH et authentification par mot de passeVérifie PermitRootLogin no. Signale un accès par clé uniquement lorsque l'authentification par mot de passe est elle aussi désactivée, et valide également le cas où elle est activée tant que la connexion directe en root est interdite
2Authentification et accèsComptes avec mot de passe videsGarantit qu'aucun utilisateur n'a de hachage de mot de passe vide dans /etc/shadow
3Authentification et accèsHygiène Privilège SudoersAudite l'absence de NOPASSWD: ALL, qui accorde tous les privilèges sans mot de passe, dans /etc/sudoers et /etc/sudoers.d
4RéseauRefus par défaut entrantAudite nftables pour la politique de dépôt entrant par défaut
5Noyau et intégritéEspaces de noms d'utilisateurs non privilégiés (Frag Gap)Coupe la voie d'attaque LPE par user.max_user_namespaces=0 (ou kernel.unprivileged_userns_clone=0)
6Noyau et intégritéPrise en charge de l'eBPF BTFValide les informations de type BTF du noyau via /sys/kernel/btf/vmlinux
7Noyau et intégritéeBPF LSM ActifVérifie bpf dans le paramètre de démarrage lsm= du noyau
8RéseauBarrière XDP au démarrageComble, avec une barrière XDP en refus par défaut, la fenêtre de démarrage précédant l'installation de la table nftables de Link Guard (expérimental, activation volontaire, non chargée par défaut). Vérifie la présence du lien épinglé ; l'absence de chargement n'est pas un échec
9Noyau et intégritéAgent de sécurité d'exécution eBPFVérifie qu'un agent comme Falco ou Tetragon est en marche. Tant que Falco tourne, vérifie aussi que l'alerte de la règle propre à RoamSwitch revient (le heartbeat) et échoue sinon (en nommant tout fichier Falco de RoamSwitch manquant)
10Noyau et intégritéChronologie unifiée des incidentsVérifie que la chronologie qui unifie les incidents de Link Guard, Canary, eBPF Guard et Resource Guard peut enregistrer (événements existants ou stockage accessible en écriture) et affiche le dernier temps de confinement
11ConteneurAudit de montage de socket DockerDétecte les montages de conteneurs de /var/run/docker.sock
12ConteneurConteneurs privilégiés DockerAudits pour les charges de travail de conteneurs --privileged à haut risque
13ConteneurIsolation du runtime de conteneurs (runc/gVisor/Kata)Vérifie le runtime de conteneurs par défaut. Un runtime de type runc (partage le noyau de l'hôte) échoue ; gVisor (runsc) ou Kata Containers (isolation par VM et par conteneur) réussit. Sans objet si Docker/containerd n'est pas détecté
14ConteneurProtection contre le contournement du pare-feu par Docker (DOCKER-USER)Vérifie que les règles de RoamSwitch sont bien présentes dans la chaîne DOCKER-USER (toujours évaluée en premier) ou dans sa table nftables dédiée au garde Docker, afin que les ports de conteneurs publiés ne contournent pas le refus par défaut du pare-feu via les règles propres à Docker. Évalué uniquement si protect_docker_ports est activé dans server.conf
15Noyau et intégritéTest de détection ClamAV en conditions réelles (EICAR)Deux minutes après le démarrage puis toutes les 6 heures, le démon analyse un fichier de test EICAR inoffensif depuis son propre environnement (par exemple dans un bac à sable systemd) pour confirmer que ClamAV le détecte réellement. Évalué uniquement si clamav_enabled est activé dans server.conf
16Noyau et intégritéfanotify : événements manqués (débordement de file)Vérifie si le noyau a abandonné des événements de surveillance FIM et d'exécution de conteneurs à cause d'un débordement de la file d'événements fanotify. Les écritures et exécutions de fichiers pendant un débordement échappent à la surveillance en temps réel ; si cela arrive souvent, augmentez fs.fanotify.max_queued_events
17Noyau et intégritéExposition aux CVE connues du noyauCompare la version du noyau en cours d'exécution à la carte locale des CVE connues (référence intégrée ou mise à jour dont la signature a été vérifiée). La vérification elle-même n'effectue aucune communication externe ; les mises à jour de la carte sont récupérées selon cve_kernel_map_updates_enabled (true par défaut, désactivable). Un résultat indéterminable est signalé comme non concluant, et non comme un échec
18IntégritéIntégrité des fichiers du chemin critique (FIM)Vérification d'intégrité SHA-256 de 23 fichiers critiques fixes, de tous les fichiers situés directement dans 12 répertoires, etc.
19Noyau et intégritéProtection des processus YamaPrévient l'espionnage de la mémoire en exigeant que kernel.yama.ptrace_scope vaille 2 ou 3
20RéseauTransfert IP désactivéVérifie net.ipv4.ip_forward = 0
21RéseauProtection des cookies TCP SYNVérifie net.ipv4.tcp_syncookies = 1
22RéseauFiltrage de chemin inverse (rp_filter)Fait rejeter les paquets usurpés en exigeant que net.ipv4.conf.all.rp_filter vaille 1 (strict) ou 2 (souple, pour les configurations multihomed)
23Noyau et intégritéRestriction de vidage de baseEmpêche les fuites de mémoire de processus via fs.suid_dumpable = 0
24OpérationsMises à jour de sécurité automatiquesVérifie la configuration d'application automatique des correctifs par unattended-upgrades (Debian / Ubuntu), dnf-automatic (RHEL / Fedora) et outils similaires
25OpérationsSynchronisation de l'heure NTPVérifie la synchronisation via chrony ou systemd-timesyncd
26OpérationsProtection SSH contre la force bruteRéussi tant que la garde SSH propre à RoamSwitch (ssh_guard) fonctionne et bannit, ou vérifie la surveillance active via fail2ban ou crowdsec
27Noyau et intégritéeBPF non privilégié désactivéPrévient les attaques Spectre/LPE en exigeant que kernel.unprivileged_bpf_disabled vaille 1 ou 2
28RéseauRedirections ICMP ignoréesEmpêche l'empoisonnement de la route via accept_redirects = 0
29RéseauDiffusions d'écho ICMP ignoréesAtténue les attaques DoS de l'amplificateur Schtroumpf
30Noyau et intégritéRenforcement de la mémoire partagéeVérifie que /dev/shm est monté avec au moins l'une des options nodev, nosuid ou noexec
31Authentification et accèsAbsence de protocoles non sécurisés héritésVérifie qu'aucun service en clair à l'écoute (telnetd, rshd, tftpd, …) ni aucun client en clair (telnet, rsh, tftp, …) n'est installé (services et clients sont signalés séparément).
32Noyau et intégritéDumps de mémoire du processus SUID restreintsEmpêche les fuites d'informations d'identification en cas de crash binaire SUID
33Noyau et intégritéProgrammes auxiliaires (leurres et collecte de preuves) installésLe démon lance par leur nom le programme de leurres d'identifiants (honeytokens) et celui des lots de preuves, et ne fait rien, sans le signaler, si l'un manque. Cette vérification contrôle que les deux sont installés.
34RéseauFichiers sensibles accessibles sur le serveur webRepère les racines de documents dans la configuration d'Apache / nginx / Caddy et vérifie, par des requêtes vers le serveur lui-même, si les .git, .env, .DS_Store, phpinfo, dumps SQL, sauvegardes et clés privées qui s'y trouvent sont réellement lisibles via le serveur web. Un fichier n'est jugé exposé que si la réponse correspond au début du fichier sur le disque : un site qui répond 200 à tous les chemins, ou un fichier déjà refusé en 403, ne déclenche donc pas de fausse alerte. Le contenu des fichiers n'est jamais signalé, seulement les chemins. Sans objet sans serveur web ; si la vérification est impossible, le résultat est « indéterminé » et le score n'est pas réduit.

Chacun des 34 éléments est associé, le cas échéant, à un identifiant de fonction/catégorie NIST CSF 2.0 (par ex. PR.DS-01, DE.CM-09) et, uniquement lorsqu'une correspondance fiable existe, à un numéro de contrôle CIS Controls v8 (par ex. 10.1, 4.1). Les éléments ambigus ne sont pas forcés dans une correspondance et restent non associés. Cette cartographie figure aussi bien dans la sortie de la CLI (roamswitch server status) que dans celle de MCP (get_security_report), permettant aux opérateurs et auditeurs soumis à des exigences de conformité de rattacher mécaniquement les contrôles de RoamSwitch aux référentiels existants.

§6Auto-vérification (vérification reproductible dans un environnement Docker)

Une auto-vérification simulant des attaques internes et externes est exécutée contre le paquet roamswitch-server installé depuis le dépôt APT officiel, avec des conteneurs Docker distincts dans le rôle de l'attaquant externe. La mesure du 2026-09-07 sur la v1.3.2 donnait les 18 points PASS. Le 2026-09-25, la suite a été relancée sur un paquet construit à partir des sources 1.10 (arm64, Docker Desktop) et 19 vérifications ont réussi. Cette nouvelle exécution a révélé six défauts du produit, tous corrigés (inclus dans la 1.10.5) : le paquet ne contenait pas les binaires auxiliaires, la FIM signalait les leurres honeytoken comme de nouveaux fichiers, un acquittement (ack) était ignoré, pendant l'isolement les réponses du SSH d'administration étaient abandonnées et l'administrateur se retrouvait verrouillé dehors, le port 22 restait ouvert même avec preserve_ssh_on_isolation=false, et le bac à sable de l'unité systemd bloquait l'écriture des leurres. Vous pouvez reproduire cette vérification sur votre propre machine. Voir la procédure et les scripts dans « Comment la reproduire vous-même » ci-dessous.

# Scénario Vecteur d'attaque Résultat
PENT-S1 Nmap SYN Furtif Scan Sonder les ports 22, 80, 443, 3306, 8080 Avec le refus par défaut de nftables, les ports non autorisés tels que 3306 (MySQL) et 8080 ne répondent pas du tout et les paquets sont abandonnés (Filtered). 100 % bloqués
PENT-S2 Sonde de port non autorisée curl connexions directes aux ports 3306 et 8080 Avec le refus entrant par défaut, seul le port autorisé 80 répond normalement ; tous les autres expirent (bloqués)
PENT-S3 Liste de blocage Egress / C2 Tentative de connexion sortante vers une adresse enregistrée dans le flux d'IP malveillantes Seule l'adresse enregistrée est bloquée par egress_ip_blocklist_enabled (true par défaut, nftables) ; le trafic vers un hôte non enregistré n'est pas affecté (ce n'est pas un blocage général)
PENT-S4 Attaque de falsification FIM Injecter la charge utile de la porte dérobée dans /bin/login Le FIM Critical Path SHA-256 détecte immédiatement l'altération. Après une restauration légitime, la référence est correctement resynchronisée
PENT-S5 Confinement basé sur la gravité via guard.yaml Injection d'un événement Falco Critical référençant un processus réel Avec on_critical: isolate_process + kill_process, le processus concerné est isolé du réseau puis terminé par SIGKILL
PENT-S6 Garde-fou de sécurité pour les processus protégés Injection d'un événement de même type référençant le vrai PID de sshd La liste des processus protégés (sshd, systemd, runtimes de conteneurs, etc.) maintient sshd en fonctionnement. Seul un refus est consigné, comme faux positif
PENT-S7 Minuteur de sécurité Incident Air-Gap laissé sans accusé de réception (aucun ACK de l'opérateur) Grâce à safety_timer_secs, l'hôte est restauré automatiquement en l'absence de réponse. Une fois l'ACK donné, l'isolement persiste jusqu'à emergency-restore
PENT-S8 Écart de fragmentation LPE du noyau Exécution de unshare -U -r avec les droits d'un utilisateur non privilégié (vérifiée sur une VM réelle) unshare -U -r, autorisé sans le produit, est refusé après le démarrage. user.max_user_namespaces et kernel.unprivileged_userns_clone valent 0, le ptrace_scope de Yama vaut 2 et l'eBPF non privilégié est désactivé (2026-09-25, VM Ubuntu 24.04, vérifié à la fois sur la version publique v1.10.4 et sur la version corrigée)

Remarque : la mesure de PENT-S7 date de la v1.3.2. Dans la version actuelle, un isolement déclenché avec une confiance élevée est levé une fois sa cause vérifiée comme disparue, et un isolement acquitté (ack) par l'opérateur est maintenu jusqu'à ce que l'opérateur le lève (un ack ignoré a été trouvé et corrigé lors de la nouvelle exécution du 2026-09-25). Les déclencheurs de faible confiance se rétablissent avec la même minuterie. Voir §4.

Nature des vérifications — PENT-S8 confirme qu'une fonction du noyau (interdiction des espaces de noms utilisateur) est active. PENT-S1 et S2 confirment les règles nftables installées par RoamSwitch ; c'est le noyau qui rejette réellement les paquets. Les points qui exercent la logique propre de RoamSwitch sont PENT-S3 (application du flux), S4 (FIM), S5 et S6 (confinement et processus protégés) et S7 (minuterie de sécurité).

Prévention du verrouillage SSH

preserve_ssh_on_isolation=true (par défaut) est conçu pour conserver le canal SSH d'administration pendant un Air-Gap (on_emergency). La nouvelle exécution du 2026-09-25 a toutefois montré que, jusqu'à la 1.10.4, les règles d'isolement abandonnaient les réponses SSH, de sorte que l'opérateur était verrouillé dehors pendant l'isolement ; la vérification précédente n'avait réussi qu'après la levée de l'isolement. Le défaut est corrigé dans la 1.10.5 ; il a été vérifié, la cause de l'isolement persistant, que SSH fonctionne pendant l'isolement.

Limites du périmètre de test (une divulgation honnête)

Ce que cela ne permet pas de vérifier

Le durcissement du noyau (désactivation des espaces de noms utilisateur non privilégiés, augmentation du ptrace_scope de Yama) ne peut pas être vérifié de bout en bout depuis l'intérieur d'un conteneur. Ces sysctl n'étant pas isolés par espace de noms au niveau du conteneur, les écritures effectuées depuis un conteneur privilégié (--privileged) affectent non pas un bac à sable de test, mais la machine réelle exécutant le test elle-même. Cette vérification confirme donc uniquement que l'écriture sysctl se dégrade proprement (sans plantage) face à un système de fichiers en lecture seule ; confirmer que la valeur est réellement appliquée nécessite une machine réelle ou une VM, conforme à l'environnement d'exécution prévu pour le produit. Cette confirmation n'a été faite qu'une seule fois, le 2026-09-25, sur une VM Ubuntu 24.04 (voir la ligne PENT-S8).

Reproduisez-le vous-même

Tous les résultats ci-dessus peuvent être reproduits à 100 % sur votre propre machine, en utilisant uniquement le paquet de distribution officiel, grâce à la suite de tests Docker publiée dans le dépôt de support officiel (roamswitch-support) — aucun accès au code source ni confiance envers nous ne sont nécessaires.

# Récupérer et reproduire la suite de vérification (installe automatiquement roamswitch-server depuis le dépôt APT officiel)
git clone https://github.com/lafine1211/roamswitch-support.git
cd roamswitch-support/test/docker/server
./run.sh

Crée trois conteneurs jetables (Target, Attacker et un leurre C2) sur le réseau bridge par défaut de Docker, vérifie automatiquement les 18 points et produit un rapport. Le résultat complet de l'exécution sur la v1.3.2 est publié à l'adresse RESULTS-PENTEST-SERVER-2026-09-07.ja.md (incluant, par souci de transparence, les problèmes du banc de test lui-même détectés et corrigés lors de sa construction). Le compte rendu de la nouvelle exécution du 2026-09-25 se trouve dans RESULTS-PENTEST-2026-09-25.ja.md.

§7Opérations, déploiement et cycle de vie

7.1 Assistant d'outillage et de configuration de l'interface CUI sans tête

Gestion à distance complète via CLI :

  • roamswitch server status : produit un rapport de diagnostic de 34 points (10 langues prises en charge, selon le réglage de l'utilisateur qui l'exécute ou la locale du système).
  • roamswitch server setup : Assistant de terminal interactif pour les ports et les notifications.
  • roamswitch server config show / set <key> <val> : automatisation scriptable pour Ansible/Terraform.
  • roamswitch server test-notify [all|telegram|line|webhook] : sonde de notification de bout en bout.
  • roamswitch server config set language <ja|en|zh-Hans|zh-Hant|ko|de|fr|es|it|pt|system> : la langue du rapport de diagnostic périodique du démon (toutes les heures) et des notifications push (Telegram / LINE / webhook, message de test compris), une langue par hôte. system suit la locale du système et une locale non reconnue donne ja. La langue d'affichage de roamswitch server status ne suit pas ce réglage, mais celui de l'utilisateur qui l'exécute ou la locale du système.
  • roamswitch server bans [--json] / sudo roamswitch server unban <IP|/64 IPv6|--all> : état de la garde contre la force brute SSH, liste et levée de ses bannissements. L'outil MCP get_ssh_guard_incidents est en lecture seule et ne peut pas lever un bannissement.

TUI Server (roamswitch server tui)

Une interface terminal qui permet de piloter toute l'édition Server via SSH, en 80x24 et au seul clavier (roamswitch server tui ou roamswitch-server-tui). Elle comporte 11 écrans : tableau de bord, protection, incidents, notifications et transfert, Sensor, configuration, service, outils, audit des journaux, informations et aide, et Fleet. L'éditeur de configuration est généré à partir des définitions de clés (le schéma) de server.conf et guard.yaml : il valide le type, la plage et les valeurs énumérées, indique si un redémarrage est nécessaire, montre un diff avant l'enregistrement et enregistre avec une sauvegarde horodatée et une écriture atomique (les valeurs secrètes comme les jetons sont masquées et saisies sans écho). Les opérations à fort impact, comme la restauration d'un isolement, l'ACK, la mise à jour de la référence FIM ou l'arrêt du service, affichent un écran de confirmation énonçant l'effet, et les plus lourdes exigent la saisie de yes (confirmation saisie). Sans root, elle est en lecture seule et indique la commande sudo roamswitch ... nécessaire. Il n'y a pas de rechargement de configuration ; un redémarrage du service est nécessaire pour la prise en compte. L'affichage prend en charge 10 langues.

7.2 Emballage

Fourni sous forme de paquets .deb pour Debian / Ubuntu et de paquets .rpm pour RHEL / Fedora et pour openSUSE.

§8Conclusion

RoamSwitch Server Edition fournit aux infrastructures Linux cloud et entreprise, dans un seul paquet intégré, « Zero Telemetry (la seule communication externe par défaut est une réception de données signées, sans envoi, que l'on peut désactiver) », « une protection périmétrique solide par refus entrant par défaut », « la détection immédiate des altérations des chemins critiques » et « le blocage préventif de l'élévation de privilèges du noyau ».

Une suite de sécurité pour serveurs Linux qui associe détection en couches et confinement autonome (Air-Gap) pour les principales étapes visées par les attaquants : reconnaissance initiale, intrusion, élévation de privilèges et persistance.

Ce document reflète l'implémentation de RoamSwitch Server Edition v1.11.1 (refus entrant par défaut, FIM des chemins critiques, moteur de politiques guard.yaml, durcissement LPE du noyau, surveillance d'exécution eBPF, levée d'Air-Gap par paliers, enregistreur d'exécutions et transfert d'événements, TUI Server, détection de tunnellisation DNS, honeytokens, capture de preuves forensiques, cartographie NIST CSF / CIS Controls et mise à jour de la carte CVE du noyau signée). Les mesures d'auto-vérification du §6 proviennent toutefois de la v1.3.2 (2026-09-07) et d'un paquet construit à partir des sources 1.10 (2026-09-25). Consultez le CHANGELOG de roamswitch-support pour l'historique détaillé des modifications. Pour les dernières informations, voir roamswitch.com/linux.