lalahop
Categorie: Administration réseau

Cet article relate la mise en œuvre d'un réseau Wi-Fi sécurisé et monitoré du type de ce que l'on peut trouver dans une société commerciale, une université ou une association. Pour un point d'accès Wi-Fi ouvert à domicile, je recommande plutôt cet excellent tutoriel : Mise en place d'un réseau Wi-Fi ouvert - Emile (iMil) Heitor.

Même si cette mise en œuvre se situe dans un cadre scolaire (« bouh c'est nul, on ne fait pas comme ça en vrai ! » diront certains), je pense qu'elle vaut la peine d'être partagée et d'être lue car les technos étudiées (RADIUS, 802.1X, radvd/dhcpv6, etc.) sont intéressantes et que les problématiques posées sont celles que l'on rencontre dans de vrais déploiements : RADIUS, limites de l'accounting RADIUS dans un réseau Wi-Fi 802.1X, problématiques légales autour des données de connexion, cohabitation entre SLAAC et DHCPv6, etc.

Ce travail a été réalisé avec Hamza Hmama.

Ce travail datant d'il y a 3 ans, je l'ai actualisé avant publication, quand j'ai estimé que c'était nécessaire. Par exemple, des situations ont évolué (comme umip), j'ai un peu de recul, etc.

Je remercie Christian pour le prêt longue durée d'un WRT54GL. 🙂 Merci à bcollet pour le don d'un Cisco Aironet 1120B. 🙂

Table des matières

Énoncé

Malheureusement, j'ai jeté l'énoncé à la poubelle lors d'un déménagement et je n'en ai pas gardé une copie numérique. Mais, en gros, il nous a été demandé de :

  • Mettre en place un hotspot Wi-Fi avec une authentification UAM (un portail captif, quoi) et une authentification 802.1X, similaire aux réseaux Wi-Fi universitaire OSIRIS et OSIRIS-sec déployés par la direction informatique de l'université sur tout le campus ;
  • Chaque client Wi-Fi doit avoir une connectivité IPv4 et IPv6 ;
  • Produire des logs et mettre en place une solution de supervision qui fait remonter des informations pertinentes (état de la machine passerelle, état des liaisons, trafic, types de trafic, etc.) ;
  • Aucune solution de filtrage (de sites web, de ports, de noms de domaine,…) ou de traçabilité (qui a consulté telle ressource à telle heure) n'a été demandée. En conséquence, nous n'avons installé aucun serveur proxy ou autres outils ;
  • Configurer un mobile node IPv6 sur une machine cliente de notre point d'accès Wi-Fi ;
  • Nous verrons tout au long de cet article que des consignes et des recommandations informelles ont été ajoutées par le prof' tout au long de ce TP.

Ce sujet est celui de Thomas Noël, de l'université de Strasbourg, dans le cadre du cours « Mobilité et réseaux sans fil ».

Choix

Panorama des choix que nous avons faits.

Machine routeur / passerelle

Il s'agit de la machine qui fait la jonction entre le réseau filaire et les réseaux sans fil et sur laquelle nous allons installer le portail captif, le serveur RADIUS, stocker les données de connexion, etc.

Afin de pouvoir avancer ce projet durant les vacances universitaires de Toussaint, d'être indépendants des séances de TP et de la disponibilité de la salle de TP, nous avons fait le choix de travailler avec l'un de nos ordinateurs personnels. Il s'agit d'un ordinateur portable standard. Le handicap principal est qu'il est équipé d'une seule prise réseau, nous y reviendrons.

Comme il était hors de question de reformater cet ordinateur pour créer une partition dédiée à ce projet (manque d'espace), ou d'installer tout et n'importe quoi sur notre système stable que nous utilisons au quotidien, ou d'utiliser la virtualisation qui peut parfois se montrer pénible lorsqu'il s'agit de prolonger des VLANs à l'intérieur de la machine virtuelle, nous avons décidé d'installer un système GNU/Linux Debian testing dédié à ce projet sur une clé USB bootable.

Point d'accès Wi-Fi / AP

Là aussi, afin de pouvoir avancer ce projet durant les vacances universitaires de Toussaint, d'être indépendants des séances de TP et de la disponibilité de la salle de TP (dans laquelle étaient stockées les AP 😉 ) et des points d'accès Wi-Fi eux-mêmes, nous avons dû chercher un moyen de nous émanciper des bornes Cisco fournies par l'université.

Dans un premier temps, nous avons utilisé hostapd, un logiciel libre qui permet la création de points d'accès Wi-Fi (en mode infrastructure) sur un ordinateur équipé d'une carte réseau compatible. Nous avons passé du temps à mettre en place un SSID sécurisé (802.1X) fonctionnel avant de nous rendre compte que la carte réseau Wi-Fi (une Atheros AR9285 😉 ) de notre machine personnelle supporte la création d'un seul SSID tout au plus en simultané.

Note : si les drivers sont compatibles, il est possible de savoir combien de SSID une carte réseau peut gérer dans tel mode en utilisant la commande « iw list » et en regardant « Supported interface modes » et « valid interface combinations » dans la sortie. Pour les tutoriels qui vont bien, voir : création d'un point d'accès Wi-Fi sur son ordinateur avec hostapd dans la doc' Ubuntu et Multiple SSIDs with hostapd.

Finalement, notre choix s'est porté sur un point d'accès Cisco Aironet 1120B de récupération. Bien que ce modèle présente quelques limitations (802.11b uniquement, pas de support de WPA2 ni de CCMP, notamment), il est amplement suffisant pour mener à bien ce projet universitaire.

Portail captif

Avant de rechercher les différents portails captifs existants, nous avons établi les critères d'évaluation et de comparaison suivants (du plus important au moins important) :

  • Il doit être libre et gratuit ;
  • Il doit être basé sur GNU/Linux ou *BSD ou être un logiciel qui fonctionne sur un système d'exploitation faisant partie de ces deux familles ;
  • Il doit gérer IPv6 avec les mêmes fonctionnalités qu'IPv4 et notamment en ce qui concerne la redirection et l'authentification. De 0 (pire) à 2 (meilleur) :
    • 0 : aucune prise en charge d'IPv6 ;
    • 1 : prise en charge expérimentale ou partielle (exemple : pas de redirection vers le portail captif en IPv6 donc obligation de s'authentifier en IPv4) ;
    • 2 : prise en charge complète d'IPv6, y compris pour effectuer la redirection vers le portail captif.
  • Il doit être compatible avec notre machine : nous n'avons pas de machine à consacrer à l'installation d'une solution ni l'envie de nous coltiner les problèmes d'interaction avec les fonctions avancées des réseaux que l'on retrouve avec les machines virtuelles. En conséquence, la solution retenue doit être bootable sur une clé USB ou être compatible Debian. « Oui » : la solution répond à cette exigence, « Non » sinon ;
  • Il doit être maintenu et documenté. De 0 (pire) à 2 (meilleur) :
    • 0 : la solution est un projet abandonné et peu documenté ;
    • 1 : la solution est suffisamment documentée et le projet est maintenu de manière approximative ;
    • 2 : le projet est encore maintenu et toute la documentation nécessaire est à disposition ;
  • Il doit être facile à administrer après la configuration initiale. « Oui » : la solution dispose d'une interface web de configuration qui donne accès à tous les paramètres nécessaires pour mener à bien ce projet ainsi qu'à des stats pertinentes, « Non » sinon.

Lors d'une première passe, nous avons éliminé les solutions suivantes car elles ne sont pas gratuites ou pas libres : Air Marshal, Net4Guest, Aradial, Cloudessa, Cloud4Wi, DNS Redirector, FirstSpot, LogiSense, Amazingports, Ecnex, Untangle.

Nous avons appliqué nos critères précédemment énoncés sur les solutions restantes. En voici une synthèse :

Nom IPv6 Compatibilité Documentation Facilité d'administration
ALCASAR Non ** Oui
Chillispot Oui Non
Coovachili *

** Non
M0n0wall Non * Oui
PacketFence Oui ** Oui
PepperSpot ** Oui ** Non
PfSense * Non ** Oui
Talweg Oui Non
Wi-Fidog, NoCat Non * Non
Zentyal Non * Oui
Zeroshell Non ** Oui

On constate que seulement trois portails captifs se démarquent par une prise en charge (parfois minimale) d'IPv6 : Coovachilli, PfSense et PepperSpot. Coovachilli et PfSense disposent toutefois d'une prise en charge expérimentale incomplète : il n'y a pas de redirection pour authentification en IPv6.

La seule limite que nous avons trouvée à PepperSpot est l'utilisation d'une fenêtre pop-up qui doit rester ouverte pour que la machine reste authentifiée sur le réseau. Cela n'est pas pratique avec un ordiphone.

En conclusion, nous avons choisi le portail captif PepperSpot.

Si c'était aujourd'hui (en octobre 2016), je conseillerais de regarder attentivement du côté de PfSense qui est une solution tout intégrée qui a l'air d'avoir bien évolué depuis ce travail. Il n'était pas stable à l'époque (genre tes paramètres pouvaient disparaître alors que c'était tout bien configuré lors de la dernière utilisation), il l'est devenu, semble-t-il. En revanche, je ne sais pas s'il gère la redirection pour authentification en IPv6 ou s'il ne gère toujours pas cela.

Toujours en 2016, avec du recul, je fais remarquer qu'une solution toute intégrée est moins susceptible de vous faire progresser qu'une solution où il faudra installer, configurer et comprendre chaque composant qui constitue l'architecture finale. Dans un TP universitaire, j'ai tendance à penser qu'on doit tout décortiquer et comprendre, quitte à présenter une solution moins jolie visuellement ou moins ergonomique.

Utiliser le portail captif en tant que proxy RADIUS ?

Une connexion sécurisée (802.1X) à notre hotspot se fera via un autre SSID et l'authentification ne passera pas par PepperSpot qui peut être un proxy RADIUS.

Nous n'utiliserons pas cette fonctionnalité : notre point d'accès Cisco contactera directement notre serveur RADIUS pour authentifier les clients, principalement car l'utilisation d'un proxy RADIUS ajoute de la complexité inutile donc un risque de panne supplémentaire qui peut s'avérer difficile à débugger). Sans compter que bien séparer les deux réseaux, y compris pour l'authentification, nous permet de mieux répartir le travail entre nous : on installe le RADIUS et chacun de nous peut travailler sur l'un des SSID.

SSIDs

Ce n'est pas le plus important donc nous avons fait dans la simplicité (l'idée étant aussi d'éviter les doublons lors de la séance de TP dédiée à l'évaluation) :

  • SSID pour le réseau avec le portail captif : TESTAP ;
  • SSID pour le réseau avec l'authentification 802.1X : TESTAP-sec.

Adressage IP

IPv6
Allocation

En IPv6, la notion de rareté n'existe pas : la salle de travaux pratiques, comme d'autres, est irriguée par un /64 qui ne sera jamais saturé. Dans ce contexte, il convient de mener une réflexion sur l'adressage à déployer.

Dans un vrai déploiement, nous aurions la main sur les routeurs ascendants et nous pourrions donc router deux /64 distincts en direction de notre machine qui héberge notre portail captif.

Dans notre cas, deux pistes apparaissent : utiliser l'allocation d'OSIRIS ou utiliser une autre allocation.

La première méthode, utiliser l'allocation d'OSIRIS, implique de laisser passer toute la signalisation ICMPv6 (router solicitation, router advertisement, neighbor solicitation, neighbor advertisement, principalement). Cela peut être fait de deux manières :

  • Au niveau 2, en utilisant un bridge. L'inconvénient majeur est que cela nuit à la compartimentation que nous souhaitons entre nos différents types d'accès (portail captif, accès sécurisé 802.1X) : le trafic multicast (IPv6, IPv4) et broadcast (IPv4), y compris celui généré par nos camarades, sera propagé sur nos deux réseaux Wi-Fi. Sans compter que cela complexifie probablement la configuration initiale de PepperSpot (il faut garantir qu'aucun paquet ne traverse le bridge avant que l'utilisateur ait été authentifié. Or, c'est le rôle même d'un bridge que de tout laisser passer).
  • En laissant la machine hébergeant notre portail captif en coupure du réseau. Mais, comme nous n'avons pas la main sur le routeur qui annonce l'allocation, il faut un réseau à plat. Cela implique l'utilisation de la fonctionnalité de proxy NDP. D'abord manuelle, cette fonctionnalité peut être automatisée avec des logiciels spécialisés tels que ndppd. L'inconvénient majeur de cette approche est qu'elle complexifie notre installation au détriment de la facilité d'administration sans pour autant offrir des garanties en terme d'efficacité.

Dans les deux cas, nous utilisons la même allocation (le même /64) pour nos deux types d'accès (portail captif et accès sécurisé), ce qui n'est pas optimal d'un point de vue de la compartimentation.

La deuxième méthode (utiliser une autre allocation que celle d'OSIRIS, pour rappel) peut être décomposée ainsi :

  • Utiliser une allocation locale unique (fc00::/7) ou réservée pour du benchmark et mettre en place du NAPT. Cette technique a fait son apparition dans la version 3.7 de Linux (donc elle est disponible dans Debian stable). En plus d'être controversée (au motif que ce n'est pas top de reproduire les erreurs commises avec IPv4), elle ne permettra pas l'usage de la mobilité IPv6, autre point de ce TP, sauf à utiliser des méthodes de contournement basées sur UDP (exemple) qui ne sont pas forcément prises en charge par toutes les implémentations… ;
  • Utiliser 6in4, soit une encapsulation d'IPv6 sur IPv4. Ça fonctionne tout aussi bien en utilisant un VPN (OpenVPN, IPSec, etc.) qui fournit une connectivité IPv6, testé et approuvé en 2016. Attention si vous utilisez un tunnel de type tun : le nom peut entrer en conflit avec PepperSpot. Avec OpenVPN, il est possible de changer le nom de la tun avec la directive de configuration suivante : « dev tun ».

Nous avons retenu cette dernière méthode car elle nous semble plus propre, plus étanche et moins complexe : un tunnel prit chez Hurricane Electric route un /48 sur notre passerelle. De là, un /64 est alloué à chaque réseau (portail captif et accès sécurisé 802.1X) : 2001:470:c8d6:2::/64 pour TESTAP, 2001:470:c8d6:3::/64 pour TESTAP-sec.

Autoconfiguration stateless ou autoconfiguration stateful ?

En IPv6, l'annonce du routeur de sortie du réseau se fait exclusivement via les messages de contrôle (ICMPv6) « Router Advertisement » (RA). L'attribution des adresses (et d'autres paramètres comme les serveurs DNS récursifs à utiliser) peut se faire de deux manières : stateless (SLAAC) ou stateful (DHCPv6).

La difficulté vient de la différence de prise en charge par les différents systèmes d'exploitation, même les plus récents. En effet, si tous gèrent l'annonce du routeur de sortie via les messages RA (puisque c'est obligatoire dans la norme), tous ne prennent pas en charge, en standard, l'annonce des serveurs DNS récursifs par ce biais (option RDNSS). Avec de tels systèmes, on n'obtient pas une configuration dual stack : si IPv4 ne fonctionne plus, alors le serveur DNS récursif est injoignable et IPv6 reste dépendant d'IPv4, ce qui n'est pas l'objectif de ce TP, à notre avis.

DHCPv6 et les messages RA peuvent cohabiter selon deux modes :

  • Mode « managed » : le message RA sert à indiquer exclusivement l'adresse du routeur de sortie. L'attribution des adresses est déléguée à un serveur DHCPv6 ;
  • Mode « other configuration » : le message RA sert à indiquer l'adresse du routeur de sortie ainsi que le préfixe du réseau afin que les machines puissent s'autoconfigurer sans utiliser de serveur DHCPv6. Néanmoins, les machines qui supportent DHCPv6 peuvent effectuer une telle requête afin d'obtenir des informations d'autoconfiguration complémentaires (comme les adresses des serveurs DNS récursifs à utiliser).

Notons que ces modes de fonctionnement diffèrent simplement par des drapeaux à positionner dans les messages RA.

Sur notre réseau TESTAP-sec, nous utilisons le mode « other configuration » ainsi qu'un serveur DHCPv6, ce qui permet de prendre en compte un maximum de cas :

  • Si le système d'exploitation prend en charge RDNSS et DHCPv6, alors il peut interroger le serveur DHCPv6 pour consolider les informations complémentaires (les adresses des serveurs DNS récursifs, par exemple) obtenues dans le message RA (RFC 4862, section 5.6) ;
  • Si le système d'exploitation ne prend pas en charge RDNSS mais prend en charge DHCPv6 (exemples : Windows XP-8, Windows Phone 6.5-8, Solaris, etc.), il interrogera le serveur DHCPv6 pour connaître les adresses des serveurs DNS récursifs (et les autres informations complémentaires) à utiliser ;
  • Si le système d'exploitation ne prend pas en charge DHCPv6 mais prend en charge RDNSS (exemples : MeeGo, (Free|Open)BSD, quelques systèmes GNU/Linux selon la configuration par défaut, etc.), il récupérera les adresses des serveurs DNS récursifs à utiliser directement dans les messages RA ;
  • Si le système d'exploitation ne prend en charge ni RDNSS ni DHCPv6 (exemples : Android, Symbian), alors la résolution des noms de domaine est dépendante d'IPv4. À l'impossible, nul n'est tenu.

En 2016, je pense qu'il faudrait actualiser cette liste : je serai surpris qu'Android n'implémente pas au moins RDNSS.

Sur notre réseau TESTAP, nous utilisons uniquement les messages RA : DHCPdv6 ne peut pas se binder sur l'interface réseau testap car elle n'a pas d'IP (PepperSport l'attribue à une tun qu'il crée en démarrant) et il ne peut pas non plus se bind sur une interface point-à-point (la tun créée par PepperSpot), puisque DHCP est un protocole broadcast/multicast par essence.

IPv4

En IPv4, la question de l'allocation à utiliser ne se pose pas : en raison d'un manque d'adresses, il n'est pas possible d'attribuer une IPv4 provenant d'OSIRIS (réseau de l'université) à chacun de nos clients Wi-Fi. On a donc recours à des adresses privées et à du NAPT.

Dans notre cas, nous avons décidé d'utiliser deux /24 découpés dans le bloc 198.18.0.0/15 réservé à l'IANA pour les tests/benchmarks : 198.18.0.0/24 servira à adresser notre réseau TESTAP et 198.18.1.0/24 servira à adresser notre réseau TESTAP-sec.

Notons que nous utilisons 198.18.255.0/31 pour adresser le lien d'administration de notre point d'accès Wi-Fi.

Les blocs réservés pour un usage privé seraient plus appropriés mais nous voulions impérativement éviter les collisions avec des adressages existants (typiquement à nos domiciles respectifs).

Gestion du RADIUS

Deux consignes informelles ont été ajoutées en cours de route. La première est que le RADIUS doit récupérer et stocker les informations sur nos utilisateurs dans une base de données genre MySQL. La deuxième, qui découle de la première, est que la gestion de cette base doit se faire avec une interface web : il n'est pas question d'ajouter ou de supprimer un utilisateur ni de consulter l'accounting avec des bouts de scripts ou manuellement.

Pour ce faire, nous avons choisi le logiciel daloRADIUS. Nous avions aussi identifié dialupadmin par les auteurs de FreeRADIUS mais son interface web est un peu rustique à notre goût.

De toute façon, aucun des logiciels libres de gestion des données d'un serveur RADIUS que nous avons identifiés ne permet nativement de travailler avec les IPv6 attribuées aux utilisateurs de nos réseaux Wi-Fi. Mais, ce n'est pas vraiment un problème puisque, pour notre réseau TESTAP-sec, nous verrons que notre AP Cisco ne communique pas les IP d'un utilisateur dans ses requêtes d'accounting. Pour notre réseau TESTAP, PepperSpot autorise l'utilisateur à utiliser uniquement une IPv6 strictement dérivée de son adresse MAC. Dans ce contexte, l'accounting IPv6 est inutile : la MAC est dans l'adresse IPv6 (avec un ff:fe en plus en plein milieu, certes). Donc, il suffit d'extraire la MAC à partir de l'IPv6 et de rechercher l'identifiant à partir de cette adresse-là (qui est stockée dans l'accounting RADIUS).

Après coup, nous nous sommes rendu compte que le code de dalORADIUS laisse à désirer : des fonctionnalités présentes sur l'interface web n'ont aucun code derrière (exemple : le stockage d'un mot de passe utilisateur haché avec SHA1), le code est doublonné (code identique dans plusieurs pages) ce qui rend difficile l'ajout d'une fonctionnalité à l'existant, les requêtes de comptage du nombre de lignes présentes dans la BDD sont insensées, la cohérence du code (pour une même action, les traitements diffèrent parfois d'une page à une autre) laisse à désirer, aucune convention de codage, etc.

Mécanismes d'authentification 802.1X

Pour l'authentification, 802.1X utilise EAP qui est un protocole générique de transport de méthodes d'authentification (voir http://www.bortzmeyer.org/3748.html). Parmi toutes les méthodes d'authentification disponibles, nous avons choisi d'utiliser EAP-TTLS-(PAP|CHAP) sur notre SSID sécurisé.

TTLS (Tunneled TLS) se justifie car :

  • TTLS est un mécanisme fiable et normalisé à l'IETF ;
  • LEAP est un mécanisme propriétaire de Cisco qui est vulnérable depuis plus de 10 ans ;
  • PEAP est similaire à TTLS mais les mécanismes d'authentification utilisables dans le tunnel sont faibles : MSCHAPv2 et MD5. MD5 n'est plus considéré comme sûr depuis plus de 10 ans. Nous allons évoquer MSCHAPv2 ci-dessous ;
  • TLS est trop contraignant car il faut réaliser une authentification du client avec un certificat x509 en plus du certificat x509 du serveur, ce qui nuit à un usage à grande échelle ;
  • Les autres méthodes sont peu implémentées/utilisées.

Pour réaliser l'authentification à l'intérieur du tunnel TLS au-dessus d'EAP, plusieurs mécanismes sont possibles :

  • MSCHAP(v2), une implémentation de CHAP réalisée par Microsoft qui est totalement trouée de nos jours puisqu'il repose sur l'algorithme cryptographique DES et ses clés de 56 bits ;
  • MD5, qui n'est plus considéré comme sûr pour des opérations cryptographiques. Certes, on est à l'intérieur d'un tunnel TLS mais son nom est trop connu : son utilisation ici pourrait faire croire à nos utilisateurs qu'il est un algorithme potable et conduire à des erreurs d'appréciation dans leurs projets (pouvoir de prescription) ;
  • CHAP, qui a recours à une fonction de hachage, ce qui rajoute une surcouche inutile à un tunnel TLS bien négocié, à notre avis, mais rien n'empêche de l'utiliser ;
  • PAP, qui est un mécanisme simple qui envoie l'identifiant et le mot de passe en clair dans le tunnel TLS. Pas de protection contre les rejeux comme en propose CHAP mais TLS s'occupe aussi de cela.

C'est pourquoi nous avons choisi de tolérer PAP et CHAP comme mécanismes d'authentification au sein de nos tunnels EAP-TTLS, sur notre réseau TESTAP-sec.

Supervision et métrologie

Note de 2016 : dans un vrai déploiement, il faudrait plutôt ajouter notre service Wi-Fi à la supervision existante et ne pas déployer des outils de supervision/métrologie sur la passerelle du réseau Wi-Fi comme nous allons le faire. Néanmoins, c'était l'une des consignes du TP et je pense qu'elle a pour but de nous faire mettre en œuvre une solution de supervision… même si une autre matière du programme, administration des réseaux (si ma mémoire est bonne), poursuit le même objectif.

Supervision

Nous utilisons Monit pour superviser les services cruciaux de notre infrastructure :

  • Point d'accès Cisco : ping et SNMP ;
  • Apache : processus lancé et tentative de requête ;
  • Freeradius : processus lancé et tentative de requête ;
  • MySQL : processus lancé et tentative de requête ;
  • Rsyslogd : processus lancé ;
  • NTPd : processus lancé et tentative de requête ;
  • DHCP(v6) : processus lancé (Monit ne supporte pas ce protocole pour une tentative de requête).

Nous avons choisi Monit pour sa simplicité d'où il ne va pas bombarder notre clé USB bootable d'IO. Il suffit amplement pour atteindre l'objectif fixé par ce TP. Et j'écris ça avec du recul, après avoir touché à du Zabbix, à du Centreon et à du Icinga (1.X et 2.X) (tous des usines à gaz) dans mes expériences pros et associatives depuis ce TP. Un avantage de Monit, c'est qu'il vérifie facilement, sans trop de configuration, que des processus sont en cours d'exécution. Utile pour superviser rsyslog sans faire entrer des données prédictibles dans les logs, par exemple.

Monit est un logiciel de monitoring extrêmement limité, aussi bien en terme de protocoles supportés qu'en terme de tests effectués : le fait qu'un processus soit lancé ne signifie pas forcément qu'il va servir la requête d'un utilisateur ; envoyer un paquet générique pour vérifier qu'un port est ouvert est insuffisant. Illustration (hors cadre de ce TP) : tester qu'un serveur DNS qui fait autorité répond est insuffisant : il faut comparer les serials de tous les SOA de tous les serveurs qui font autorité sur une même zone, entre autres choses. Néanmoins, Monit est suffisant et cohérent dans le cadre de ce projet. Notons néanmoins que la version de Monit packagée dans Debian Jessie n'est pas compatible IPv6…

La principale limite de notre infrastructure de supervision est qu'elle teste les services indépendamment les uns des autres. Mais ce n'est pas parce qu'on vérifie qu'Apache sert bien la page de notre portail captif que ça signifie qu'il n'y aura aucun problème lors de la soumission d'un couple identifiant/mot de passe. Pour être sûr que toute notre infrastructure fonctionne, il faudrait tester la chaîne de bout en bout : connexion sur chaque SSID, authentification, envoi de trafic vers une mire choisie en v4 et en v6, etc. À notre connaissance, aucun logiciel n'existe pour faire un tel test de bout en bout.

Métrologie

Nous utilisons Munin pour grapher les composants de la machine qui héberge notre portail captif : charge CPU, utilisation mémoire, nombre de processus, entrées/sorties sur les périphériques de stockage, température, etc. Autant de facteurs qui peuvent révéler des anomalies en cours ou passées (puisque nous avons un historique). De même, nous graphons le trafic sur les différentes interfaces réseau, ce qui nous permet de constater, par des données chiffrées, l'utilisation de nos deux réseaux Wi-Fi.

Nous avons choisi munin pour sa renommée et sa simplicité de configuration et d'utilisation dans un contexte aussi simple que le nôtre.

Typologie des flux réseaux

Pour moi, en 2016, je pense que nous sommes au-delà de ce que devrait s'autoriser à faire un FAI mais ce n'est pas illégal pour autant. Ce point est exigé dans le sujet et il nous a été rappelé lors de la première séance de notation.

Pour obtenir une visualisation fine des flux réseaux qui passent via notre hotspot, nous utilisons ntop-ng, version améliorée de ntop : interface web revue (moins de bugs, plus efficace), plus de données volumétriques récoltées et affichées, etc. Ça vaut bien un Netflow / IPFix.

Après coup, je trouve curieux que le prof' nous ait indiqué ce logiciel (ou plus précisément sa première version, ntop) alors qu'il se met en écoute uniquement en IPv4. 😉

Architecture déployée

Voici un schéma général de l'architecture du réseau que nous avons déployé :

Architecture générale de notre réseau

Architecture générale de notre réseau.

Nous utilisons un switch car notre machine passerelle est équipée d'une seule carte réseau filaire. Notre machine (appelée serveur sur le schéma) regroupe l'intégralité des services utilisés : PepperSpot, RADIUS, MySQL, Apache, DHCP(v4/v6), radvd, etc.

VLANs

Dans notre architecture, nous avons deux SSID : TESTAP, qui redirige le client vers notre portail captif PepperSpot et TESTAP-sec qui utilise une authentification 802.1X. Chaque SSID est affecté à un VLAN : numéro 2 pour TESTAP, numéro 3 pour TESTAP-sec. Les communications inter-VLAN, ou depuis un des réseaux Wi-Fi vers le réseau de management du point d'accès Wi-Fi, sont interdites.

Le VLAN numéro 1 chez Cisco est le VLAN de management natif (pas tagué) et nous l'utilisons pour toutes les communications avec le point d'accès : RADIUS pour 802.1X, configuration en ligne de commande (SSH) du point d'accès, etc.

Sur les modèles Aironet de Cisco, il est possible de changer l'ID du VLAN natif de management, mais il est impossible de taguer ce VLAN. En conséquence, nous utilisons l'aliasing IP pour configurer notre machine passerelle sur nos différents réseaux et pour différencier le trafic entrant/sortant depuis/vers Internet et le trafic circulant sur le VLAN de management.

Données de connexion

Généralités

La législation française impose aux Fournisseurs d'Accès à Internet (FAI) de communiquer aux autorités, dans le cadre d'une procédure judiciaire, toutes les informations associées à une adresse IP dont le FAI aurait connaissance. Cette obligation court pendant un an.

Dans un contexte de 56k avec une identification "toto/toto", le FAI donne le numéro de téléphone appelant. Dans un contexte de VPN "open-bar", le FAI communique l'IP source qui a servie à monter le tunnel. Dans un contexte de Wi-Fi ouvert, le FAI donne l'adresse MAC (c'est par exemple le cas à l'aéroport de Toulouse-Blagnac). Il n'y a pas d'obligation de conserver une identité au sens identité d'état civil.

Si c'est un-e abonné-e et que le FAI a des infos sur lui-elle (identité, adresse postale, adresse mail, autre), il doit les donner.

S'il s'agit d'un service payant, le FAI doit garder les informations relatives au paiement.

S'il y a la création d'un compte, il faut communiquer les hashs et la réponse aux questions secrètes mais pas le mot de passe en clair.

Il est strictement interdit (CPCE) de garder trace de qui a visité tel site. Il est interdit de conserver tout ce qui touche au contenu ou à la navigation.

Les consignes informelles données lors de ce TP font que l'on sent qu'une association IP(v4|v6)<->identifiant est attendue. C'est donc dans cette direction que nous avons creusée même si cela n'est pas nécessaire d'un point de vue légal si l'on dispose de blocs IP dédiés aux réseaux Wi-Fi, comme nous venons de le formuler ci-dessus.

Authentification UAM

PepperSpot garantit l'association identité<->MAC<->IP. L'utilisateur ne peut pas changer d'IPv4 ni d'IPv6 après son authentification. Par ailleurs, le seul schéma d'adressage IPv6 pris en charge par PepperSpot est celui dans lequel l'adresse IPv6 dérive de l'adresse MAC. Il n'est donc pas possible d'utiliser les adresses IPv6 temporaires des extensions pro-vie privée, par exemple (PepperSpot est donc incompatible avec un client Wi-Fi Unbuntu sans une désactivation préalable de ces extensions).

Ici, le portail captif est un NAS dans la terminologie RADIUS. L'accounting RADIUS de base enregistre les informations suivantes à propos du client : identifiant, MAC, IPv4, début de la session, fin de la session, octets envoyés et reçus.

Néanmoins, nous avons complété cet accounting de base :

  • Nous avons positionné la valeur « 120 » pour l'attribut « Acct-Interim-Interval ». Cela permet de forcer le portail captif à faire une mise à jour de l'accounting toutes les deux minutes, histoire d'avoir des informations actualisées sur nos clients Wi-Fi.
  • Nous avons positionné la valeur « 600 » pour l'attribut « Idle-Timeout ». Cela permet de déconnecter un utilisateur qui aurait oublié de se déconnecter manuellement au bout de 10 minutes d'inactivité (au niveau réseau).
  • Nous aurions pu positionner l'attribut « Session-Timeout » pour donner un temps maximal à la connexion à notre hotspot Wi-Fi et pour forcer l'utilisateur à se re-identifier comme cela se fait dans les trains et les aéroports, mais c'est extrêmement contraignant donc nous ne l'avons pas fait.
  • Nous avons modifié le schéma de la base de données et les requêtes d'accounting que freeRADIUS effectue afin d'intégrer le stockage de l'IPv6 de notre client Wi-Fi. Nous avons également modifié daloRADIUS (interface web pour configurer simplement le RADIUS, pour rappel) afin qu'il récupère et affiche ces IPv6.

Pour notre réseau TESTAP, conserver l'accounting RADIUS est suffisant pour atteindre l'objectif fixé.

Authentification 802.1X

L'authentification 802.1X garantit uniquement l'association entre une adresse MAC et un couple identifiant/mot de passe. On est donc en couche 2 uniquement. Dans cette configuration, nous n'avons plus le contrôle forcené de notre portail captif : l'utilisateur peut changer d'IP, v4 comme v6, après son authentification.

De plus, dans cette configuration, le NAS est notre point d'accès Cisco. Or, ce dernier n'envoie pas les IP obtenues par le client Wi-Fi dans les requêtes d'accounting (accounting-request) bien que le point d'accès connaisse l'IPv4 du client et l'affiche dans la sortie de la commande « sh dot11 associations » même si ce dernier en change après authentification. En revanche, l'adresse IP d'un client peut-être récupérée via SNMP mais, à l'inverse, l'adresse MAC de ce même client ne peut pas être récupérée car cela n'est pas prévu dans la MIB. Cela nous est donc inutile : nous avons besoin de stocker ces deux informations pour avoir une association entre identifiant et IP.

Nous pouvons conserver les logs DHCP (v4 et v6) mais ils sont insuffisants : un utilisateur peut fixer ses adresses IP manuellement. De plus, en IPv6, l'autoconfiguration stateless permet de se passer de DHCPv6, ce qui rend inutile tout log DHCPv6.

Sur OSIRIS(-sec), la direction informatique (DI) de l'université collecte toutes les adresses MAC sur tous les commutateurs et toutes les associations MAC<->IP sur les routeurs. Un outil interne agrège toutes les données (association identité<->MAC<->IP) et un moteur de recherche, développé aussi en interne, permet de retrouver la bonne information en cas de réquisition. Il ne leur semble pas exister de solution plus fiable.

Dans le cadre de ce projet, nous avons mis en place une solution similaire à celle déployée sur OSIRIS(-sec) mais sans l'énergie dépensée par la DI : nous enregistrons les associations MAC<->IP avec deux logiciels : arpwatch (IPv4) et ndpmon (IPv6). Dès qu'un client Wi-Fi émet sur le réseau, pouf, nous avons l'association entre son adresse MAC et ses IPs. Toute machine qui rejoint le réseau ou change sa MAC ou reprend une IP allouée à quelqu'un d'autre sera détectée. En cas de réquisition judiciaire concernant une IP, il suffira de chercher, dans les logs arpwatch et ndpmon, la MAC associée puis d'aller chercher les informations (identité, plage horaire de connexion, etc.) relatives à cette adresse MAC dans l'accounting RADIUS. Le développement d'un moteur de recherche convivial dépasse le cadre de ce TP.

Outro

Nous avons aussi mis en place une conservation des associations MAC<->IP sur le SSID UAM dans un objectif de sécurité supplémentaire (ceinture et bretelles). Les logs ainsi produits peuvent être croisés avec l'accounting RADIUS afin d'être sûrs de la véracité de l'information, par exemple.

Note : pour enregistrer les associations IPv6<->MAC, nous aurions pu avoir recours à Netfilter pour noter tous les messages ICMPv6 « Neighbor Advertisement ». Il ne semble rien exister de tel pour IPv4 pour l'environnement GNU/Linux (iptables travaille à partir de la couche 3 donc au-dessus d'ARP et arptables n'implémente pas de cible « LOG »). De plus, arpwatch et ndpmon permettent de prévenir les administrateurs de toute tentative (même infructueuse) d'usurpation d'une adresse MAC, ce que ne permet pas un simple log ip6tables. Pour bénéficier de cette dernière fonctionnalité, il faudra simplement activer les fonctionnalités d'envoi de mail en cas d'événements critiques dans ces deux logiciels. Mais cela dépasse le cadre de ce TP.

Page d'administration

La tâche principale d'administration, à savoir ajouter/supprimer un utilisateur afin que celui-ci ait accès ou non à notre hotspot, se fait avec daloRADIUS, interface web qui permet d'interagir avec la base de données utilisée par freeRADIUS.

La supervision et la métrologie se vérifient à partir des interfaces web des outils déployés (monit, munin, ntop-ng).

Les autres tâches (mises à jour du système, maintenance, consultation des logs, etc. ) se réalisent en ligne de commande.

De manière informelle, il nous a été demandé de créer une sorte de page d'accueil de nos outils d'administration : une page qui afficherait les informations importantes issues de l'accounting RADIUS concernant les utilisateurs actuellement connectés à notre hotspot et qui proposerait des liens pertinents vers les outils d'administration que nous avons déployés (exemple : un lien sur une IP conduirait à la page ntop-ng concernant cette IP).

Partant de là, nous avons décidé de récupérer des données intéressantes (uptime, charge CPU, mémoire, nombre de clients Wi-Fi connectés, etc.) depuis notre point d'accès Cisco en utilisant SNMP et de les afficher sur cette page d'administration. Histoire de montrer qu'on sait vaguement utiliser SNMP, étudié précédemment dans une autre matière (administration des réseaux, si ma mémoire est bonne).

Cette page d'administration est disponible sur https://localhost/admin. (en IPv4 et en IPv6, forcément 😉 ). Identifiant/mdp : toor/toor.

Mobilité IPv6

Kézako ?

L'idée derrière la mobilité IP (ça existe aussi en IPv4), c'est qu'une machine conserve ses IP lorsqu'elle change de réseau, ce qui lui permet de conserver ses connexions actives. Utilité ? Les chercheurs voient ça pour les voitures connectées qui devront se connecter à des réseaux différents tout au long de leur déplacement. Ou pour nos ordiphones, qui pourraient ainsi passer d'un réseau Wi-Fi à un réseau 4G sans interruption de service.

Comment cela se matérialise-t-il ? Un entête IPv6 "mobility" chaîné à un entête IPv6 standard, des options IPv6, de nouveaux messages ICMPv6, etc. En gros, cela forme un tunnel similaire à un tunnel ip6ip6. Ce tunnel peut être protégé par IPSec. À mes yeux, c'est donc une énième solution de tunneling. Je pense que le principal avantage de MIPv6 est sa légèreté dans un usage avec chiffrement (comparé à un OpenVPN en couche 7 + userland ou à du IPSec + GRE, par exemple).

Choix de l'implémentation

Nous avons utilisé umip (ex-mipd6) simplement parce que le prof' a dit que c'était l'implémentation qu'il utilise pour monter le Home Agent avec lequel notre Mobile Node devra communiquer. Cette partie du sujet ne nous a pas semblé être assez significative pour justifier une recherche et une comparaison d'autres implémentations.

Quand la mobilité IP rencontre BCP38

Nous avons constaté qu'il est impossible d'installer un Mobile Node IPv6 derrière une connexion Free ou FDN (et sans doute d'autres fournisseurs d'accès à Internet). Hurricane Electric (fournisseur de tunnels 6in4) n'est pas concerné.

Après quelques recherches, nous avons constaté que le « Binding Update » parvient jusqu'au Home Agent (celui installé par le prof' ou un autre, sur un réseau sur lequel nous avons la main) mais que le « Binding Ack » n'atteint pas le Mobile Node.

Cela ne peut pas venir d'un blocage (à cause des options IPv6 utilisées, par exemple) puisque l'un des fournisseurs d'accès à Internet que nous utilisons, FDN, fait de la neutralité du réseau son cheval de bataille.

La cause la plus probable semble être l'utilisation, par ces FAI, de l'ingress filtering (RFC 2827 et 3704 pour les réseaux multihomés, dit BCP38). En effet, l'ingress filtering posait problème avec la mobilité IPv4 (voir la section 5 du RFC 2827) et peut encore en poser avec Mobile IPv6 même si le Mobile Node ne met plus sa home address comme source des paquets qu'il émet (voir, entre autres, ce brouillon de standard).

En 2016, je suis en mesure de confirmer cette hypothèse. En installant un Home Agent dans une VM ARN et un Mobile Node chez Grifon, je constate que le message « Binding Ack » est filtré par le routeur d'entrée de Grifon lorsque le mécanisme bcp38 est activé.

Mise en œuvre

Configurer un point d'accès Cisco Aironet 1120B

La première étape, c'est de revenir à la configuration par défaut. On zieute les instructions de Cisco. En résumé : il faut appuyer sur le bouton « MODE » tout en allumant l'AP, jusqu'à ce que la LED change de couleur.

Après son redémarrage automatique, la borne récupérera une IPv4 via DHCP et on pourra se connecter à l'interface d'admin en telnet en utilisant le couple identifiant/mdp Cisco/Cisco.

On configure les paramètres principaux : nom, domaine, ajout d'un utilisateur, suppression de l'utilisateur Cisco, activer SSH, assigner une IP à l'interface d'administration, etc. Pour cela, on suit ce tutoriel : Configuration minimale d'une borne Cisco Aironet 1200 Series.

Ensuite, on vire CDP :

interface fa0
 no cdp enable
 
interface Dot11Radio0
 no cdp enable

Maintenant, créons nos VLAN. D'abord, le VLAN d'administration (natif, pas tagué) :

interface fa0.1
 description admin
 encapsulation dot1Q 1 native
 bridge-group 1
 no cdp enable
 bridge-group 1 spanning-disabled

Puis on crée les VLANs de nos réseaux Wi-Fi :

interface fa0.2
 description TESTAP
 encapsulation dot1Q 2
 bridge-group 2
 no cdp enable
 bridge-group 2 spanning-disabled
 
interface fa0.3
 description TESTAP-sec
 encapsulation dot1Q 3
 bridge-group 3
 no cdp enable
 bridge-group 3 spanning-disabled

On crée les interfaces Wi-Fi qui correspondent :

interface Dot11Radio0.2
 description TESTAP
 encapsulation dot1Q 2
 bridge-group 2
 no cdp enable
 bridge-group 2 spanning-disabled
 
interface Dot11Radio0.3
 description TESTAP-sec
 encapsulation dot1Q 3
 bridge-group 3
 no cdp enable
 bridge-group 3 spanning-disabled

On crée nos deux SSID :

dot11 ssid TESTAP
 vlan 2
 authentication open
 mbssid guest-mode
 
dot11 ssid TESTAP-sec
 vlan 3
 authentication open
 mbssid guest-mode

On accroche nos SSID aux interfaces radio et on allume l'interface :

interface Dot11Radio 0
 mbssid
 ssid TESTAP
 ssid TESTAP-sec
 no sh

Notre point d'accès est désormais configuré, à l'exception de l'authentification 802.1X, de SNMP et d'autres bricoles que nous ajouterons en cours de route. Faisons les choses dans l'ordre pour un debug plus facile.

Pour rappel : sur les modèles Aironet de Cisco, il est possible de changer l'ID du VLAN natif de management, mais il est impossible de taguer ce VLAN.

Configurer un point d'accès Linksys WRT54GL avec OpenWRT

Nous n'avions pas d'équipements compatibles avec OpenWRT lors de la réalisation de ce TP, mais, lors de la publication de ce travail en 2016, j'ai trouvé intéressant de le reproduire avec un AP fonctionnant avec OpenWRT.

Pour revenir à une config par défaut, je re-flashe le WRT54GL. On est limité à la version Backfire 10.03.01, déclinaison brcm2.4 d'OpenWRT. Les versions plus récentes d'OpenWRT ou celles de la déclinaison brcm47 sont inutilisables sur un WRT54GL (lenteurs, plantages, etc.).

Là aussi, on fait la config' de base via telnet (192.168.1.1, pas d'identifiant/mdp). On change le mot de passe de root (ce qui active SSH) avec la commande « passwd » habituelle. On change le nom de la machine (et le fuseau horaire) dans le fichier « /etc/config/system ».

On veut un AP passif donc on désactive le pare-feu, le serveur DHCP, l'interface web et d'autres bricoles.

J'ai décidé d'utiliser le port WAN pour faire passer mes VLANs car il s'agit déjà d'un port physiquement isolé des autres, ce qui ressemble fortement à l'AP Cisco qui a un unique port.

Créons nos VLANs dans /etc/config/network. D'abord, je supprime toute la config sauf :

#### VLAN configuration
config switch eth0
    option enable   1
 
config switch_vlan eth0_1
    option device   "eth0"
    option vlan     1
    option ports    "4 5"
 
#### Loopback configuration
config interface loopback
    option ifname   "lo"
    option proto    static
    option ipaddr   127.0.0.1
    option netmask  255.0.0.0

Ensuite, ajoutons l'interface d'administration (VLAN 1 pas tagué) à la fin du fichier :

### Admin iface configuration
config interface admin
    option ifname  "eth0.1"
    option proto   static     
    option ipaddr  198.18.255.1
    option netmask 255.255.255.254

Dans la section sur la configuration des VLANs, on ajoute les VLANs de nos réseaux Wi-Fi :

config switch_vlan eth0_2
    option device "eth0"
    option vlan   2
    option ports  "4t 5t"
 
config switch_vlan eth0_3
    option device "eth0"
    option vlan   3
    option ports  "4t 5t"

Correspondance entre les ports physiques et les ports logiques : port physique Internet = 4, port physique 1 = 3, port physique 2 = 2, port physique 3 = 1, port physique 4 = 0, CPU (pour dire que le WRT54GL fait partie du VLAN et s'il doit le taguer ou non) = 5.

Notons que, contrairement à notre Aironet Cisco, il est parfaitement possible d'isoler la liaison de management avec l'AP dans un VLAN tagué en ajoutant des « t » dans « ports » de « eth0_1 ». Il faudra créer le VLAN (avec l'ID 1, du coup, mais ça se change) sur la passerelle. Si nous faisions cela, ça change un peu les règles de filtrage que nous écrirons plus loin.

On crée les interfaces bridges associées aux VLANs :

# VLAN ifaces configuration
config interface testap  
    option type   bridge  
    option ifname "eth0.2"
    option proto  static
    option ipaddr  192.0.2.1
    option netmask 255.255.255.255
 
config interface tapsec    
    option type   bridge  
    option ifname "eth0.3"
    option proto  static
    option ipaddr  192.0.2.2
    option netmask 255.255.255.255

Attention : il ne faut pas que les deux noms d'interface se ressemblent trop (du genre « testap - testapsec », début identique) sinon un seul bridge sera créé.

Oui, il faut assigner des IP bidons (ici, il s'agit d'IP qui font partie du bloc réservé à l'IANA pour écrire de la documentation). Sinon, il faut ajouter une ligne « option proto none » pour chaque bridge. Mais cela entraîne l'erreur : « /sbin/Wi-Fi: eval: line 1: syntax error: bad substitution » et cela empêche les interfaces VLAN filaires d'être configurées et montées automatiquement au boot. Si l'on tente de ne pas mettre « option proto none », on obtient l'erreur « Interface not found » et le même comportement de non-configuration au boot.

On crée nos SSID, on les associe à l'interface radio et à nos VLANs et on active l'interface radio. Le tout dans /etc/config/wireless :

config Wi-Fi-iface
    option device      wl0
    option network     testap
    option mode        ap
    option ssid        "TESTAP"
    option encryption  none
 
config Wi-Fi-iface
    option device      wl0
    option network     tapsec
    option mode        ap
    option ssid        "TESTAP-sec"
    option encryption  none

Évidemment, je supprime le SSID « OpenWrt » créé par défaut.

Pour appliquer les modifications :

/etc/init.d/network restart

Notons que la session telnet va être interrompue à cause du retrait du bout de config "LAN". Il faudra se brancher sur le port WAN et établir une nouvelle session telnet.

La rupture de la session telnet peut interrompre prématurément le restart. Il peut donc être nécessaire de l'exécuter une deuxième fois pour que l'ensemble de la config' devienne effectif.

Vous pouvez obtenir l'erreur « Command 'set ssid' failed: -1 » qui semble être une race condition (le SSID est assigné deux fois dont une fois quand l'interface est allumée d'où le driver sort en erreur) corrigée dans les nouvelles versions d'OpenWRT.

Installer un système Debian GNU/Linux sur une clé USB bootable

Ce tutoriel reste toujours d'actualité : Installing Debian on a USB stick (from a running Debian system). Si vous installez un environnement graphique, pensez également à installer un gestionnaire d'affichage genre gdm3.

Configuration de base de la passerelle

Virer avahi

On n'a pas besoin de cette pourriture sur nos réseaux donc :

sudo systemctl disable avahi-daemon.service avahi-daemon.socket
sudo systemctl mask avahi-daemon.service avahi-daemon.socket
NTP

Il est important de maintenir notre système à l'heure, notamment car c'est l'heure locale qui servira à horodater les logs et l'accounting RADIUS.

On installe l'implémentation NTP de référence :

sudo apt-get install ntp

Config' à mettre dans /etc/ntp.conf :

disable monitor
 
restrict default ignore
 
restrict ntp-p1.obspm.fr nomodify notrap nopeer noquery
restrict canon.inria.fr nomodify notrap nopeer noquery
 
restrict 127.0.0.1
restrict ::1
 
server ntp-p1.obspm.fr iburst prefer
server canon.inria.fr iburst
 
# Undisciplined Local Clock. This is a fake driver intended for backup
# and when no outside source of synchronized time is available. 
server  127.127.1.0 
fudge   127.127.1.0 stratum 10
restrict 127.127.1.0
 
# Driftfile.
driftfile /var/lib/ntp/ntp.drift

On redémarre NTPd avec cette nouvelle config' :

sudo systemctl restart ntp
IPv6

Soit monter un tunnel 6in4 fournit par Hurricane Electric ou SixXS par exemple, soit monter un tunnel quelconque (OpenVPN, IPSec) qui fournit une connectivité IPv6 avec au moins 2 * /64. Je vous laisse faire. 🙂

/etc/network/interfaces

Voici notre fichier /etc/network/interfaces :

# Internet
auto eth0
iface eth0 inet dhcp
 
 
# IPv6 connectivity
auto he-ipv6
iface he-ipv6 inet6 v4tunnel
  address 2001:470:1f12:273::2/64
  endpoint 216.66.84.42
  local 130.79.92.13
  ttl 255
  gateway 2001:470:1f12:273::1
 
 
# AP management
auto eth0:0
iface eth0:0 inet static
  address 198.18.255.0/31
 
 
# TESTAP 
auto testap
iface testap inet manual
  post-up ip link add link eth0 name $IFACE type vlan id 2 2> /dev/null || true
  post-up ip link set $IFACE up
  post-up ip addr add 198.18.0.1/24 dev $IFACE
  pre-down ip addr del 198.18.0.1/24 dev $IFACE
 
iface testap inet6 static
  address 2001:470:c8d6:2::1/64
 
 
# TESTAP-sec
auto tapsec
iface tapsec inet manual
  post-up ip link add link eth0 name $IFACE type vlan id 3 2> /dev/null || true
  post-up ip link set $IFACE up
  post-up ip addr add 198.18.1.1/24 dev $IFACE
  pre-down ip addr del 198.18.1.1/24 dev $IFACE
 
iface tapsec inet6 static
  address 2001:470:c8d6:3::1/64

On applique la nouvelle config' :

sudo systemctl restart networking
Activer l'IP forward

Dans /etc/sysctl.conf, décommenter :

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

On applique :

sudo sysctl -p
radvd

On installe :

sudo apt-get install radvd

On configure en mettant ce qui suit dans /etc/radvd.conf :

# TESTAP
interface testap
{
    AdvSendAdvert on;
    MaxRtrAdvInterval 10;
 
    # "You can use DHCPv6 to obtain moar informations"
    # AdvManagedFlag on;
    AdvOtherConfigFlag on;
 
    prefix 2001:470:c8d6:2::/64
    {
    };
 
    RDNSS 2a00:5881:8100:1000::3
    {
    };
 
    RDNSS 2a00:5884:8218::1
    {
    };
};
 
# TESTAP-sec
interface tapsec
{
    AdvSendAdvert on;
    MaxRtrAdvInterval 10;
 
    # "You can use DHCPv6 to obtain moar informations"
    # AdvManagedFlag on;
    AdvOtherConfigFlag on;
 
    prefix 2001:470:c8d6:3::/64
    {
    };
 
    RDNSS 2a00:5881:8100:1000::3
    {
    };
 
    RDNSS 2a00:5884:8218::1
    {
    };
};

On abuse un peu sur le MaxRtrAdvInterval (par défaut, il est configuré à 600 secondes, soit 10 minute) tout en restant au-dessous du minimum indiqué dans le manuel de radvd (4 secondes). L'idée première est de parer les pare-feu qui bloquent l'ICMPv6 en sortie donc les « Router Solicitation » sans pour autant bloquer les « Router Advertisement ». Cela permet aussi de garantir que nos clients Wi-Fi auront rapidement une IPv6 afin de montrer au prof' que PepperSpot redirige bien l'utilisateur pour authentification, même en IPv6. Sur un vrai déploiement en dual stack, il faudrait augmenter cette valeur.

On fait prend en compte la nouvelle configuration :

sudo systemctl restart radvd
DHCP
DHCPv4

On installe l'implémentation de référence :

sudo apt-get install isc-dhcp-server

On écrit la configuration dans /etc/dhcp/dhcpd.conf :

# Seems great for a Wi-Fi network
default-lease-time 1800; # 30 mns
max-lease-time 25200;    # 7  H
 
authoritative;
 
log-facility local7;
 
# TESTAP
subnet 198.18.0.0 netmask 255.255.255.0 
{
    range 198.18.0.10 198.18.0.254;
 
    option subnet-mask 255.255.255.0;
    option broadcast-address 198.18.0.255;
 
    option routers 198.18.0.1;
 
    option domain-name-servers 89.234.141.66, 89.234.186.18; 
}
 
# TESTAP-sec
subnet 198.18.1.0 netmask 255.255.255.0 
{
    range 198.18.1.10 198.18.1.254;
 
    option subnet-mask 255.255.255.0;
    option broadcast-address 198.18.1.255;
 
    option routers 198.18.1.1;
 
    option domain-name-servers 89.234.141.66, 89.234.186.18;
}

On fait prendre en compte la nouvelle config' :

sudo systemctl restart isc-dhcp-server
DHCPv6

Dans l'implémentation DHCP de l'ISC, un même binaire assure le job pour IPv4 et pour IPv6 : un commutateur permet d'indiquer la version désirée. Aucun initscript n'est livré dans le paquet Debian pour démarrer un serveur DHCPv6. Voici notre initscript, librement adapté de l'initscript v4, qui contrairement à son homologue, n'inclu pas un fichier /etc/default/isc-dhcp6-server (aka tout est défini dans notre initscript) :

#!/bin/sh
#
# $Id: isc dhcp server.init.d,v 4.2.1-P1 2011/04/05 /usr/local/sbin/dhcpd$
#
 
### BEGIN INIT INFO
# Provides:          dhcpd6-server
# Required-Start:    $remote_fs $network $syslog
# Required-Stop:     $remote_fs $network $syslog
# Should-Start:      $local_fs slapd
# Should-Stop:       $local_fs slapd
# Default-Start:     2 3 4 5
# Default-Stop:      1
# Short-Description: DHCP server
# Description:       Dynamic Host Configuration Protocol Server
### END INIT INFO
 
PATH=/sbin:/bin:/usr/sbin:/usr/bin
 
# config file
NAME=dhcpdv6
DESC="DHCP IPv6 server"
INTERFACES=""
 
SERVER=/usr/sbin/dhcpd
SERVERARGS="-6"
CONFIGFILE=/etc/dhcp/dhcpd6.conf
LIBFOLDER=/var/lib/dhcpv6
LEASEFILE="${LIBFOLDER}/dhcpdv6.leases"
RUNFOLDER=/var/run/dhcpv6
DHCPDPID="${RUNFOLDER}/dhcpdv6.pid"
 
 
# check filetypes/values
test -f "${SERVER}" || exit 0
 
# include all init functions
. /lib/lsb/init-functions
 
test_config()
{
	# 1.) check config
	if [ ! "${SERVER}" "${SERVERARGS}" -t -q -cf "${CONFIGFILE}" > /dev/null 2>&1 ]; then
		echo "${NAME} self-test failed. Please fix the config file."
		echo "The error was: "
		"${SERVER}" "${SERVERARGS}" -t -cf "${CONFIGFILE}"
		exit 1
	fi
 
	# 2.) test_config will started if someone wants to start the server
	# test if the server is currently running
	if [ "${1}" = "start" ]; then
		if [ -e "${DHCPDPID}" ]; then
		  stop_server "Currently running instance of ${DESC} found (PID: `cat ${DHCPDPID}`) - will now stop this instance"
		fi
	fi
}
 
stop_server(){
	if [ "${1}" != "" ]; then
	 log_daemon_msg "${1}"
	fi
 
	if [ -e "${DHCPDPID}" ]; then
	  log_daemon_msg "Stopping ${DESC} ${NAME} [`cat ${DHCPDPID}`]"
	  start-stop-daemon --stop --quiet --pidfile "${DHCPDPID}"
	  log_end_msg $?
	  rm -f "${DHCPDPID}"
	else
	  log_daemon_msg "Stopping ${DESC} ${NAME}: nothing do do, no pidfile found"	
	fi
}
 
# single arg is -v for messages, -q for none
check_status(){
  if [ ! -r "$DHCPDPID" ]; then
    test "$1" != -v || echo "$NAME is not running."
    return 3
  fi
 
  if read pid < "$DHCPDPID" && ps -p "$pid" > /dev/null 2>&1; then
    test "$1" != -v || echo "$NAME is running."
    return 0
  else
    test "$1" != -v || echo "$NAME is not running but $DHCPDPID exists."
    return 1
  fi
}
 
case "$1" in
	start)
	  test_config ${1}
		log_daemon_msg "Starting ${DESC} ${NAME}"
 
		# allow dhcp server to write lease and pid file
		if [ ! -e "${RUNFOLDER}" ]; then
		  # create run folder
		  mkdir -p "${RUNFOLDER}"
		  #chown dhcpd:dhcpd "${RUNFOLDER}"
 
		  # create pid file
		  touch "${DHCPDPID}"
		  #chown dhcpd:dhcpd "${DHCPDPID}"
		else
		   # create pid file
		  touch "${DHCPDPID}"
		  #chown dhcpd:dhcpd "${DHCPDPID}"
		fi
 
		if [ ! -e "${LIBFOLDER}" ]; then
		  # create run folder
		  mkdir -p "${LIBFOLDER}"
		  #chown dhcpd:dhcpd "${LIBFOLDER}"
 
		  # create lease file
		  touch "${LEASEFILE}"
		  #chown dhcpd:dhcpd "${LEASEFILE}"
		else
		   # create pid file
		  touch "${LEASEFILE}"
		  #chown dhcpd:dhcpd "${LEASEFILE}"
		fi
 
		start-stop-daemon --start --quiet --pidfile "${DHCPDPID}" \
			--exec "${SERVER}" -- "${SERVERARGS}" -q $OPTIONS -cf "${CONFIGFILE}"  -lf "${LEASEFILE}" -pf "$DHCPDPID" ${INTERFACES}
		sleep 2
 
 
		if check_status -q; then
		  log_end_msg 0
		else
			log_failure_msg "check syslog for diagnostics."
			log_end_msg 1
			exit 1
		fi
		;;
	stop)
		# stop dhcp server
		stop_server
		;;
 
	restart | force-reload)
		test_config
		$0 stop
		sleep 2
		$0 start
		if [ "$?" != "0" ]; then
			exit 1
		fi
		;;
	status)
		echo -n "Status of $DESC: "
		check_status -v
		exit "$?"
		;;
	*)
		echo "Usage: $0 {start|stop|restart|force-reload|status}"
		exit 1 
esac
 
exit 0

Bon, en 2016, je pense qu'une unit systemd serait plus pertinente et prendrait moins de temps à être déployée, mais je vous livre quand même cet initscript : puisqu'il existe, autant qu'il serve.

On active ce service :

sudo chmod +x /etc/init.d/isc-dhcp6-server
sudo systemctl daemon-reload
sudo systemctl enable isc-dhcp6-server

On pose la configuration dans le fichier /etc/dhcp/dhcpd6.conf :

# Enable RFC 5007 support (same than for DHCPv4)
allow leasequery;
 
# TESTAP
subnet6 2001:470:c8d6:2::/64
{
    option dhcp6.name-servers 2a00:5881:8100:1000::3, 2a00:5884:8218::1;
    option dhcp6.info-refresh-time 25200; # 7H
 
    # range6 2001:470:c8d6:2::/64;
}
 
# TESTAP-sec
subnet6 2001:470:c8d6:3::/64 
{
    option dhcp6.name-servers 2a00:5881:8100:1000::3, 2a00:5884:8218::1;
    option dhcp6.info-refresh-time 25200;
 
    # range6 2001:470:c8d6:3::/64;
}

La configuration est répétitive, mais il nous faut obligatoirement de la config' dans les sections « subnet6 » sinon le serveur refuse de démarrer. Cette configuration sera simplifiée par la suite.

Si l'on active les options « range6 », notre serveur DHCPv6 répondra aux requêtes DHCPv6 d'attribution d'adresse. On serait alors dans une configuration stateful. Comme nous l'avons déjà écrit, ce n'est pas ce que nous voulons.

On démarre le serveur DHCPv6 :

sudo systemctl start isc-dhcp6-server
NAPT

On ajoute une règle de NAPT :

sudo iptables -t nat -A POSTROUTING -o eth0 -j ACCEPT

Nous mettrons en place la persistance de cette configuration lorsque nous installerons PepperSpot.

Tester

On peut procéder à un premier test de notre installation en nous connectant à chacun de nos deux réseaux Wi-Fi. Pour chaque, on doit obtenir une IPv4 dans le bon réseau, une IPv6 dans le bon réseau, les adresses (v4 et v6) du routeur et les adresses (IPv4 et IPv6) de nos résolveurs DNS. On doit être en mesure d'utiliser pleinement Internet.

De plus, à la réception d'un message « Router Advertisement », notre machine doit émettre une requête DHCPv6 « Information-request » et notre serveur DHCPv6 doit répondre avec un message « Reply » contenant les adresses IPv6 de nos résolveurs DNS.

Si tout cela est OK, nous pouvons passer à la suite.

Authentification 802.1X

(Free)RADIUS

À propos de RADIUS, de FreeRADIUS, d'EAP, etc., je recommande les deux ressources suivantes : présentation de RADIUS, EAP et FreeRADIUS et formation vidéo RADIUS.

On installe l'implémentation RADIUS de référence, FreeRADIUS :

sudo apt-get install freeradius

Par défaut, FreeRADIUS écoute uniquement en IPv4. Il faut corriger cela en ajoutant ce qui suit dans /etc/freeradius/radiusd.conf :

# Listen v6
# For authentication 
listen {
    type = auth
    ipv6addr = ::1
    port = 1812
}
 
# For accounting                                                
listen {
    type = acct
    ipv6addr = ::1
    port = 1813
}

Oui, on pourrait mettre « ipv6addr = * » mais, notre AP Cisco n'étant pas compatible IPv6, il n'y a que notre portail captif, installé sur la même machine, qui viendra faire des requêtes.

Dans /etc/freeradius/clients.conf, il faut ajouter les NAS qui seront autorisés à faire des requêtes auprès de notre serveur :

# localhostv6
client ::1 {
    ipv6addr        = ::1
    secret          = <secret_radius>
    shortname       = localhostv6
}
 
client ap {
    ipaddr          = 198.18.255.1
    secret          = <secret_radius>
}

Notons que quand le nom d'un client peut être résolu, il n'y a pas besoin de préciser « ipaddr ».

Il faut maintenant ajouter les utilisateurs autorisés à se connecter à notre base de données. Par défaut, FreeRADIUS utilise uniquement un fichier, /etc/freeradius/users. Exemple de ligne à ajouter :

guigui    Cleartext-Password := "toor"

Mais stocker les mots de passe en clair, ça tue des chatons. FreeRADIUS prend en charge quelques fonctions cryptographiques désuètes : NT, MD5, SHA-1, SHA-1 avec un sel et crypt (oui, la fonction anciennement utilisée pour stocker les mdps de session dans /etc/shadow, désormais on l'utilise avec un sel et des fonctions de hachage genre SHA2). On remarquera que tous ces algos sont morts : NT = MD4 = mort, MD5 = mort, crypt (version originale) = mort, SHA-1 et SHA-1 salé résistent encore au bruteforce quand elles sont utilisées avec de vrais mots de passe complexes donc c'est quasiment une illusion sur un hotspot (nous n'aurons pas de mdp complexes). La seule alternative viable serait de modifier les modules de FreeRADIUS ou d'utiliser pam plus des algos cryptos encore viable derrière…

Regardons malgré tout pour SHA1. Pour obtenir le hash d'un mdp de passe, on utilise la commande suivante :

echo -n "<mdp>" | sha1sum

Exemple de ligne à ajouter dans le fichier users de freeRADIUS :

guigui    SHA1-Password := "<hash>"

On redémarre notre serveur RADIUS avec la nouvelle configuration :

sudo systemctl restart freeradius

On teste notre RADIUS :

radtest -6 <identifiant> <mdp> ::1 0 <secret_radius_pour_localhost>

On doit forcément obtenir un « Accept-Accept » avant de passer à la suite.

802.1X sur un AP Cisco Aironet

Les lignes de commande KiVontBien pour activer l'authentification 802.1X sur notre SSID TESTAP-sec :

radius-server host 198.18.255.0 auth-port 1812 acct-port 1813 key 0 <secret_radius>
 
aaa group server radius rad_eap
 server 198.18.255.0 auth-port 1812 acct-port 1813
 
aaa authentication identifiant eap_methods group rad_eap
 
int Dot11Radio 0
 encryption vlan 3 mode ciphers tkip 
 
dot11 ssid TESTAP-sec
  authentication open eap eap_methods 
  authentication network-eap eap_methods 
  authentication key-management wpa
802.1X sur un AP WRT54GL avec OpenWRT

Documentation : Introduction to 802.1x sur le wiki OpenWRT.

Dans /etc/config/wireless, ajouter ce qui suit à notre SSID TESTAP-sec :

    option encryption  wpa2
    option server      198.18.255.0
    option key         <secret_radius>

Puis :

/etc/init.d/network restart

Note : dans la déclinaison brcm2.4 d'OpenWRT, le NAS est un soft privateur de Broadcom, nommé « nas » (imagination, quand tu nous tiens). Il va de pair avec le driver Wi-Fi privateur de Broadcom, wl. hostapd prend en charge uniquement le driver libre mac80211 qui ne prend pas en charge le chip Wi-FI du WRT54GL. hostapd fonctionne avec le driver libre b43 (qui est présent dans la déclinaison brcm47 d'OpenWRT) mais ce driver ne permet pas d'émettre plusieurs SSID en même temps.

Tester

À partir d'ici, il doit être possible de se connecter à notre réseau TESTAP-sec en utilisant l'authentification 802.1X. \o/

Pour vous connecter, votre gestionnaire réseau (GNOME Network Manager, par exemple) vous demandera le certificat x509 public de l'autorité de certification (AC) qui a signé le certificat de votre AP afin de valider ce dernier. Par défaut, sous Debian, freeRADIUS utilise un certificat bidon autosigné généré automatiquement lors de l'installation. Il se trouve dans /etc/freeradius/certs/server.pem et c'est lui qu'il faudra utiliser. Hé, oui, en EAP-TTLS, chaque client discute avec le RADIUS : les messages EAP sont encapsulés dans des messages RADIUS par le NAS qui fait partie intégrante du point d'accès Wi-Fi. Dans le sens inverse, le NAS relaie aussi les challenges cryptographiques envoyés par le serveur RADIUS au client afin que ce dernier les résolve afin de prouver son identité.

Attention avec OpenWRT : le NAS de Broadcom est très chatouilleux : un acces-reject et votre machine cliente est bloquée (probablement en utilisant la MAC). J'ai attendu plusieurs heures, pas de déblocage, seul un « /etc/init.d/network restart » permet de redonner une chance à la machine cliente. En revanche, on peut se déconnecter/reconnecter du réseau autant de fois que l'on veut si le RADIUS retourne un « access-accept ». Comportement incompréhensible. :-

Stocker les utilisateurs de notre hotspot dans MySQL

On installe le nécessaire :

sudo apt-get install freeradius-mysql mysql-server

On crée la base de données pour freeRADIUS, on crée un utilisateur radius dédié, on crée un premier utilisateur de notre hotspot :

sudo mysql -u root -p
mysql> CREATE DATABASE radius;
mysql> GRANT ALL PRIVILEGES ON radius.* TO radius@localhost IDENTIFIED BY "<mdp>";
mysql> flush privileges;
mysql> source /etc/freeradius/sql/mysql/schema.sql
mysql> insert into radcheck VALUES (1, 'guigui', 'SHA1-Password', ':=', '<hash_mdp>');
mysql> exit

On utilise « sudo » afin que MySQL puisse lire le fichier /etc/freeradius/sql/mysql/schema.sql . 😉 On ne restreint pas cet utilisateur en lecture seule car daloRADIUS utilisera ce compte pour écrire dans la base de données RADIUS.

Il faut activer le module SQL dans freeRADIUS : dans /etc/freeradius/radiusd.conf, « $INCLUDE sql.conf » ne doit plus être en commentaire. Dans /etc/freeradius/sql.conf, il faut changer le mot de passe permettant de se connecter à la base de données.

Il faut indiquer à freeRADIUS d'utiliser le module SQL pour réaliser l'autorisation, c'est-à-dire remonter les informations que l'on a sur un utilisateur depuis la BDD. Pour cela, il faut décommenter « sql » dans la section « authorize » de /etc/freeradius/sites-enabled/inner-tunnel pour l'authentification 802.1X (car notre serveur RADIUS connaît le mot de passe de l'utilisateur uniquement quand le tunnel EAP-TTLS a été établi) et dans la section « authorize » du fichier /etc/freeradius/sites-enabled/default pour le portail captif puisque celui-ci n'utilise pas de tunnel.

On met en commentaire notre tout premier utilisateur de test dans /etc/freeradius/users.

On teste qu'il est toujours possible de nous authentifier sur notre SSID TESTAP-sec.

Alléger et sécuriser freeRADIUS

Si l'on est sûr que notre RADIUS ne servira pas à faire autre chose que de l'authentification au-dessus d'EAP, alors on peut mettre en commentaire chap, mschap, digest et pap dans la section « authorize » de /etc/freeradius/sites-enabled/default. Le seul module dont nous ayons besoin ici est eap. Pour le portail captif, le module « sql » ou le module « files » positionnera un attribut « Auth-Type := PAP » qui activera le bon module dans la section « authenticate ». On peut également mettre en commentaire « preprocess » et « suffix », nous n'en avons pas besoin ici (nous n'utilisons pas de huntgroup ni de realm).

On peut également alléger la configuration du module eap dans /etc/freeradius/eap.conf. On peut mettre en commentaire md5 (aka eap-md5), leap (aka eap-leap), peap et mschapv2 dans la section « authorize ». On peut également changer la valeur de « default_eap_type » pour la passer de « md5 » à « ttls ». Notons qu'il n'est pas possible de changer la valeur du même « default_eap_type » dans le module ttls.

On peut également alléger la configuration qui s'applique à tout tunnel EAP établi. C'est dans /etc/freeradius/sites-enabled/inner-tunnel que cela se passe. On peut mettre en commentaire mschap, eap et suffix. On notera là aussi que les modules files et sql autorisent obligatoirement et forcément l'usage de CHAP (et PAP) pour l'authentification à l'intérieur du tunnel. On ne peut pas désactiver ce comportement, même en mettant en commentaire toute référence à CHAP/MSCHAP dans tous les fichiers de freeRADIUS.

Puisque nous utilisons TTLS, donc un tunnel TLS, toutes les recommandations applicables à TLS s'appliquent ici aussi :

  • Il faut utiliser autre chose qu'un certificat x509 qui n'est pas à votre nom. De plus, utiliser autre chose qu'un certificat auto-signé permet d'en changer régulièrement sans devoir le diffuser à tous les utilisateurs de votre réseau sans fil (diffuser le certificat de l'autorité de certification (AC) suffit). Quant à savoir s'il faut utiliser une AC publique reconnue ou une AC privée interne à votre organisation pour signer le certificat de votre serveur RADIUS, la problématique qui se pose est la même qu'avec OpenVPN : utiliser une AC publique vous expose à un point d'accès malveillant portant le même nom (les machines de vos clients Wi-Fi s'y connecteront donc automatiquement, sans rien demander à leur utilisateur car le standard Wi-Fi est ainsi fait) et qui utilisera un certificat x509 parfaitement légitime obtenu auprès de la même AC publique que vous pour voler les mots de passe, savoir qui est connecté au réseau, etc.
  • Il faudrait normalement utiliser TLS v1.2 ainsi que des suites cryptographiques pas complètement moisies, comme dans tout usage de TLS. Sauf que la compatibilité avec les clients est loin d'être garantie. Illustration : GNOME Network Manager dans Debian Jessie, qui se repose sur wpa_supplicant, utilise uniquement TLS v1.0… Pour changer les suites cryptographiques autorisées, il faut modifier la valeur de « cipher_list » dans /etc/freeradius/eap.conf et ça mange une chaîne habituelle dans le monde TLS.

Pro-tips : j'imagine que vous allez vouloir tester cette nouvelle configuration. GNOME network-manager est parfois long à la détente quand on change le mode d'authentification. Ce qui fait qu'on a choisi « MD5 » (alors qu'on utilisait PAP avant, par exemple) et que l'on constate que l'authentification fonctionne. Ce n'est pas que freeRADIUS a accepté du EAP-MD5 (qu'on vient de désactiver) mais simplement que GNOME Network Manager a bien utilisé du EAP-PAP. Il faut réessayer et là, l'authentification échouera. Mon conseil est de lancer freeradius en mode debug avec la commande « freeradius -X ». Comme cela, on voit très précisément les modules qui ont été utilisés et les résultats produits.

PepperSpot

Faire le ménage

PepperSpot s'occupe de l'adressage de son interface réseau de travail et de fournir un serveur DHCPv4. Il faut donc mettre en commentaire notre configuration applicable à TESTAP dans /etc/dhcp/dhcpd.conf et redémarrer le serveur DHCPv4. Même chose pour DHCPv6 qui ne pourra pas se binder sur l'interface tun crée par PepperSpot comme nous l'avons déjà exposé. De même, le fichier /etc/network/interfaces doit désormais ressembler à ça pour notre interface testap (le reste est inchangé) :

# TESTAP 
auto testap
iface testap inet manual
  post-up ip link add link eth0 name $IFACE type vlan id 2 2> /dev/null || true
  post-up ip link set $IFACE up
 
iface testap inet6 manual
Lire le manuel

Le fichier README de PepperSpot est plutôt complet. Après toute la configuration qu'on a déjà abattu, les sections encore pertinentes sont la 3 sur la configuration du serveur web pour servir la page d'auth, la 1,3 et 1,4 sur les dépendances nécessaires à la compilation de PepperSpot, la 5 sur la compilation, l'installation et la configuration ainsi que la 6 sur le lancement du portail captif.

Je vais formuler quelques remarques complémentaires ci-dessous.

Remarque sur l'IPv6 forwarding et l'autoconfiguration IPv6 (section 1.1.3)

Avec Linux, il est parfaitement possible d'être un routeur et d'accepter des messages RA sur certaines interfaces pour s'autoconfigurer. Pour ce faire, il faut donner la valeur « 2 » à « net.ipv6.conf.all.forwarding » dans sysctl.conf. Pas besoin de Quagga ni du protocole de routage RIP.

Remarques sur le serveur web

La version PHP du portail captif fonctionne très bien out-of-box dès lors que l'on a installé libapache2-mod-php5, ce qui est logique.

Pour la version CGI, il faut activer CGI :

sudo a2enmod cgi && sudo systemctl reload apache2

Notons que le script n'est pas prévu pour être exécuté par fcgid car il provoque l'affichage du portail captif dans les logs d'Apache et, car la variable d'environnement « HTTPS » (qui indique si l'URL demandée contient « https ») n'est pas définie par fcgid, ce qui ne convient pas à PepperSpot.

Dans tous les cas, depuis Apache 2.4, il convient de remplacer les lignes « Order [...] » par « Require all granted ». De même, le fichier représentant le virtualhost doit avoir une extension « .conf » afin qu'il soit pris en considération par Apache.

Remarques sur les règles de filtrage

Dans pepper.iptables, en plus d'effectuer la modification des variables $EXTIF4 et $INTIF4, nous modifions la règle « $IPTABLES -A INPUT -i $EXTIF4 -j REJECT » pour remplacer le REJECT par un DROP. Ensuite, nous autorisons notre AP Cisco à causer avec notre serveur RADIUS (à mettre avant la règle précédemment modifiée) :

$IPTABLES -A INPUT -i $EXTIF -s 198.18.255.1 -d 198.18.255.0 -p udp -m multiport --dports 1812,1813 -m state --state NEW -j ACCEPT

Nous décommentons également la règle « Allow ICMP echo on other interfaces (input) », car c'est toujours utile pour debug.

Enfin, avant « Drop everything to and from $INTIF (forward) », on ajoute les règles suivantes pour empêcher tout trafic entre nos clients Wi-Fi et le réseau de management :

$IPTABLES -A FORWARD -i testap -d 198.18.255.0/31 -j DROP
$IPTABLES -A FORWARD -i tun0 -d 198.18.255.0/31 -j DROP
$IPTABLES -A FORWARD -i tapsec -d 198.18.255.0/31 -j DROP

Et l'on ajoute les règles suivantes pour éviter tout trafic entre nos deux réseaux Wi-Fi :

$IPTABLES -A FORWARD -i testap -o tapsec -j DROP
$IPTABLES -A FORWARD -i tun0 -o tapsec -j DROP
 
$IPTABLES -A FORWARD -i tapsec -o testap -j DROP
$IPTABLES -A FORWARD -i tapsec -o tun0 -j DROP

Pour faire propre, on peut créer des variables au début du script pour stocker tapsec, tun0 & co mais on va voir que le but de ce script est de servir de rares fois. 🙂

Dans pepper.ip6tables, en plus d'effectuer la modification des variables $EXTIF4 et $INTIF4, nous mettons en commentaire la règle qui autorise RIPng. Nous modifions également la règle « $IP6TABLES -A INPUT -i $EXTIF6 -j REJECT » pour remplacer le REJECT par un DROP.

Nous ajoutons une règle pour autoriser DHCPv6 depuis l'intérieur et ping6 depuis partout (avant « Allow everything on loopback ») :

$IP6TABLES -A INPUT -i tapsec -p udp --sport 546 --dport 547 -j ACCEPT
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT

Avant « Drop everything to and from $INTIF (forward) », nous ajoutons les règles suivantes pour bloquer tout trafic entre nos deux réseaux Wi-Fi :

$IP6TABLES -t filter -A FORWARD -i testap -o tapsec -j DROP
$IP6TABLES -t filter -A FORWARD -i tun1 -o tapsec -j DROP
 
$IP6TABLES -t filter -A FORWARD -i tapsec -o testap -j DROP
$IP6TABLES -t filter -A FORWARD -i tapsec -o tun1 -j DROP

Note : fermer le port 3990 est une mauvaise idée : PepperSpot utilise ce port pour communiquer avec lui-même pour l'authentification.

Pour rendre ces règles de filtrage persistantes, nous utilisons netfilter-persistent. D'abord, on ajoute les règles à Netfilter en exécutant les scripts pepper.iptables et pepper.ip6tables. Ensuite, on installe netfilter-persistent :

sudo apt-get install netfilter-persistent iptables-persistent

Lors de l'installation, il sera demandé s'il faut conserver les règles de filtrage existantes. Il faudra acquiser. Les règles de filtrage (et de NAPT) seront restaurées à chaque boot de la passerelle.

Remarque sur la configuration

Voici notre fichier /etc/pepper.conf une fois les lignes vides ou commentées retirées :

ipversion dual
net 198.18.0.0/24
staticipv6 2001:470:c8d6:2::1
ipv6prefix 2001:470:c8d6:2::/64
dns1 89.234.141.66
dns2 89.234.186.18
radiusserver1 ::1
radiusserver2 127.0.0.1
radiussecret <secret_radius>
radiuslocationid isocc=fr,cc=33,ac=67000,network=portail_ipv6
dhcpif testap
uamserver https://198.18.0.1/cgi-bin/hotspotidentifiant.cgi
uamserver6 https://[2001:470:c8d6:2::1]/cgi-bin/hotspotidentifiant.cgi
uamsecret testing234
Remarques sur le lancement automatique

Dans un premier temps, on peut lancer PepperSpot en premier plan pour voir si la config' est OK :

sudo pepper -fd

Un bout d'initscript est présent dans le dossier « debian » des sources, mais une unit systemd nous a paru plus adaptée (nous la stockons dans /etc/systemd/system/pepperspot.service) :

[Unit]
Description=Pepperspot
After=syslog.target network.target netfilter-persistent.service
 
[Service]
Type=simple
ExecStart=/usr/sbin/pepper
 
[Install]
WantedBy=multi-user.target

On active ce service et on le lance :

sudo systemctl enable pepperspot
sudo systemctl start pepperspot

Données de connexion

Accounting RADIUS
Sur un point d'accès Cisco Aironet 1120B

Pour activer l'accounting RADIUS, il faut utiliser les commandes suivantes :

aaa accounting network acct_methods start-stop group rad_eap
 
dot11 ssid TESTAP-sec
 accounting acct_methods

Pour rappel : l'AP n'envoie pas les adresses IP du client dans les requêtes d'accounting. L'attribut RADIUS correspondant, « Framed-IP-Address » peut seulement être ajouté aux requêtes de demande d'accès en utilisant la commande « radius-server attribute 8 include-in-access-req ».

Sur un point d'accès WRT54GL avec OpenWRT

Le NAS privateur de Broadcom ne prend pas en charge l'accounting RADIUS. hostapd le fait, mais nous avons vu qu'il n'est pas utilisable avec notre WRT54GL.

Actualisation fréquente…

Un NAS peut remonter fréquemment au serveur RADIUS les données concernant un client (volume de données échangé, etc.). Par défaut, notre AP Cisco Aironet envoie uniquement des Accounting-Start et des Accounting-Stop qui marquent le début et la fin de la connexion du client Wi-Fi. Par défaut, PepperSpot envoie des informations toutes les 10 minutes. Lorsqu'on est sur de la facturation au volume consommé (comme sur les abonnements Internet mobile en France), par exemple, on peut avoir envie d'envoyer des mises à jour plus fréquentes afin de prévenir plus rapidement le client qu'il va/a dépasser son forfait et/ou afin d'avoir une granularité permettant de ne pas se faire gruger. Une actualisation plus fréquente suppose un bon dimensionnement du serveur RADIUS qui devra écrire, sur un support de stockage, chaque ticket d'accounting correspondant à chaque client Wi-Fi. Dans le cadre de ce TP, une actualisation plus fréquente n'est pas pertinente, mais la mise en œuvre de celle-ci est intéressante à découvrir.

Il existe un attribut RADIUS pour conseiller à un NAS un délai entre chaque actualisation : « Acct-Interim-Interval ». PepperSpot en tient compte, notre borne Cisco non. La liste des attributs RADIUS pris en charge par notre Aironet est disponible en ligne. La liste des attributs RADIUS normalisés (en dehors des attributs spécifiques à des vendeurs) est disponible sur le site web de l'IANA.

Sur notre Aironet, il faut utiliser la commande suivante pour activer l'actualisation fréquente de l'accounting :

aaa accounting update periodic <delai_en_minutes>

On notera également que, sur notre Cisco Aironet, il faut parfois saisir des commandes pour activer la prise en compte de certains attributs RADIUS. Exemple : « dot1x timeout reauth-period server » pour indiquer à la borne de prendre en considération l'attribut « Session-Timeout » qui indique la durée maximale de la connexion d'un client avant réauthentification (on trouve cela dans les gares, les aéroports et sur les hotspots payants où l'on peut acheter 10 minutes, 30 minutes, 1 heure d'accès). En 2016, je trouve cela parfaitement idiot : RADIUS existe pour centraliser l'authentification et l'autorisation. S'il faut saisir une commande sur chacun de nos AP pour activer la prise en compte de chaque attribut RADIUS sur chaque équipement réseau, autant de pas utiliser RADIUS et pousser toute la config' sur chaque AP.

… et disparition subite d'un client Wi-Fi

Un autre attribut RADIUS est intéressant : « Idle-Timeout ». Il permet de déconnecter un client Wi-Fi en cas d'inactivité constatée au niveau du trafic réseau émis. Forcément, il n'est pas pris en charge par notre Cisco Aironet, mais il est pris en charge par PepperSpot. Ce n'est pas grave puisqu'en cas de déconnexion/reconnexion, notre Cisco Aironet se rend compte que la précédente connexion a été coupée et émet un Accounting-Stop pour cette connexion. En revanche, si un client quitte la zone de couverture de notre AP (ou réseau d'AP) sans s'être déconnecté au préalable (notamment sur le portail captif), alors aucun Accounting-Stop n'est émis et l'on obtient une connexion zombie dans notre accounting RADIUS. C'est dans ce genre de cas que l'attribut « Idle-Timeout » est intéressant.

Comment ajouter un attribut de réponse dans une réponse RADIUS ?

Les attributs qui nous intéressant s'appliquent à l'ensemble de nos utilisateurs. Il faut donc les ajouter dans la définition de chaque utilisateur dans notre fichier users ou dans notre base de données. Trop chiant et redondant. On peut créer des groupes, leur appliquer nos attributs et attribuer ces groupes à nos utilisateurs. Trop chiant. Il existe un utilisateur, « DEFAULT » qui matche toujours, pour tout identifiant. Utilisons ça.

Dans notre fichier /etc/freeradius/users, avant toute définition d'un quelconque utilisateur réel, ajoutons ceci :

DEFAULT	
	Acct-Interim-Interval := 120,
	Idle-Timeout := 600

On positionne le délai max entre deux actualisations à 2 minutes et on positionne la clôture d'une connexion zombie après 10 minutes d'inactivité.

Si vous utilisez le module « sql » pour remonter les informations sur les utilisateurs de votre réseau Wi-Fi, alors les modules « files » et « sql » sont complémentaires : chacun apportera sa part de la réponse finale.

Si vous utilisez uniquement le module « files », il y a une petite méchanceté : ce module s'arrête à la première occurrence trouvée. Il s'arrêtera donc à l'utilisateur DEFAULT et rejettera donc votre utilisateur. Pour pas que cela se produise, il faut ajouter l'attribut de contrôle « Fall-Through := yes » à la fin de la définition de l'utilisateur DEFAULT.

Si vous voulez utiliser uniquement MySQL, c'est un peu plus compliqué : il faut utiliser un « default user profile » c'est-à-dire un utilisateur qui fait partie d'un groupe auquel seront appliqués les attributs de réponse désirés. Par défaut, l'identifiant de cet utilisateur est « default ». Pour créer ce profile, il faut exécuter ces deux requêtes SQL :

INSERT INTO radusergroup(groupname, priority, username) VALUES('defaultgroup', 1, 'DEFAULT');
INSERT INTO radgroupreply(id, groupname, attribute, op, VALUE) VALUES((1, 'defaultgroup', 'Idle-Timeout', ':=', '600'), (2, 'defaultgroup', 'Acct-Interim-Interval', ':=',  '120'));

Puis, dans /etc/freeradius/sql/mysql/dialup.conf, il faut décommenter la ligne « default_user_profile = "DEFAULT" et redémarrer freeRADIUS.

Attributs de réponse et subtilité EAP-TTLS

Lors d'un échange EAP-TTLS, les attributs de réponse seront insérés dans la réponse « Access-Challenge » qui suit un « Access-Request », c'est-à-dire qu'ils seront insérés durant l'initialisation du tunnel TLS. Or, ce tunnel est de bout en bout, entre le serveur RADIUS et le client Wi-Fi, alors que nos attributs sont destinés au NAS de l'AP Wi-Fi ! Dans le cas de PepperSpot, il est NAS et utilisateur final à la fois et il n'y a pas de tunnel TLS donc il voit bien les attributs et les prend en compte. Mais pas notre AP Cisco qui est un intermédiaire.

De même, lorsqu'on utilise EAP-TTLS, le véritable identifiant de l'utilisateur de notre réseau Wi-Fi circule uniquement à l'intérieur du tunnel chiffré. Or, le NAS utilise l'identifiant retourné par le serveur RADIUS lors de l'authentification dans ses requêtes d'accounting. Il s'agit donc de l'identifiant hors tunnel TLS, donc d'un identifiant bidon, bien souvent. C'est conçu pour cela, afin d'éviter que le vrai identifiant circule sur le réseau Wi-Fi, ce qui permet aisément le pistage (qui était connecté à notre réseau Wi-Fi de telle heure à telle heure ?) puisque tout le monde peut capter le trafic Wi-Fi non chiffré ou chiffré avec une clé commune comme c'est le cas d'un réseau WPA(2) PSK.

Pour résoudre ces deux problèmes, il faut demander au serveur RADIUS de copier les attributs de réponse qu'il a positionnés dans le tunnel TLS pour les positionner en dehors du tunnel. Cela se fait dans la configuration du module « ttls », dans le fichier /etc/freeradius/eap.conf, en positionnant la valeur de « use_tunneled_reply » à « yes ». Pour que le bon identifiant apparaisse dans le message « Access-Accept » et donc dans l'accounting, il faut également ajouter la ligne suivante dans les attributs de réponse de l'utilisateur DEFAULT :

User-Name := "%{User-Name}"

Il n'y a pas de risques pour la vie privée, sauf si le lien réseau entre le serveur RADIUS et les AP n'est pas isolé du reste du réseau (mais alors, vous avez un problème bien plus grave). L'identifiant en clair circule uniquement sur ce lien, par entre l'AP et le client Wi-Fi.

Stockage de l'IPv6 des tickets d'accounting

Nous avons vu que notre portail captif envoie les IP (v4 et v6) de nos clients Wi-Fi dans ses requêtes d'accounting. Seulement, freeRADIUS stocke uniquement l'IPv4 dans notre base de données. Ça n'a à peu près aucun sens de stocker l'IPv6 de nos clients compte tenu que PepperSpot accepte uniquement qu'ils utilisent une IPv6 dérivée de leur adresse MAC mais, c'est utile pour découvrir l'envers du décor.

Pour ce faire, il est nécessaire de changer le schéma de la table « radacct » pour ajouter deux colonnes (une pour le préfixe IPv6 du réseau, une pour la partie "machine" de l'IPv6). Voici les requêtes SQL à utiliser :

ALTER TABLE radacct ADD COLUMN framedipv6prefix VARCHAR(25) NOT NULL DEFAULT '' AFTER framedipaddress;
ALTER TABLE radacct ADD COLUMN framedinterfaceid VARCHAR(25) NOT NULL DEFAULT '' AFTER framedipv6prefix;

Ensuite, il faut modifier la requête SQL utilisée par freeRADIUS pour réaliser l'accounting. Cela se passe dans le fichier /etc/freeradius/sql/mysql/dialup.conf. La requête à modifier se nomme « accounting_start_query ». Il faut la modifier comme cela :

INSERT INTO ${acct_table1} \
(acctsessionid,    acctuniqueid,     username, \
 realm,            nasipaddress,     nasportid, \
 nasporttype,      acctstarttime,    acctstoptime, \
 acctsessiontime,  acctauthentic,    connectinfo_start, \
 connectinfo_stop, acctinputoctets,  acctoutputoctets, \
 calledstationid,  callingstationid, acctterminatecause, \
 servicetype,      framedprotocol,   framedipaddress, \
 framedipv6prefix, framedinterfaceid, \
 acctstartdelay,   acctstopdelay,    xascendsessionsvrkey) \
VALUES \
('%{Acct-Session-Id}', '%{Acct-Unique-Session-Id}', \
 '%{SQL-User-Name}', \
 '%{Realm}', '%{NAS-IP-Address}', '%{NAS-Port}', \
 '%{NAS-Port-Type}', '%S', NULL, \
 '0', '%{Acct-Authentic}', '%{Connect-Info}', \
 '', '0', '0', \
 '%{Called-Station-Id}', '%{Calling-Station-Id}', '', \
 '%{Service-Type}', '%{Framed-Protocol}', '%{Framed-IP-Address}', \
 '%{Famed-IPv6-Prefix}', '%{Framed-Interface-id}', \
 '%{%{Acct-Delay-Time}:-0}', '0', '%{X-Ascend-Session-Svr-Key}')"

On redémarre freeRADIUS et c'est prêt. \o/

Au passage, on notera que PepperSpot ouvre deux tickets d'accounting, un avec l'IPv4, l'autre avec l'IPv6 du client Wi-Fi et qu'un seul des tickets contient l'identifiant : celui qui contient l'IP qui a servi à accéder à la page d'authentification. Ce n'est donc pas terrible pour retrouver une association MAC<->identifiant sur des machines clientes mutualisées (genre les salles de TP) car il n'y pas de valeurs en commun. Il faut faire un croisement en prenant un triplet MAC (calling station ID) + Starttime/Stoptime (il diffère d'au plus une seconde entre les deux tickets) et le session-id (qui est incrémenté d'au plus une unité entre les deux tickets).

Ménage régulier

Toutes les données de connexion doivent être conservées un an, pas plus, pas moins. Il est donc important de purger régulièrement l'accounting RADIUS. DaloRADIUS propose une telle option dans son interface, mais il est inhumain de réaliser cette opération tous les jours ou même tous les mois.

Du coup, laissons ça à cron en ajoutant ceci dans la crontab de root pour un ménage quotidien :

crontab -e
0 0 * * * echo "DELETE FROM radacct where TIMESTAMPDIFF(YEAR, acctstarttime, NOW()) >=1;" | mysql -u radius -p<mdp> radius

Pour les personnes qui s'offusquent de stocker un mdp dans la crontab de root, rappelons que sous Debian, il existe un utilisateur MySQL, « debian-sys-maint », qui a accès en écriture à toute base de données MySQL et dont le mdp est accessible à root depuis /etc/mysql/debian.cnf. Rappelons aussi que qui à accès au compte root a accès à tout, de toute façon.

Calculer à partir de starttime permet de virer également les connexions interrompues brutalement.

Outro

On se rend compte que l'accounting RADIUS a quelques limites franches : il est agnostique au protocole de couche 3 donc il faut conserver ailleurs l'association entre couche 3 et couche 2, et ce qu'il est possible de faire dépend vraiment des implémentations des NAS. :/

Logs DHCP

Sur TESTAP, c'est PepperSpot qui fait office de serveur DHCPv4. Sur TESTAP-sec, c'est l'implémentation de l'ISC que nous avons configurée. Dans tous les cas, il n'y a pas d'allocations d'adresses stateful avec DHCPv6. Dans les deux cas, les allocations effectuées sont journalisées dans les fichiers syslog et daemon.log. Nous allons voir ici comment trier nos logs et garantir leur effacement au-delà d'un an.

On install rsyslog (syslog propose aussi des filtres mais leur syntaxe me pique un peu plus les yeux) :

sudo apt-get install rsyslog logrotate

On crée un fichier /etc/rsyslog.d/dhcpd.conf avec le contenu suivant :

if $programname == 'dhcpd' then /var/log/dhcp/dhcpd.log
& ~
 
if $programname == 'pepperspot' then /var/log/dhcp/dhcpd.log
& ~

Tout ce qui est émit par PepperSpot et par notre serveur DHCP est rangé dans /var/log/dhcp/dhcpd.log .

On fait prendre en compte la nouvelle configuration par rsyslog :

sudo systemctl restart rsyslog

On crée un fichier /etc/logrotate.d/dhcpd avec le contenu suivant :

/var/log/dhcpd.log {
    monthly
    missingok
    rotate 12
    compress
    delaycompress
    ifempty
    create 640 root adm
    sharedscripts
    olddir /var/log/archives/dhcpd
    postrotate
        service rsyslog rotate > /dev/null
    endscript
}

Il est de notre ressort de créer le dossier d'archivage /var/log/archives/dhcpd :

sudo mkdir -p /var/log/archives/dhcpd
Journaliser les associations MAC<->IP

On installe les deux logiciels KiVontBien :

sudo apt-get install arpwatch ndpmon

Dans /etc/default/arpwatch, il faut ajouter « -Q » à la variable « ARGS » pour demander à arpwatch de ne pas envoyer d'emails à chaque événement suspect, car ce n'est pas l'usage que nous attendons d'arpwatch dans ce contexte.

Par défaut, arpwatch est actif uniquement sur eth0. Il faut ajouter les interfaces supplémentaires (testap et tapsec, dans notre cas) dans /etc/arpwatch.conf.

On redémarre arpwatch pour qu'il prenne en compte la nouvelle configuration :

sudo systemctl restart arpwatch

ndpmon ne nécessite pas de configuration particulière.

Comme pour les logs DHCP, nous trions nos logs avec rsyslog en ajoutant un fichier /etc/rsyslog.d/arp-ndp-watch.conf avec le contenu suivant :

if $programname == 'arpwatch' then /var/log/assocs/mac-ipv4.log
& ~
 
if $programname == 'NDPMon' then /var/log/assocs/mac-ipv6.log
& ~

Il est de notre ressort de créer le dossier de stockage des logs /var/log/assocs :

sudo mkdir /var/log/assocs

On fait prendre en compte la nouvelle configuration par rsyslog :

sudo systemctl restart rsyslog

De la même manière, nous configurons logrotate avec un fichier /etc/logrotate.d/arp-ndp-watch avec le contenu suivant :

/var/log/assocs/*.log {
    monthly
    missingok
    rotate 12
    compress
    delaycompress
    ifempty
    create 640 root adm
    sharedscripts
    olddir /var/log/archives/assocs/
    postrotate
        service rsyslog rotate > /dev/null
    endscript
}

Il est de notre ressort de créer le dossier d'archivage /var/log/archives/assocs/ :

sudo mkdir -p /var/log/archives/assocs/

Administration, supervision, types de flux

Supervision

On installe Monit :

sudo apt-get install moni monitoring-plugins

Il faut activer le démon HTTP dans /etc/monit/monitrc. Nous n'activons pas les alertes emails dans ce même fichier, c'est volontaire : nous trouvons cela inutile dans le cadre de ce TP.

Il faut créer les fichiers de conf' qui définissent nos services dans /etc/monit/conf.d/ . Nos configs sont copiés/inspirées du wiki officiel Monit.

ap :

 check host AP-Cisco with address 198.18.255.1
   if failed icmp type echo with timeout 5 seconds then alert
   if failed port 161 type udp then alert

apache2 :

 check process apache with pidfile /var/run/apache2/apache2.pid
   group www
   group apache
   start program = "/etc/init.d/apache2 start"
   stop program  = "/etc/init.d/apache2 stop"
   if failed host 127.0.0.1 port 443 with protocol https and request "/" with timeout 10 seconds for 4 times within 5 cycles then alert
   depend apache_bin
   depend apache_rc
 
 check file apache_bin with path /usr/sbin/apache2
   group apache
   include /etc/monit/templates/rootbin
 
 check file apache_rc with path /etc/init.d/apache2
   group apache
   include /etc/monit/templates/rootbin

FreeRADIUS :

 check process freeradius with pidfile /var/run/freeradius/freeradius.pid
   start program = "/etc/init.d/freeradius start"
   stop program = "/etc/init.d/freeradius stop"
   if failed host 127.0.0.1 port 1812 type udp protocol radius secret <secret_radius> then alert
   if failed host 127.0.0.1 port 1813 type udp protocol radius secret <secret_radius> then alert

MySQL :

 check process mysqld with pidfile /var/run/mysqld/mysqld.pid
   group database
   group mysql
   start program = "/etc/init.d/mysql start"
   stop  program = "/etc/init.d/mysql stop"
   if failed host localhost port 3306 protocol mysql with timeout 5 seconds for 3 times within 4 cycles then alert
   if failed unixsocket /var/run/mysqld/mysqld.sock protocol mysql for 3 times within 4 cycles then alert
   depend mysql_bin
   depend mysql_rc
 
 check file mysql_bin with path /usr/sbin/mysqld
   group mysql
   include /etc/monit/templates/rootbin
 
 check file mysql_rc with path /etc/init.d/mysql
   group mysql
   include /etc/monit/templates/rootbin

NTPd :

 check process ntpd with pidfile /var/run/ntpd.pid
   start program = "/etc/init.d/ntp start"
   stop  program = "/etc/init.d/ntp stop"
   if failed host 127.0.0.1 port 123 type udp then alert
   if 5 restarts within 5 cycles then timeout

rsyslog :

 check process rsyslogd with pidfile /var/run/rsyslogd.pid
   group system
   group rsyslogd
   start program = "/etc/init.d/rsyslog start"
   stop  program = "/etc/init.d/rsyslog stop"
   depend on rsyslogd_bin
   depend on rsyslogd_rc
   depend on rsyslog_file
 
 check file rsyslogd_bin with path /usr/sbin/rsyslogd
   group rsyslogd
   include /etc/monit/templates/rootbin
 
 check file rsyslogd_rc with path "/etc/init.d/rsyslog"
   group rsyslogd
   include /etc/monit/templates/rootbin
 
 check file rsyslog_file with path /var/log/syslog
   group rsyslogd
   if timestamp > 65 minutes then alert
   if failed permission 640  then unmonitor
   if failed uid root        then unmonitor
   if failed gid adm         then unmonitor

dhcpd4 :

check process isc-dhcp-server with pidfile /var/run/dhcpd.pid

dhcpd6 :

check process isc-dhcp6-server with pidfile /var/run/dhcpv6/dhcpdv6.pid

On redémarre monit afin de prendre en compte les nouveaux checks à réaliser :

sudo systemctl restart monit

L'interface web de monit est désormais accessible en http://127.0.0.1:2812/ . Cette page est uniquement consultable depuis localhost car cela est défini dans la configuration et que notre pare-feu bloque les connexions entrantes.

Métrologie

On installe munin :

sudo apt-get install munin munin-node

Nous n'avons rien d'autre à faire : l'interface web de Munin est accessible en http://[::1]/munin et elle présente les métriques qui nous intéressent (trafic réseau, IO, charge CPU, etc.). Cette page est uniquement consultable depuis localhost car cela est défini dans la configuration et que notre pare-feu bloque les connexions entrantes.

Typologie des flux réseaux

On installe ntop-ng :

sudo apt-get install ntopng

Il n'y a rien d'autre à faire : l'interface web de ntop-ng est accessible en http://127.0.0.1:3000 avec le couple identifiant/mdp admin/admin. Cette page est uniquement consultable depuis localhost car notre pare-feu bloque les connexions entrantes.

Zone d'administration

On crée une place pour notre page web d'administration dans le DocumentRoot d'Apache :

sudo rm -r /var/www/*
sudo mkdir /var/www/admin
sudo chown www-data:www-data /var/www/admin

On modifie le virtualhost Apache de PepperSpot, /etc/apache2/sites-enabled/pepperspot.conf, pour ajouter une authentification pour toute la zone d'administration :

<Directory /var/www/admin>
    AuthName "Olympe"
    AuthType Basic
    AuthUserFile "/etc/apache2/htpasswd"
    Require valid-user
</Directory>

On ajoute un utilisateur autorisé dans le fichier htpasswd (qui peut être nommé différemnent, d'ailleurs) :

sudo htpasswd -c /etc/apache2/htpasswd <identifiant>
Gestion de la base de données RADIUS

On crée une place pour daloRADIUS et on l'y extrait :

cd /var/www/admin
sudo tar xf $HOME/daloradius-0.9.9.tar.gz
sudo mv daloradius-0.9-9 daloradius
sudo chown -R www-data:www-data daloradius
sudo chmod 6400 daloradius/library/daloradius.conf.php

Il faut ensuite lire le manuel (le fichier « INSTALL ») et installer les dépendances requises :

sudo apt-get install libapache2-mod-php5 php5-gd php-pear php-db php-mail php5-mysql

Puis, il faut enrichir la base de données de FreeRADIUS avec les tables de daloRADIUS :

mysql -u radius -p radius < daloradius/contrib/db/mysql-daloradius.sql

Enfin, il faut configurer les paramètres d'accès à la base de données dans daloradius/library/daloradius.conf.php .

L'interface web de daloRADIUS est désormais disponible en http://[::1]/admin/daloradius/ . Elle est accessible avec le couple identifiant/mdp administrator/radius.

Par défaut, daloRADIUS n'affiche pas les IPv6. Il est possible de patcher une partie des pages « acct-* » pour afficher l'information désirée. Notre patch est disponible ici : daloradius-ipv6.patch.

DaloRADIUS ne prend pas en charge SHA1 malgré ce qui est indiqué dans l'interface web (le mdp sera bel et bien stocké en clair). Pour corriger cela, il faut ajouter ce qui suit ligne 440 de mng-new.php (rien n'est implémenté dans mng-new-quick.php, pas même MD5 donc nous ne nous sommes pas occupés de ce fichier…) :

} elseif (preg_match("/sha1/i", $passwordtype)) {
    $dbPassword = "SHA1('$password')";
}
SNMP

Comme annoncé plus haut, nous utilisons SNMP pour remonter quelques infos pertinentes depuis notre AP.

SNMPd sur notre Cisco Aironet

Voici les commandes à utiliser pour activer SNMP sur notre AP Cisco :

access-list 1 permit 198.18.255.0
snmp-server community <nom_idiot_de_la_communauté_SNMP> RO 1

Oui, il est nécessaire d'utiliser un numéro pour désigner l'access-list.

OIDs intéressants :

  • Uptime : 1.3.6.1.2.1.1.3.0
  • Load average (en pourcentage). Sur les 5 dernières secondes : 1.3.6.1.4.1.9.9.109.1.1.1.1.6.1 ; Sur la dernière minute : 1.3.6.1.4.1.9.9.109.1.1.1.1.7.1 ; Sur les 5 dernières minutes : 1.3.6.1.4.1.9.9.109.1.1.1.1.8.1
  • RAM (octets). Libre : 1.3.6.1.4.1.9.9.48.1.1.1.6.1 ; Occupée : 1.3.6.1.4.1.9.9.48.1.1.1.6.1
  • Nombre total de clients Wi-Fi associés depuis le boot : 1.3.6.1.4.1.9.9.273.1.1.3.1.1.1
  • Nombre total de clients Wi-Fi authentifiés depuis le boot : 1.3.6.1.4.1.9.9.273.1.1.3.1.2.1
  • Nombre de clients Wi-Fi associés en ce moment (authentifiés ou non) : 1.3.6.1.4.1.9.9.273.1.1.2.1.1.1
SNMPd sur notre WRT54GL avec OpenWRT

D'abord, il faut installer un démon snmpd. J'ai choisi snmpd, la version complète que l'on retrouve partout sous GNU/Linux :

opkg update && opkg install snmpd

Ensuite, il faut configurer ce démon dans /etc/config/snmp. Voici ma configuration complète :

config agent
        option agentaddress UDP:161
 
config com2sec public
        option secname ro
        option source 198.18.255.0
        option community <nom_idiot_communauté>
 
config group public_v2c
        option group public
        option version v2c
        option secname ro
 
config view all
        option viewname all
        option type included
        option oid .1
 
config access public_access
        option group public
        option context none
        option version any
        option level noauth
        option prefix exact
        option read all
        option write none
        option notify none
 
config system
        option sysLocation      'test'
        option sysContact       'test@example.com'
        option sysName          'testap'

Enfin, on redémarre snmpd :

/etc/init.d/snmpd restart

OIDs intéressants :

  • Uptime : iso.3.6.1.2.1.25.1.1.0
  • RAM. Total : iso.3.6.1.2.1.25.2.3.1.5.1 ; Occupée : iso.3.6.1.2.1.25.2.3.1.6.1

Pour avoir le nombre de clients Wi-Fi associés en ce moment, il faut bricoler un peu :

mkdir -p /usr/local/bin

Dans un fichier /usr/local/bin/wlassocs.sh, mettre le contenu suivant :

#!/bin/sh
wlc assoclist | sed '/^$/d' | wc -l | sed 's/ //g'

Penser à rendre ce script exécutable :

chmod +x /usr/local/bin/wlassocs.sh

Ajouter le bout de config' qui suit dans /etc/config/snmp (l'OID est totalement arbitraire) :

config exec
        option name     wlassocs
        option prog     /usr/local/bin/wlassocs.sh
        option miboid   .1.3.6.1.2.1.2.2.1.50

On redémarre snmpd et le nombre clients Wi-Fi sera disponible en iso.3.6.1.2.1.2.2.1.50.1.1 \o/

Page d'administration

Notre page est une simple page PHP qui réalise des requêtes SNMP et SQL pour récupérer des informations sur notre AP Wi-FI et pour récupérer les informations sur les utilisateurs connectés à nos réseaux Wi-Fi depuis l'accounting RADIUS. Elle fait également le lien avec les outils de gestion et de supervision que nous avons déployé.

Pour réaliser des requêtes SNMP depuis PHP, il faut installer le module SNMP pour avoir accès à la classe SNMP :

sudo apt-get install php5-snmp

Le reste, c'est du PHP classique :

<?php
	$AP_IP                  = '198.18.255.1';
	$AP_SNMP_COMMUNITY	= '<communauté_SNMP>';
 
	// Sans le masque du réseau
	$IPv6_PREFIX            = '2001:470:c8d6:2:';
 
	$DB_HOST                = 'localhost';
	$DB_NAME                = 'radius';
	$DB_USER                = 'radius';
	$DB_PWD                 = '<mdp>';
?>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Page d'administration réseau sans fil sécurisé et monitoré</title>
	</head>
 
	<body>
		<h1>Quelques infos sur le point d'accès (SNMP) : </h1>
 
			<?php
				$snmp = new SNMP(SNMP::VERSION_2C, $AP_IP , $AP_SNMP_COMMUNITY, 5000, 1); // timeout = 5 secs, 1 seul essai.
				$snmp->quick_print = 1; // Ne pas afficher autre chose que la valeur des OID remontés
			?>
 
			Uptime du point d'accès Cisco (jours:heures:minutes:secondes.ms) : <?php echo @$snmp->get('1.3.6.1.2.1.1.3.0'); ?>
 
			<br/>Charge CPU du point d'accès Cisco (5 secs, 1 minute, 5 minutes, en pourcentage) : <?php echo @$snmp->get('1.3.6.1.4.1.9.9.109.1.1.1.1.6.1')
			.' '.@$snmp->get('1.3.6.1.4.1.9.9.109.1.1.1.1.7.1').' '.@$snmp->get('1.3.6.1.4.1.9.9.109.1.1.1.1.8.1'); ?>
 
			<br />Mémoire (octets) : libre = <?php echo @$snmp->get('1.3.6.1.4.1.9.9.48.1.1.1.6.1').' | occupée (octets) : '.@$snmp->get('1.3.6.1.4.1.9.9.48.1.1.1.5.1'); ?>
 
 
			<br /><br /> Nombre total de clients Wi-Fi associés depuis le boot : <?php echo @$snmp->get('1.3.6.1.4.1.9.9.273.1.1.3.1.1.1'); ?>
 
			<br/> Nombre total de clients Wi-Fi authentifiés depuis le boot : <?php echo @$snmp->get('1.3.6.1.4.1.9.9.273.1.1.3.1.2.1'); ?>
 
			<br/> Nombre de clients Wi-Fi associés en ce moment (authentifiés ou non) : <?php echo @$snmp->get('1.3.6.1.4.1.9.9.273.1.1.2.1.1.1'); ?>
 
 
		<br /><br />
		<h1>Utilisateurs authentifiés</h1>
		<?php
			try
			{
				$bdd = new PDO("mysql:host=$DB_HOST;dbname=$DB_NAME", $DB_USER, $DB_PWD);
			}
			catch(Exception $e)
			{
				die('Erreur : '.$e->getMessage());
			}
		?>
 
 
		<h2>TESTAP :</h2>
		<table style="border-collapse: collapse;">
		<tr>
			<th style="border: 1px solid black;">Identifiant</th>
			<th style="border: 1px solid black;">Adresse MAC</th>
			<th style="border: 1px solid black;">Adresse IPv4</th>
			<th style="border: 1px solid black;">Adresse IPv6</th>
			<th style="border: 1px solid black;">Début</th>
			<th style="border: 1px solid black;">Durée (secondes)</th>
			<th style="border: 1px solid black;">Octets reçus</th>
			<th style="border: 1px solid black;">Octets émis</th>
		</tr>
 
		<?php
			// On récupére les personnes connectées sur TESTAP (NAS = '' car Pepperspot ne stocke pas d'IP...), 
			// dont la session est encore en cours (acctstoptime IS null) depuis moins de 6h (une journée de taff universitaire, en gros) 
			// afin d'éviter d'afficher les sessions mal fermées qui resteront sans accounting stop pour toujours.
			$reponse = $bdd->query('SELECT username, callingstationid, framedipaddress, framedinterfaceid, acctstarttime, acctsessiontime, acctinputoctets, acctoutputoctets 
									FROM radacct 
									WHERE nasipaddress = "" AND acctstoptime IS null AND TIMESTAMPDIFF(HOUR, acctstarttime, NOW()) < 6;');
 
			while ($donnees = $reponse->fetch())
			{
			?>
				<tr>
					<td style="border: 1px solid black;"><?php echo $donnees['username']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['callingstationid']; ?></td>
					<td style="border: 1px solid black;"><?php if(!empty($donnees['framedipaddress'])) echo '<a href="http://localhost:3000/lua/host_details.lua?host='.$donnees['framedipaddress'].'">'.$donnees['framedipaddress'].'</a>'; ?></td>
					<td style="border: 1px solid black;"><?php if(!empty($donnees['framedinterfaceid'])) echo '<a href="http://localhost:3000/lua/host_details.lua?host='.$IPv6_PREFIX.$donnees['framedinterfaceid'].'">'.$IPv6_PREFIX.$donnees['framedinterfaceid'].'</a>'; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctstarttime']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctsessiontime']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctinputoctets']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctoutputoctets']; ?></td>
				</tr>
			<?php
			}
 
			$reponse->closeCursor();
		?>
		</table>
 
 
		<h2>TESTAP-sec :</h3>
		<table style="border-collapse: collapse;">
		<tr>
			<th style="border: 1px solid black;">Identifiant</th>
			<th style="border: 1px solid black;">Adresse MAC</th>
			<th style="border: 1px solid black;">Début</th>
			<th style="border: 1px solid black;">Durée (secondes)</th>
			<th style="border: 1px solid black;">Octets reçus</th>
			<th style="border: 1px solid black;">Octets émis</th>
		</tr>
 
		<?php
			$reponse = $bdd->query('SELECT username, callingstationid, acctstarttime, acctsessiontime, acctinputoctets, acctoutputoctets
						FROM radacct
						WHERE nasipaddress="198.18.255.1" AND acctstoptime IS null AND TIMESTAMPDIFF(HOUR, acctstarttime, NOW()) < 6;');
 
			while ($donnees = $reponse->fetch())
			{
			?>
				<tr>
					<td style="border: 1px solid black;"><?php echo $donnees['username']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['callingstationid']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctstarttime']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctsessiontime']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctinputoctets']; ?></td>
					<td style="border: 1px solid black;"><?php echo $donnees['acctoutputoctets']; ?></td>
				</tr>
			<?php
			}
 
			$reponse->closeCursor();
		?>
		</table>
 
 
		<br /><br />
		<h1>Ressources : </h1>
		<a href="/admin/daloradius">DaloRADIUS</a> (créer/supprimer un utilisateur, accès accounting RADIUS, ...)<br/>
		<a href="http://localhost:3000/">Ntop-ng</a> (visualiser l'activité réseau) <br/>
		<a href="http://localhost:2812/">Monit</a> (supervision des services) <br/>
		<a href="/munin/">Munin</a> (metrologie de la machine locale) <br/>
 
		<br /><br />
		<h1>Rappels :</h1>
		Les logs DHCP (TESTAP-sec) et DHCPv6 (*) sont disponibles dans les fichiers /var/log/dhcpd.log et /var/log/archives/dhcpd/<br/>
		Les logs des associations MAC<->IP(v6) sont disponibles dans les fichiers /var/log/assocs/mac-ip(v4|v6).log et /var/log/archives/assocs/<br/>
 
		Visualiser/modifier le pare-feu : iptables - ip6tables
	</body>
</html>

Les « @ » disgracieux se justifient car la classe SNMP est toujours bavarde, même si l'on utilise un try/catch ou un if/else sur la valeur de retour. Autrement dit : il ne semble pas être possible d'afficher une erreur personnalisée sans que l'erreur définie dans la classe s'affiche également. D'où cette mise sous silence.

Mais si vous préférez, vous pouvez utiliser une alternative crade à la classe SNMP comme (-Oqv permet d'afficher uniquement la valeur de l'OID remontée) :

system("/usr/bin/snmpget -Oqv -v 2c -c <communauté_snmp> 198.18.255.1 1.3.6.1.2.1.1.3.0");

Notre page d'administration est accessible en https://[::1]/admin/index.php .

Mobilité IPv6

L'implémentation de la mobilité IPv6 se décompose en deux : une prise en charge de la mobilité IPv6 par le noyau et un démon userland.

Côté noyau

Celui packagé dans Debian est compilé avec la prise en charge de la mobilité IPv6 :

curl http://www.umip.org/docs/umip-install.html 2>/dev/null | grep -oE "CONFIG_[A-Z0-9_]*" | xargs -I{} grep {}= /boot/config-3.11-2-amd64

Dans la sortie de cet enchaînement de commandes, tout doit être à « y » ou à « m » ou même à « n » pour IPSec puisque nous ne l'utiliserons pas ici.

Côté userland

Le paquet umip disponible dans le dépôt apt du projet possède des dépendances impossibles à satisfaire depuis un Debian GNU/Linux amd64 multiarch. On va donc préférer compiler à partir des sources.

Le dépôt git du projet n'est plus accessible depuis plusieurs années semble-t-il. Une ancienne version d'umip est disponible sur Github : https://github.com/jlanza/umip/.

Installons les paquets nécessaires à la compilation :

sudo apt-get install make build-essential indent linux-headers-amd64 autoconf bison flex

La compilation et l'installation ne posent pas de problème particulier :

autoreconf -i
CPPFLAGS='-isystem /usr/include/' ./configure --enable-vt
make
sudo make install

Nous stockons la configuration de notre mobile node, adaptée du site web officiel d'umip, dans /usr/local/etc/mip6d.conf :

# Sample UMIP configuration file for a MIPv6 Mobile Node
NodeConfig MN;
 
# Set DebugLevel to 0 if you do not want debug messages
DebugLevel 10;
 
# Enable the optimistic handovers
OptimisticHandoff enabled;
 
# Disable RO with other MNs (it is not compatible
# with IPsec Tunnel Payload)
DoRouteOptimizationMN disabled;
 
# The Binding Lifetime (in sec.)
MnMaxHaBindingLife 60;
 
# List here the interfaces that you will use
# on your mobile node. The available one with
# the smallest preference number will be used.
Interface "wlan0" {
    MnIfPreference 2;
}
 
# Replace eth0 with one of your interface used on
# your mobile node
MnHomeLink "wlan0" {
    HomeAgentAddress 2001:660:4701:f055:ffff::1;
    HomeAddress 2001:660:4701:f055:ffff::1012/64;
}
 
# Enable IPsec static keying
UseMnHaIPsec disabled;
KeyMngMobCapability disabled;

« 2001:660:4701:f055:ffff::1 » est l'adresse IPv6 pour établir une communication avec notre Home Agent et « 2001:660:4701:f055:ffff::1012/64 » est l'adresse que nous sommes autorisés à utiliser en mobilité.

Oui, il n'y a pas d'IPSec, c'est volontaire, le prof' n'ayant pas configuré cela sur le Home Agent.

On lance umip et on vérifie que cela fonctionne (se reporter à la section « Démonstrations » pour voir ce que l'on obtient) :

sudo mip6d -c /usr/local/etc/mip6d.conf

Si cela ne fonctionne pas, peut-être y a-t-il un filtrage BCP38 sur le réseau de votre FAI (voir la section sur la présentation de la mobilité IPv6 ci-dessus pour plus infos) ?

Si d'aventure vous vouliez configurer un Home Agent sans IPSec, il faudrait activer l'IPv6 forward, utiliser la configuration radvd distribuée sur le site web officiel d'umip (en changeant juste le préfixe annoncé) et utiliser un fichier de configuration semblable à celui-ci :

# Sample UMIP configuration file for a MIPv6 Home Agent
NodeConfig HA;
 
# Set DebugLevel to 0 if you do not want debug messages
DebugLevel 10;
 
# Replace eth0 with the interface connected to the home link
Interface "eth0";
 
# Binding information
BindingAclPolicy 2a00:5881:8110:1000::42 allow;
DefaultBindingAclPolicy deny;
 
# Enable IPsec static keying
UseMnHaIPsec disabled;
KeyMngMobCapability disabled;

« BindingAclPolicy » permet d'indiquer quelles sont les IPv6 que les mobile nodes peuvent utiliser.

Démonstrations

Ajouter un utilisateur autorisé à se connecter à notre hotspot

Il faut aller sur daloRADIUS : https ://localhost/admin/daloradius, s'authentifier pour toute l'administration (admin/toor), s'authentifier dans daloRADIUS (administrator/radius, oui, c'est le couple par défaut), cliquer sur « Management », « Users », « New User ». Il suffit ensuite de remplir les champs « Username » et « Password », de choisir « SHA1-Password » dans « Password Type » puis de soumettre le formulaire.

Création d'un nouvel utilisateur dans daloRADIUS.

Création d'un nouvel utilisateur dans daloRADIUS.

L'utilisateur apparaît désormais dans la liste des utilisateurs (« List Users ») :

Listes des utilisateurs dans daloRADIUS.

Listes des utilisateurs dans daloRADIUS.

L'utilisateur peut désormais se connecter à notre hotspot, de manière sécurisée ou via le portail captif en utilisant son couple identifiant/mdp.

Authentification via le portail captif

IPv4

Un utilisateur se connecte à notre hotspot. Il ouvre un navigateur et tape l'URL d'un site web accessible uniquement en v4 (exemple : ent.unistra.fr) :

Un utilisateur connecté à notre hotspot tente d'accéder à un site web accessible uniquement en IPv4.

Un utilisateur connecté à notre hotspot tente d'accéder à un site web accessible uniquement en IPv4.

Il est redirigé vers notre portail captif en utilisant l'IPv4 de ce dernier :

L'utilisateur est redirigé vers la page de connexion en utilisant IPv4.

L'utilisateur est redirigé vers la page de connexion en utilisant IPv4.

Si l'authentification échoue, il recevra un message « login failed ». Si l'authentification réussit, l'utilisateur sera redirigé vers le site web qu'il a demandé.

IPv6

Un utilisateur se connecte à notre hotspot. Il ouvre un navigateur et tape l'URL d'un site web accessible en v6 (exemple : bind6.it - pastebin-like accessible uniquement en v6) :

Un utilisateur connecté à notre hotspot tente d'accéder à un site web accessible en IPv6.

Un utilisateur connecté à notre hotspot tente d'accéder à un site web accessible en IPv6.

Il est redirigé vers notre portail captif en utilisant l'IPv6 de ce dernier :

L'utilisateur est redirigé vers la page de connexion en utilisant IPv6.

L'utilisateur est redirigé vers la page de connexion en utilisant IPv6.

Si l'authentification échoue, il recevra un message « login failed ». Si l'authentification réussit, l'utilisateur sera redirigé vers le site web qu'il a demandé.

Authentification 802.1X

L'utilisateur scanne les réseaux Wi-Fi environnements, tente de se connecter à notre réseau TESTAP-sec puis configure les paramètres de la connexion (TTLS, PAP, certificat de l'AC que nous mettons à disposition, identifiant, mot de passe) comme sur OSIRIS-sec. Sous GNU/Linux, avec GNOME, cela se configure lors de la première connexion ou dans les paramètres avancés de GNOME Network Manager. Sous Windows, il faudra utiliser un logiciel comme secureW2.

Paramètres 802.1X pour notre SSID TESTAP-sec avec GNOME Network Manager.

Paramètres 802.1X pour notre SSID TESTAP-sec avec GNOME Network Manager.

Accounting RADIUS

L'accounting RADIUS peut être consulté facilement avec daloRADIUS (voir « Ajouter un utilisateur »). L'onglet « Accounting » permet d'accéder à toutes les fonctionnalités : recherche par identifiant, recherche par adresse IP, recherche par AP, historique, etc. Exemple :

Accounting RADIUS : l'utilisateur lg est connecté à TESTAP-sec depuis 57 secondes.

Accounting RADIUS : l'utilisateur lg est connecté à TESTAP-sec depuis 57 secondes.

Administration & supervision

Une page web regroupant tous les outils d'administration et de supervision est accessible en https://[::1]/admin/. Il faut s'identifier (admin/toor dans notre cas). Cette page web affiche : des informations concernant notre point d'accès Cisco collectées en utilisant SNMP (uptime, charge CPU, mémoire, nombre de clients Wi-Fi associés, ...), une liste des utilisateurs connectés à notre hotspot et des liens vers les interfaces web des autres outils d'administration (ntop-ng, monit, munin, daloRADIUS).

Illustration de cette page web d'administration (oui, nous ne connaissons pas CSS, et alors ? 🙂 ) :

Notre page web d'administration.

Notre page web d'administration.

Quand l'administrateur clique sur une adresse IP, il atteint bien la page voulue dans ntop-ng. Exemples :

Après un clic sur l'adresse IPv4 d'Hamza, on atterrit sur la page de ntop-ng qui recense toutes les informations au sujet de cette machine et de son trafic réseau v4.

Après un clic sur l'adresse IPv4 d'Hamza, on atterrit sur la page de ntop-ng qui recense toutes les informations au sujet de cette machine et de son trafic réseau v4.

Après un clic sur l'adresse IPv6 d'Hamza, on atterrit sur la page de ntop-ng qui recense toutes les informations au sujet de cette machine et de son trafic réseau v6.

Après un clic sur l'adresse IPv6 d'Hamza, on atterrit sur la page de ntop-ng qui recense toutes les informations au sujet de cette machine et de son trafic réseau v6.

Voici un aperçu de l'interface web de Monit, notre outil de supervision :

Notre interface de supervision avec Monit.

Notre interface de supervision avec Monit.

Voici un aperçu de l'interface web de Munin, notre outil de métrologie :

Notre interface de métrologie avec Munin.

Notre interface de métrologie avec Munin.

Mobilité IPv6

D'abord, l'utilisateur s'authentifie sur notre hotspot Wi-Fi en utilisant notre portail captif ou l'authentification 802.1X. Ensuite, l'utilisateur peut lancer umip en tant que mobile node avec la commande suivante lancée en root : « mip6d -c /usr/local/etc/mip6d.conf »

Sur notre machine qui héberge le portail captif, nous capturons le trafic réseau avec tcpdump et tshark. Nous constatons que l'initialisation s'effectue correctement : le mobile node s'enregistre auprès de son home agent (« Binding Update » puis « Binding Ack »).

Sur TESTAP (2001:470:c8d6:2::/64) :

  1 0.000000000 2001:470:c8d6:2:721a:4ff:feea:d429 -> 2001:660:4701:f055:ffff::1
         110 MIPv6 Binding Update
  2 1.026945000 2001:660:4701:f055:ffff::1 -> 2001:660:4701:f055:ffff::1012
         94 MIPv6 Binding Acknowledgement
  3 1.040577000 2001:470:c8d6:2:721a:4ff:feea:d429 -> 2001:660:4701:f055:ffff::1 
         86 ICMPv6 Mobile Prefix Solicitation
  4 1.061257000 2001:660:4701:f055:ffff::1 -> 2001:660:4701:f055:ffff::1012
         118 ICMPv6 Mobile Prefix Advertisement

Sur TESTAP-sec (2001:470:c8d6:3::/64) :

  1 0.000000000 2001:470:c8d6:3:721a:4ff:feea:d429 -> 2001:660:4701:f055:ffff::1
         110 MIPv6 Binding Update
  2 1.029726000 2001:660:4701:f055:ffff::1 -> 2001:660:4701:f055:ffff::1012
         94 MIPv6 Binding Acknowledgement
  3 1.035904000 2001:470:c8d6:3:721a:4ff:feea:d429 -> 2001:660:4701:f055:ffff::1
         86 ICMPv6 Mobile Prefix Solicitation
  4 1.054201000 2001:660:4701:f055:ffff::1 -> 2001:660:4701:f055:ffff::1012
         118 ICMPv6 Mobile Prefix Advertisement

Voici la sortie d'un traceroute6 depuis un client Wi-Fi avec umip en cours d'exécution :

# traceroute6 www.google.fr
traceroute to www.google.fr (2a00:1450:4007:808::1018), 30 hops max, 
        80 byte packets
 1  2001:660:4701:f055:ffff::1 (2001:660:4701:f055:ffff::1)  
        19.783 ms  19.994 ms  20.392 ms
 2  2001:660:4701:f02f:ff::1 (2001:660:4701:f02f:ff::1)  
        21.054 ms  22.442 ms  23.055 ms
 3  2001:660:2402:9:fe:: (2001:660:2402:9:fe::)  
        23.721 ms  28.171 ms  28.011 ms
 4  vl2501-te1-3-strasbourg-rtr-021.noc.renater.fr (2001:660:7904:14:0:41:0:2200)  
        28.525 ms  28.439 ms  29.781 ms
 5  te1-2-nancy-rtr-021.noc.renater.fr (2001:660:7903:17:2::2)  
        31.251 ms  31.964 ms  34.137 ms
 6  te0-0-0-2-paris1-rtr-001.noc.renater.fr (2001:660:7903:111:1::1)  
        41.122 ms  30.286 ms  30.429 ms
 7  te0-0-0-0-paris2-rtr-001.noc.renater.fr (2001:660:7903:e:1::1)  
        33.383 ms  33.504 ms  36.780 ms
 8  te1-1-paris2-rtr-021.noc.renater.fr (2001:660:7903:3:2::2)  
        33.812 ms  35.004 ms  30.097 ms
 9  2001:660:7904:10:2::2 (2001:660:7904:10:2::2)
        30.471 ms  32.072 ms  30.507 ms
10  2001:4860::1:0:4a3a (2001:4860::1:0:4a3a)  
        32.632 ms  32.903 ms  34.101 ms
11  2001:4860:0:1::66b (2001:4860:0:1::66b)  
        34.401 ms  31.986 ms  31.961 ms
12  2a00:1450:8000:31::2 (2a00:1450:8000:31::2)  
        30.643 ms  28.606 ms  29.181 ms

That's all Folks!

Découvrons la RIPE database

Le RIPE, comme tout RIR, dispose d'une base de données publique qui regroupe toutes (un maximum on va dire) les informations "technico-administratives" (à qui a été alloué telle ressource numérique unique ? qui est le responsable technique de ce réseau, quel AS doit annoncer tel préfixe IP ?, ...) concernant les réseaux informatiques publics en Europe (version large) et au Moyen-Orient.

Habituellement, on accède à cette base de données lorsque l'on fait une requête avec le protocole whois sur une ressource (plage d'IP, numéro d'AS, ...) allouée/assignée par le RIPE. Aujourd'hui, on va plutôt regarder de quoi est constituée cette base de données et comment les LIR et les opérateurs réseaux la mettent à jour (ou non, d'ailleurs).

Si tous les RIR ont une base de données, pourquoi je me focalise sur celle du RIPE ? Simplement car le RIPE est bien de chez nous et comme le format de la base (RPSL ou non, objets/attributs différents, ...) change entre RIR, autant étudier ce qui nous concerne. De plus, la RIPE DB est la mieux documentée, à mon humble avis.

Attention : ce billet n'est ni de la vulgarisation ni une source d'information fiable. Il s'agit juste d'un retour d'expérience et de quelques notes d'un n00b qui s'est penché sur le sujet.

Table des matières

De la documentation générale

Documentation spécifique

Quelques paramètres whois bien utiles

Ce que je retiens principalement des lectures précédentes, c'est des paramètres de la commande whois, qui sont spécifiques à certaines bases comme celle du RIPE mais qui n'en sont pas moins très utiles :

  • whois -t <type_objet> : afficher les attributs d'un type d'objet
  • whois -B : afficher toutes les infos (mail ...) de l'objet demandé (sans ça : « source: RIPE # Filtered »)
  • whois -L : remonter toute l'arborescence d'une allocation. Exemple pour un inetnum : organisation - LIR - RIR - IANA
  • whois -l : pareil que précédemment mais remonte uniquement d'un étage/d'une étape/d'un cran
  • whois -r -i org <id_organisation> : afficher toutes les ressources/objets assignés/alloués à une organisation (objet de type « organisation »)
  • whois -i origin <ASN> : afficher tous les préfixes déclarés comme étant annoncés par l'AS donné. En vrai, ça remonte les objets route(6) et la fioriture qui va avec. On peut également remonter tous les objets dont le tech ou l'admin sont un objet person passé en argument : whois -i person . On ne peut pas itérer sur tous les attributs. Les attributs possibles sont indiqués dans « RIPE Database Queries Reference Card » (https://www.ripe.net/manage-ips-and-asns/db/support/documentation )

RIPE TEST Database

Le RIPE met à disposition une base de données de test en libre-service pour que chacun puisse découvrir/s'entraîner à l'arrache. Je pense que c'est un excellent moyen de voir comment ça se passe de l'autre côté du whois. C'est vraiment intéressant et formateur.

Ci-dessous, je ne vais pas être exhaustif, juste donner des pistes et énoncer quelques "pièges" que j'ai rencontrés pour vous éviter de tomber dedans.

Dans mes exemples, je serai Jean-Kevin Boulay de l'association TESTASSO.

person/mntner

La première étape est de créer un objet de type « person ». Voyons les différents attributs disponibles :

whois -t person
person:         [mandatory]  [single]     [lookup key]
address:        [mandatory]  [multiple]   [ ]
phone:          [mandatory]  [multiple]   [ ]
fax-no:         [optional]   [multiple]   [ ]
e-mail:         [optional]   [multiple]   [lookup key]
org:            [optional]   [multiple]   [inverse key]
nic-hdl:        [mandatory]  [single]     [primary/lookup key]
remarks:        [optional]   [multiple]   [ ]
notify:         [optional]   [multiple]   [inverse key]
abuse-mailbox:  [optional]   [multiple]   [inverse key]
mnt-by:         [mandatory]  [multiple]   [inverse key]
changed:        [mandatory]  [multiple]   [ ]
source:         [mandatory]  [single]     [ ]

Hum ... mnt-by doit pointer un objet de type « mntner » (maintainer) ... Quels sont ses attributs ?

whois -t mntner
mntner:         [mandatory]  [single]     [primary/lookup key]
descr:          [mandatory]  [multiple]   [ ]
org:            [optional]   [multiple]   [inverse key]
admin-c:        [mandatory]  [multiple]   [inverse key]
tech-c:         [optional]   [multiple]   [inverse key]
upd-to:         [mandatory]  [multiple]   [inverse key]
mnt-nfy:        [optional]   [multiple]   [inverse key]
auth:           [mandatory]  [multiple]   [inverse key]
remarks:        [optional]   [multiple]   [ ]
notify:         [optional]   [multiple]   [inverse key]
abuse-mailbox:  [optional]   [multiple]   [inverse key]
mnt-by:         [mandatory]  [multiple]   [inverse key]
referral-by:    [mandatory]  [single]     [ ]
changed:        [mandatory]  [multiple]   [ ]
source:         [mandatory]  [single]     [ ]

Il y a une référence circulaire : un objet person dépend d'un objet de type maintainer mais un objet maintainer dépend aussi d'un objet de type person (attribut « admin-c »).

La solution ? Utiliser le formulaire pour les nouveaux arrivants dans la RIPE Database.

Mode d'emploi rapide :

  • Source : bien choisir la TEST Database
  • Person : prénom nom. Exemple : J-K Boulay
  • NIC hdl : ne peut pas être vide contrairement à ce qui est indiqué. Il s'agit de la clé primaire de l'objet. Elle doit être unique dans la base donc la base l'attribue elle-même. Vous pouvez mettre :
    • Soit une suite de caractères qui respecte le formalisme (« From 2 to 4 characters optionally followed by up to 6 digits optionally followed by a source specification. The first digit must not be "0". Source specification starts with "-" followed by source name up to 9-character length. »). Exemple : JKB1-TEST. Attention : un nic-hdl déjà pris générera une erreur « The nic-hdl "JKB1-TEST" is not available ».
    • Soit la valeur « AUTO-1 » pour laisser complètement le choix à la base ou « AUTO-1<abréviation> ». Abréviation est composée de 1 à 4 lettres. Exemple : AUTO-1JKB pour obtenir le nic-hdl : « JKB1-TEST ». Il est inutile d'essayer de mettre AUTO-42JKB pour tenter d'obtenir un nic-hdl « JKB42-TEST » : ça ne marche pas. Je n'ai pas trouvé comment forcer le nombre et c'est à mon avis bien normal : ID, on prend le suivant, même si un objet avec un ID inférieur a été supprimé depuis, tout ça.
  • Address : FR suffit, par exemple
  • Phone : +33123456789 suffit, par exemple
  • Email : ripedb@example.com suffit, par exemple
  • Pour le mntner, le nom est une clé et on reprend souvent le nom de l'organisation à laquelle la personne qui s'inscrit est rattachée. Exemple : Maintainer name : TESTASSO-MNT

Normalement, vous obtenez le message suivant :

We have created two RIPE Database objects for you, please make a note of the primary keys (shown in bold letters) for these two objects for future reference:
 
person nic-hdl: JKB1-TEST
 
person:         J-K Boulay
address:        FR
phone:          +33123456789
e-mail:         ripedb@example.com
nic-hdl:        JKB1-TEST
mnt-by:         TESTASSO-MNT
changed:        ripedb@example.com 20140418
source:         TEST
 
maintainer name: TESTASSO-MNT
 
mntner:         TESTASSO-MNT
descr:          Startup maintainer
admin-c:        JKB1-TEST
upd-to:         ripedb@example.com
auth:           MD5-PW blabla
mnt-by:         TESTASSO-MNT
referral-by:    TESTASSO-MNT
changed:        ripedb@example.com
source:         TEST

Petite note : un objet de type maintainer est un verrou qui empêche la modification et la suppression inopinée de vos enregistrements présents dans la base. Il recense également les moyens qui permettent d'authentifier le requérant d'une modification/suppression. Actuellement, la base supporte une auth par mot de passe (stocké sous forme de hash MD5 dans la base) ou par signature PGP des objets (uniquement par mail, pas par l'interface web. Oui, on peut créer/modifier des objets par mail.). Pour l'instant, nous n'avons qu'une auth par mot de passe.

Vous pouvez effectuer une recherche pour vérifier que vos objets ont bien été créés : https://apps.db.ripe.net/search/query.html.

Si vous souhaitez modifier vos objets, c'est par là : https://apps.db.ripe.net/webupdates/search.html. Cependant, lors de la soumission de vos modifications, vous allez recevoir l'erreur : « No authorisation has been supplied ». En effet, vous n'avez pas prouvé votre identité selon l'un des mécanismes prévus dans l'objet maintainer qui verrouille l'objet que vous souhaitez modifier. Pour vous authentifier, il suffit de saisir votre mot de passe de maintainer dans la zone « Session Passwords » dans le menu de droite.

À partir de maintenant, vous pouvez créer vos objets à l'adresse suivante : https://apps.db.ripe.net/webupdates/select-type.html. Deux méthodes de saisies sont disponibles : format brut (que du texte) ou formulaire avec uniquement les attributs obligatoires.

organisation

Un objet de type « organisation » permet de lier toutes les ressources associées/allouées à une organisation donnée. Très pratique mais pas obligatoire. Les autres objets peuvent pointer dessus avec un attribut « org ». L'attribut organisation fonctionne comme le NIC-hdl d'une person donc « AUTO-1<abréviation> ».

Exemple :

organisation: 	ORG-TSTA1-TEST
org-name: 	Association TESTASSO
org-type: 	OTHER
address: 	FR
e-mail: 	ripedb@example.com
mnt-ref: 	TESTASSO-MNT
mnt-by: 	TESTASSO-MNT
changed: 	ripedb@example.com 20140418
source: 	TEST

inetnum/inet6num

Passons au plus intéressant : une plage d'adresses IPv4 : un inetnum (ou v6 avec inet6num, ça fonctionne pareil).

En situation réelle, vous n'aurez pas besoin de créer cet objet : c'est le boulot de votre LIR qui vous mettra en mnt-* si vous le souhaitez pour que vous ayez des droits sur vos allocations. De plus, en situation réelle, le processus d'allocation de ressources ne se passe pas aussi facilement en mode "ho, cette plage est disponible, je la prends". Néanmoins, ici, je choisis 192.0.2.0/24 qui est une plage réservée à la documentation.

Ici, il faudra l'accord de la hiérarchie, c'est à dire de l'organisation (RIR ou LIR) a qui a été attribuée la plage d'adresses IP qui englobe celle que cette organisation vous a attribuée. Pour trouver quel est le maintainer responsable, il suffit de faire une recherche.

Via l'interface web :

  • Search term : 192.0.2.0 - 192.0.2.255
  • Sources : TEST Database
  • Hierarchy Flags : l (l comme lapin)

Ou via whois : whois -h whois-test.ripe.net -l 192.0.2.0 - 192.0.2.255

Voilà un extrait de la réponse :

inetnum:         0.0.0.0 - 255.255.255.255
netname:         IANA-BLK
descr:           The whole IPv4 address space
country:         EU # Country is really world wide
org:             ORG-TT1-TEST
admin-c:         AA1-TEST
tech-c:          AA2-TEST
status:          ALLOCATED UNSPECIFIED
remarks:         The country is really worldwide.
mnt-by:          TEST-ROOT-MNT
mnt-lower:       TEST-DBM-MNT
mnt-routes:      TEST-DBM-MNT
remarks:         This is an automatically created object.
source:          TEST # Filtered

On regarde l'attribut « mnt-lower: ». Le maintainer est « TEST-DBM-MNT ». Dans cette base de test, le mot de passe est donné dans un attribut « remark » de cet objet mntner. Je vous laisse le retrouver et l'ajouter à vos « Session Passwords » (menu de droite, tout ça ...).

Maintenant, vous pouvez enregistrer un inetnum. Exemple :

inetnum: 	192.0.2.0 - 192.0.2.255
netname: 	TESTASSO-MAIN
descr:		IPv4 TESTASSO allocation
country: 	FR
admin-c: 	JKB1-TEST
tech-c: 	JKB1-TEST
status: 	ASSIGNED PA
mnt-by: 	TESTASSO-MNT
changed: 	ripedb@example.com 20140418
source: 	TEST

Remarquez que le statut est fantaisiste et ne correspond pas à la réalité : l'IANA n'alloue pas directement des ressources. Le RIPE le faisait (PI + allocation directe). Mais c'est fini (famine IPv4, tout ça et il me semble que c'est pareil en IPv6). Donc, au minimum, il faudrait un RIR et un LIR entre TESTASSO et l'IANA avec autant d'objets. Si ça vous tente de faire une maquette plus réaliste ... Ce n'est pas difficile, juste répétitif.

ÉDIT du 26/04/2014 à 22h00 : Remarquez que le mnt-by est lui aussi fantaisiste : un LIR ne vous mettra pas en mnt-by car il s'agit de ses allocations à lui en tant que LIR tout comme le RIPE n'a pas défini le LIR comme mnt-by sur le(s) inet(6)num qu'il a attribué au LIR. C'est le rôle de ces organismes (RIR et LIR) de délimiter les allocations. Sur les inet(6)num, votre LIR vous mettra en mnt-lower pour gérer des découpages dans vos allocations (exemple : fourre-tout v6), en mnt-domain pour gérer les objets domain associés et en mnt-routes pour créer/modifier les objets de type route, ... Fin de l'édit

ASN

Pour un numéro de système autonome (ASN), le type d'objet est « aut-num ». Sa création ne pose pas de problèmes si vous êtes arrivé jusque-là.

Délégation zone reverse

Pour la délégation d'une zone reverse v4/v6, c'est un objet de type « domain » qu'il vous faut. Mais dans la base de test, sa création échouera car le niveau supérieur (0/0 en v4) n'a pas configuré les serveurs de noms qui font autorité sur cette zone reverse (et pour cause ...).

Politiques de routage inter-AS

Pour ce qui est des politiques de routage, on a les objets de type route/route6 qui permettent de dire : « tel préfixe IPv4 ou IPv6 est annoncé par tel AS » ou des choses plus marrantes comme « quelle est l'adresse IP de la machine que ce réseau met à disposition du public pour servir d'amer ? » (attribut « pingable » d'un route/route6.

Pour les politiques de routage, on a aussi d'autres objets (filter-set, par exemple) et des attributs dans l'objet « aut-num » : import, export et les types liés (as-set, route-set). Tout cela constitue l'Internet Routing Registry (IRR). Exemple :

aut-num: AS197422
as-name: TETANEUTRAL-NET-AS
[...]
from AS31576 accept ANY
from AS6777 accept ANY
from AS6939 accept ANY
[...]
to AS31576 announce AS197422
to AS6939 announce AS197422
to AS6777 announce AS197422
[...]

Ici, ce FAI toulousain annonce que ses pairs sont GIXE (transitaire), l'AMS-IX (point d'échange néerlandais) et Hurricane Electric (gros opérateur). Tetaneutral.net récupère toutes les routes auprès d'eux sans prioriser un pair vis-à-vis d'un autre. Enfin, Tetaneutral annonce ses préfixes à ses pairs.

ÉDIT du 05/05/2016 à 13h20 : Un jour, vous fournirez peut-être du transit IP à d'autres réseaux/organisations. Il faudra alors mettre à jour l'IRR pour dire : « à mes upstreams/pairs, j'annonce non seulement mon AS mais aussi celui de mes downstreams ». Si l'on procède comme on vient de voir, il faudrait une ligne « export: to AS64500 announce AS6500 » (AS64500 = upstream et AS6500 = downstream) pour chaque couple downstream+upstreams ainsi qu'une ligne « import: from AS6500 accept AS6500 » pour chaque downstream ainsi qu'une ligne « export: to AS65000 announce ANY » pour chaque downstream. Ces deux dernières lignes sont inévitables. En revanche, on peut factoriser les premières.

Pour se faire, on utilise un objet de type as-set qui regroupera les objets de type aut-num de vos downstreams + votre aut-num. Ensuite, pour chaque upstream, on aura une ligne : « export: to AS64500 announce <votre_as-set> ». Lors de l'ajout d'un nouveau downstream, il suffit de rajouter son aut-num à l'as-set et les lignes « import: from <son_aut-num> accept <son_aut-num> » et « export: to <son_aut-num> announce ANY » à votre aut-num et c'est fini. Évidemment, le downstream devra mettre à jour son IRR pour indiquer que votre organisation est l'un de ses upstreams/pairs.

Exemple :

aut-num:        AS60630
as-name:        ARN
[...]
import:         from AS174 accept ANY
export:         to AS174 announce AS-ARN
import:         from AS8928 accept ANY
export:         to AS8928 announce AS-ARN
import:         from AS204092 accept AS204092
export:         to AS204092 announce ANY
[...]

as-set:         AS-ARN
descr:          ARN and downstreams
members:        AS60630
members:        AS204092
[...]

aut-num:        AS204092
as-name:        GRIFON
[...]
import:         from AS60630 accept ANY
export:         to AS60630 announce AS204092
[...]

Ici, ce FAI alsacien annonce lui (AS60630) + Grifon (AS204092) à ses upstreams (Cogent - AS174 et Interoute - AS8928), récupère les routes de Grifon et lui annonce la full view.

Il y a deux intérêts à faire ça. Premièrement, avoir une base de données à jour ce qui, en plus d'être du bon sens civique, permet de ne pas se faire filtrer ses préfixes par des opérateurs qui construisent leurs filtres automatiquement à partir de la RIPE DB. Deuxièmement, certains transitaires (comme Interoute) vérifient ces informations avant de vous autoriser à relayer les annonces de vos downstreams lors de votre appel à leur NOC. Tous les transitaires ne vérifient pas : Cogent, par exemple, croit ses clients sur parole. Fin de l'édit

ÉDIT du 17/10/2017 à 9h25 : Comme la politique de routage IPv4 peut être différente de la politique de routage IPv6 (pairs différents, priorité différente, etc.), le RIPE recommande, dans ses formations, de publier les deux politiques de routage dans l'IRR. Cela se fait avec les attributs « mp-import » et « mp-export ». Exemple :

aut-num:        AS60630
[…]
import:         from AS174 accept ANY
export:         to AS174 announce AS-ARN
[…]
mp-import:      afi ipv6.unicast from AS174 accept ANY
mp-export:      afi ipv6.unicast to AS174 announce AS-ARN

Dans cet exemple, l'opérateur déclare être relié à Cogent (AS174) en IPv4 et en IPv6, qu'il lui annonce son réseau et celui de ses clients (AS-ARN) et que Cogent lui envoie la full view (ANY), c'est-à-dire toutes les routes qu'il connait.

Note : on peut remplacer « import » et « export » par « mp-import: afi ipv4.unicast » et par « mp-export: afi ipv4.unicast ». Les deux syntaxes sont équivalentes, la deuxième est plus moderne et plus lisible. Fin de l'édit du 17/10/2017 à 9h25

ÉDIT du 17/10/2017 à 9h40 : Il est possible de se connecter à des collecteurs (ring nlnog, route views, ripe stat, etc.) c'est-à-dire, à des projets qui sont des vigies d'observation d'Internet depuis lesquelles on voit battre le cœur d'Internet, on voit la topologie générale du réseau varier, on peut diagnostiquer et comprendre les pannes techniques, etc. Généralement, on envoie toutes nos routes à ces projets-là, mais lui ne nous envoie rien. Comment explicite-t-on cela dans la politique de routage ? Comme ceci :

aut-num:        AS60630
[…]
import:         from AS199036 accept NOT ANY
export:         to AS199036 announce ANY
[…]
mp-import:      afi ipv6.unicast from AS199036 accept NOT ANY
mp-export:      afi ipv6.unicast to AS199036 announce ANY

Dans cet exemple, l'opérateur déclare être connecté au looking glass du nlnog (AS199036), en IPv4 et en IPv6. Il déclare lui envoyer toutes les routes qu'il connaît (« export: […] ANY ») et que le nlnog ne lui envoie rien (« import: […] NOT ANY »). Oui, cette syntaxe « NOT ANY » n'est pas la plus intuitive qui soit… Fin de l'édit du 17/10/2017 à 9h40

Cogestion

Et pour faire de la cession de droits ou, à minima, de la cogestion sur des objets ? Il suffit de leur associer de nouveaux objets de type mntner dans leurs attributs « mnt-by » ou « mnt-* » pour des droits plus fins. Exemples : mnt-routes pointe sur un objet maintainer qui aura le droit d'ajouter des objets de type route/route6 en rapport avec l'objet courant ; mnt-domain = même principe mais pour les reverse v4/v6. L'authentification fonctionne selon un OU logique : toute personne qui aura un des mécanismes d'authentification d'un des maintainers pourra modifier l'objet.

ÉDIT du 26/04/2014 à 23h55 :

Modifier la base par mail avec signature PGP

Documentations : PGP Authentication in the RIPE Database — RIPE Network Coordination Centre ainsi que le tutoriel « Maintenance whois de Lulu » que j'ai linké en début de billet.

Notez qu'il n'est pas nécessaire d'utiliser PGP : vous pouvez envoyer vos modifications par mail en y ajoutant un attribut « password » contenant le mot de passe de votre objet maintainer en clair. Mais c'est mal, très mal. La réciproque n'est pas vraie : l'interface web ne supporte pas les signatures PGP.

Les robots à contacter (source) :

  • auto-dbm(at)ripe(dot)net pour la vraie DB
  • test-dbm(at)ripe(dot)net pour la DB de test (un greylisting est en place sur cette adresse)

La première étape pour soumettre vos modifications signées avec PGP par mail est de créer un objet de type « key-cert ». Il semble que le formulaire « individual fields » est cassé (il retourne toujours l'erreur « certif value is required ») donc utilisez le formulaire « single text area ».

KEY-CERT : PGPKEY-<ID_de_votre_cle>

CERTIF : votre clé publique au format ASCII obtenu avec gpg --armor --export <ID_de_votre_cle>. Il faut autant de lignes « certif » que de lignes dans la sortie gpg. Il faut conserver les lignes vides et les marqueurs de début et de fin de la clé.

Les autres attributs sont soit générés automatiquement (« method », « owner », « fingerpr ») soit facultatifs. On supprime les premiers. On complète ou non les seconds.

Exemple :

KEY-CERT:        PGPKEY-698DB1DD
CERTIF:          -----BEGIN PGP PUBLIC KEY BLOCK-----
CERTIF:          Version: GnuPG v1.4.12 (GNU/Linux)
CERTIF:          
CERTIF:          mQINBEvXHzcBEACwopvTtQLlh+vfqwolOHaplhV8x1zFjd1lT1E0CLwpsC5qpmQb
CERTIF:          o5bNlSoDDqlMG6IHP98TUWM24mZU626MuWHte/vMOE5X1Q7tk0TRREeYjXxBftaa
CERTIF:          9pGhynrjdk4FSDSZjZ5N0CgCDeX61j1TgCH0LT1EPB0/hJ+Tv2jDHc0vfCq6tnCg
CERTIF:          a3GoKrAlPOWff/nNFpXQwpLbeFDizurGXNDpGa5yBPfn0k8bKzhPa+h9geYIx+4e
CERTIF:          NbtG0J/g+GJuLhmlZyMVQ5+VYgeggbwLw8CvvcsH82v5PG5XIyceMqEXOvji5BOL
CERTIF:          jzuYFYF/XinPbWgz5q2+5hV3HsJz8jMHlkPZnj1hbHa9mT1A+xSFUQ95gbvRXgF2
CERTIF:          iOmewYUVPKoOfIhGis1cq1f4SlhrgO0vS32bBb/6hPp8EVeblGzMUhAyfc+RNbof
CERTIF:          IvUUlbp6CDy2qrCV4C0UHlyFMC4/11p2mZ63J0o88qL5sIAzwi7wuYfep+xjaYFv
CERTIF:          W8for8VZPtu9Fui6LyRvgv9OvUy5R+1wW2F1IChM9G4AbxdaYrtjtXuCuTVkfgdC
CERTIF:          CYabxcEQeh4uhjNqhP3hxAag7CI3KUud26w4Q/9kQ2BWxXMheZpDGkapfzJypbbe
CERTIF:          jLC7OHhqV6r+GoD8VmNMlKNQ/Jzvrf23Z2QLCuzSuIGwVIyKJG5y5ifecwARAQAB
CERTIF:          tCdKb25hdGhhbiBNaWNoYWxvbiA8am9obmRlc2NzQGdtYWlsLmmvbT6JAjcEEwEI
CERTIF:          ACEFAkvXHzcCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQqS0qaGeNoc1Z
CERTIF:          EA//YQ13Sl5rxBQuXZcxWvsBlON4ZjqfZB6HnOdeeIdEnR7hEVgICDD/3AEZKXs5
CERTIF:          38uwTO1h++2T47KsbBaKFK+bCJfzivSVHj6SWWlvhJZo5WUcNZ6NxX5iBfM+IZ9r
CERTIF:          /2z8LS+t/iv6jy0tNeziVCxIDf3KJ9FIUl0QOrKRMIaj9q66rkVk9qZuNQRfx+3B
CERTIF:          9g1eKyhLj/CFjzz1yANIK7Ga74bgYhXTxHEGK+JKOf+tq4RymYpdRu87YauwL3ZY
CERTIF:          KbQbG4JzNWsqnJint7pueiEOrCcTHecUgkpQpPXrk/J2Xt07XudvteUcArOoRNP4
CERTIF:          qdb9ku1kkbtlsc74DQwE9sysbZbYoIT1YD1y0x2S9IqYsW/NpsOCek+8viRUSTHK
CERTIF:          mHnYcR2RDB/6EkkU2Rwjjt3c1idnrl2u40jTtU6ZZ/3Cbqjs/HEvf5EFacxx1JVA
CERTIF:          RQI0pTja56qn3sk85avJfM1I4P0swG9Ga+wnb/K1nyuIBQJZO3W+SfpJFOgzmXBu
CERTIF:          M1IiXIjdCbzhHTcrbc9xnvaqXCf5L13ZQy+CpEyvVpAWOTd4s6Ia9PldlPaRWwIG
CERTIF:          faZKVjCKLaDKle75vrP48pinCFu5JJ+6BrTVR0LxJRsd/XF8fbcOwk4TH1z2/bOt
CERTIF:          N1N1dqHMx3RD/NZW+5nMae/MugVWZZUXJEremM3WpopvdciIRgQQEQgABgUCTL75
CERTIF:          gQAKCRD41bgH8Cma2QcnAKCWEllqpyiJn0Usj9iJa0xxsUKv2wCdFr7Buq1Ib+iu
CERTIF:          ufv5awslcQYCaNW0MEpvbmF0aGFuIE1pY2hhbG9uIChQZXJzbykgPGpvbmF0aGFu
CERTIF:          QG1pY2hhbG9uLmV1PokCNwQTAQgAIQUCUcNc4wIbAwULCQgHAwUVCgkICwUWAgMB
CERTIF:          AAIeAQIXgAAKCRCpLSpoZ42hzYaHEACqIwSOGZBPK6oT1S5PEweHC4MxgeaQQl/0
CERTIF:          uF3u4G/hEo20zVzrv4PSBuRwdBfCjnZE3NakRfJGx5Y4T2dkYrZXNrB2DG+kqyqw
CERTIF:          KMa06gC4ZGK+DyVusU5+qCLDeTXvtY6W5z7GQo1SV6iHE0iFKqoqjuaXXG8Wuh3t
CERTIF:          C12VoQOxWUi6VE6Z8Ys+E76YpLztOhy4nFeEBL8w1NIq8KDKlydjbLnBkhUes0IP
CERTIF:          tIdLynxqm+tkRUA9WaPSIY/KQoFAbTLP3WAjnNM7wCqShAwgTBhH8v66pYKt/qpk
CERTIF:          jvVCEx3C48TTDgmHXlhrZTYR5jA4wpnrJwP+FhyBdA+fKPmJHzmsNwT321fH1Z40
CERTIF:          x+xfTZSVjfkgoW9cl95Sy3JGeD1omHjmR5cURz7Iwyg02z+k86Ve44/TKMXgXa2F
CERTIF:          H7D8Zjv9AYP4xKzDF+QnPrl55KzotMX9r501AaYB913jJ14xRMFjqWphB2hijrhQ
CERTIF:          hebW5a/HI+aKLyWkdNOA7N0AYhJ5MAjaKjsG7LfOo69X7HUtWtLeOATjJFT8CgFB
CERTIF:          FfVJBrK5GQ1v6l7dgUKMgUsKCAhKE7TbU6sqWTT+ohFTHcHhlziwWA+3jEfhhR2y
CERTIF:          JFUBhZ/nFo2v69Okav29LKTooHKaFpHzXLbvUb4P/G/wX15LffHHYb9JvU+PXP1r
CERTIF:          ddSIxmwKRw==
CERTIF:          =zj2g
CERTIF:          -----END PGP PUBLIC KEY BLOCK-----
org:             ORG-TSTA1-TEST
admin-c:         JKB1-TEST
tech-c:          JKB1-TEST
MNT-BY:          TESTASSO-MNT
CHANGED:         ripedb@example.com 20140426
SOURCE:          TEST

Ce qui nous donne l'objet suivant une fois le formulaire validé :

whois -h whois-test.ripe.net -B -r PGPKEY-698DB1DD
% Information related to 'PGPKEY-698DB1DD'
 
key-cert:       PGPKEY-698DB1DD
method:         PGP
owner:          Jean-Kevin Boulay <jkb@example.net>
fingerpr:       E99E BD56 AA68 C0CD FEC2  EB25 A92D 2A68 698D B1DD
certif:         -----BEGIN PGP PUBLIC KEY BLOCK-----
certif:         Version: GnuPG v1.4.12 (GNU/Linux)
certif:          
certif:         mQINBEvXHzcBEACwopvTtQLlh+vfqwolOHaplhV8x1zFjd1lT1E0CLwpsC5qpmQb
certif:         o5bNlSoDDqlMG6IHP98TUWM24mZU626MuWHte/vMOE5X1Q7tk0TRREeYjXxBftaa
certif:         9pGhynrjdk4FSDSZjZ5N0CgCDeX61j1TgCH0LT1EPB0/hJ+Tv2jDHc0vfCq6tnCg
certif:         a3GoKrAlPOWff/nNFpXQwpLbeFDizurGXNDpGa5yBPfn0k8bKzhPa+h9geYIx+4e
certif:         NbtG0J/g+GJuLhmlZyMVQ5+VYgeggbwLw8CvvcsH82v5PG5XIyceMqEXOvji5BOL
certif:         jzuYFYF/XinPbWgz5q2+5hV3HsJz8jMHlkPZnj1hbHa9mT1A+xSFUQ95gbvRXgF2
certif:         iOmewYUVPKoOfIhGis1cq1f4SlhrgO0vS32bBb/6hPp8EVeblGzMUhAyfc+RNbof
certif:         IvUUlbp6CDy2qrCV4C0UHlyFMC4/11p2mZ63J0o88qL5sIAzwi7wuYfep+xjaYFv
certif:         W8for8VZPtu9Fui6LyRvgv9OvUy5R+1wW2F1IChM9G4AbxdaYrtjtXuCuTVkfgdC
certif:         CYabxcEQeh4uhjNqhP3hxAag7CI3KUud26w4Q/9kQ2BWxXMheZpDGkapfzJypbbe
certif:         jLC7OHhqV6r+GoD8VmNMlKNQ/Jzvrf23Z2QLCuzSuIGwVIyKJG5y5ifecwARAQAB
certif:         tCdKb25hdGhhbiBNaWNoYWxvbiA8am9obmRlc2NzQGdtYWlsLmmvbT6JAjcEEwEI
certif:         ACEFAkvXHzcCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQqS0qaGeNoc1Z
certif:         EA//YQ13Sl5rxBQuXZcxWvsBlON4ZjqfZB6HnOdeeIdEnR7hEVgICDD/3AEZKXs5
certif:         38uwTO1h++2T47KsbBaKFK+bCJfzivSVHj6SWWlvhJZo5WUcNZ6NxX5iBfM+IZ9r
certif:         /2z8LS+t/iv6jy0tNeziVCxIDf3KJ9FIUl0QOrKRMIaj9q66rkVk9qZuNQRfx+3B
certif:         9g1eKyhLj/CFjzz1yANIK7Ga74bgYhXTxHEGK+JKOf+tq4RymYpdRu87YauwL3ZY
certif:         KbQbG4JzNWsqnJint7pueiEOrCcTHecUgkpQpPXrk/J2Xt07XudvteUcArOoRNP4
certif:         qdb9ku1kkbtlsc74DQwE9sysbZbYoIT1YD1y0x2S9IqYsW/NpsOCek+8viRUSTHK
certif:         mHnYcR2RDB/6EkkU2Rwjjt3c1idnrl2u40jTtU6ZZ/3Cbqjs/HEvf5EFacxx1JVA
certif:         RQI0pTja56qn3sk85avJfM1I4P0swG9Ga+wnb/K1nyuIBQJZO3W+SfpJFOgzmXBu
certif:         M1IiXIjdCbzhHTcrbc9xnvaqXCf5L13ZQy+CpEyvVpAWOTd4s6Ia9PldlPaRWwIG
certif:         faZKVjCKLaDKle75vrP48pinCFu5JJ+6BrTVR0LxJRsd/XF8fbcOwk4TH1z2/bOt
certif:         N1N1dqHMx3RD/NZW+5nMae/MugVWZZUXJEremM3WpopvdciIRgQQEQgABgUCTL75
certif:         gQAKCRD41bgH8Cma2QcnAKCWEllqpyiJn0Usj9iJa0xxsUKv2wCdFr7Buq1Ib+iu
certif:         ufv5awslcQYCaNW0MEpvbmF0aGFuIE1pY2hhbG9uIChQZXJzbykgPGpvbmF0aGFu
certif:         QG1pY2hhbG9uLmV1PokCNwQTAQgAIQUCUcNc4wIbAwULCQgHAwUVCgkICwUWAgMB
certif:         AAIeAQIXgAAKCRCpLSpoZ42hzYaHEACqIwSOGZBPK6oT1S5PEweHC4MxgeaQQl/0
certif:         uF3u4G/hEo20zVzrv4PSBuRwdBfCjnZE3NakRfJGx5Y4T2dkYrZXNrB2DG+kqyqw
certif:         KMa06gC4ZGK+DyVusU5+qCLDeTXvtY6W5z7GQo1SV6iHE0iFKqoqjuaXXG8Wuh3t
certif:         C12VoQOxWUi6VE6Z8Ys+E76YpLztOhy4nFeEBL8w1NIq8KDKlydjbLnBkhUes0IP
certif:         tIdLynxqm+tkRUA9WaPSIY/KQoFAbTLP3WAjnNM7wCqShAwgTBhH8v66pYKt/qpk
certif:         jvVCEx3C48TTDgmHXlhrZTYR5jA4wpnrJwP+FhyBdA+fKPmJHzmsNwT321fH1Z40
certif:         x+xfTZSVjfkgoW9cl95Sy3JGeD1omHjmR5cURz7Iwyg02z+k86Ve44/TKMXgXa2F
certif:         H7D8Zjv9AYP4xKzDF+QnPrl55KzotMX9r501AaYB913jJ14xRMFjqWphB2hijrhQ
certif:         hebW5a/HI+aKLyWkdNOA7N0AYhJ5MAjaKjsG7LfOo69X7HUtWtLeOATjJFT8CgFB
certif:         FfVJBrK5GQ1v6l7dgUKMgUsKCAhKE7TbU6sqWTT+ohFTHcHhlziwWA+3jEfhhR2y
certif:         JFUBhZ/nFo2v69Okav29LKTooHKaFpHzXLbvUb4P/G/wX15LffHHYb9JvU+PXP1r
certif:         ddSIxmwKRw==
certif:         =zj2g
certif:         -----END PGP PUBLIC KEY BLOCK-----
org:            ORG-TSTA1-TEST
admin-c:        JKB1-TEST
tech-c:         JKB1-TEST
mnt-by:         TESTASSO-MNT
changed:        ripedb@example.com 20140426
source:         TEST

La deuxième étape est de lier votre nouvel objet key-cert à votre objet de type mntner. Cela se fait avec un attribut « auth » dans l'objet mntner. Exemple : « auth: PGPKEY-698DB1DD ».

Vous pouvez désormais soumettre des objets signés par mail au robot du RIPE :

  1. Il faut obtenir une copie complète (-B) de l'objet que vous souhaitez modifier. Exemple :
    whois -h whois-test.ripe.net -B -r JKB1-TEST > jkb1
  2. On modifie avec notre éditeur de texte favori. Dans mon cas, j'ajoute un attribut « remarks » et un attribut « changed ».
  3. On signe en gardant le texte en clair (on ajoute la signature à la fin, voir : Gnu Privacy Guard (GnuPG) Mini Howto (Français)) :
    gpg -u <id_de_votre_cle> --armor --clearsign jkb1

    On obtient un fichier jkb1.asc qui contient quelque chose dans ce genre :

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
     
    Avertissement : des options RIPE ont été utilisées avec un serveur classique.
    % This is the RIPE Database query service.
    % The objects are in RPSL format.
    %
    % The RIPE Database is subject to Terms and Conditions.
    % See http://www.ripe.net/db/support/db-terms-conditions.pdf
     
    % Information related to 'JKB1-TEST'
     
    person:         J-K Boulay
    address:        FR
    phone:          +33123456789
    e-mail:         ripedb@example.com
    nic-hdl:        JKB1-TEST
    mnt-by:         TESTASSO-MNT
    remarks:        Ceci est un test
    changed:        ripedb@example.com 20140418
    changed:        jkb@example.net 20140426
    source:         TEST
     
    % This query was served by the RIPE Database Query Service version 1.72 (DBC-WHOIS4)
     
     
    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.12 (GNU/Linux)
     
    iQIcBAEBCAAGBQJTXBLSAAoJEKktKmhnjaHNb4MP/0aF2GABfKvilKSrGiCn7RUH
    yQ9k4D8SN7nJi6jJK9skvcELTp7eTEuCQmvNTuO2RFg5362bm1UNOcjKZep5++8h
    i79wcjolfyxjkFrlOJaLVDmF1fVR+aqo5+ZoWNfbnba5pP8HyVY+n7027OvDO2yL
    mwcGSKD7h/Za2dYl0wnWF9dlmcIGcYk+m5ruKb3bqjpTF79n4S3/ua+WzACkX9UH
    Mg2B60xJoVIAO0A/o7y6UvxCO7+8mI/E3Y2B4AVYOvRaJ1QnJpU/Ty1csx9pDbJg
    dicChmOaZ+EbXYHLi0axc8ixucpo0mFIXQ4nIV1lear3E5EaVv7wx/wr1r7d5BYi
    g8BpLGB60hYBISpoEUlWF/V8hxf7pM3QNUagm+MjLzvMFLLcx9s58P0urVzijPPX
    b1H4Tzorx6Lcm512Gcu2IfHGVVxO0JTfCXPgubDTV2QcSS4RL5vF7C8J0nrGr59m
    Nw5hUBW+J0nV1418yVB4oXIC+Q0LFN5rfFRRlahhTZ5aGR/ieU+Ftk6MPE+Nt43o
    bfO64IfCkrb4XKSFzwbpCcFlAkImFuun74L+dOfVstzEMYc0p5XetN1IEhPeppiv
    j0S/p1tmCDHEkUZUkl51xAeRiQdqqVwx4P20tW7P9Dt7H4yqABgas6Yyia5DVjLj
    MZYvC/9Fm4s1ysKQctwP
    =74PE
    -----END PGP SIGNATURE-----
  4. On envoie notre objet signé avec notre MUA préféré. Exemple :
    mail -s "" test-dbm@ripe.net < jkb1.asc

On reçoit un mail récapitulatif : réussite ou échec, ce qui ne s'est pas bien passé, ... Exemple :

SUMMARY OF UPDATE:
 
Number of objects found:                   1
Number of objects processed successfully:  1
  Create:         0
  Modify:         1
  Delete:         0
  No Operation:   0
Number of objects processed with errors:   0
  Create:         0
  Modify:         0
  Delete:         0
 
DETAILED EXPLANATION:
 
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following object(s) were processed SUCCESSFULLY:
 
---
Modify SUCCEEDED: [person] JKB1-TEST   JK Boulay
 
 
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following paragraph(s) do not look like objects
and were NOT PROCESSED:
 
Avertissement : des options RIPE ont été utilisées avec un serveur classique.
% This is the RIPE Database query service.
% The objects are in RPSL format.
%
% The RIPE Database is subject to Terms and Conditions.
% See http://www.ripe.net/db/support/db-terms-conditions.pdf
 
% Information related to 'JKB1-TEST'
 
% This query was served by the RIPE Database Query Service version 1.72 (DBC-WHOIS4)
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
The RIPE Database is subject to Terms and Conditions:
http://www.ripe.net/db/support/db-terms-conditions.pdf
 
For assistance or clarification please contact:
RIPE Database Administration <ripe-dbm@ripe.net>
 
Generated by RIPE WHOIS Update version 1.72 on DBC-WHOIS2
Handled email update (TEST, 2014-04-26 23:42:04)

Il est possible de soumettre plusieurs objets en même temps, signés (avec la même clé ou non) ou non-signés (il faudra alors un mot de passe dans un attribut « password ») ce qui est très pratique pour une modification de masse. Un maintainer peut avoir plusieurs clés PGP. L'authentification se fait "en OU LOGIQUE" sur tous les moyens d'authentification (clé PGP ou non) associés à un des objets maintainer qui sont associés à l'objet à modifier.

Merci à Johndescs pour ces tests.
Fin de l'édit

Faire le ménage dans la base de test

Pour supprimer tous les objets que vous avez créés dans la base de test, il faut les rechercher et choisir l'option « delete ». À cause de la référence circulaire entre vos objets de types mntner et person, vous ne pourrez pas les supprimer directement. Néanmoins, sur cette base de test, on peut ruser : casser la référence circulaire en définissant « TEST-DBM-MNT » comme mntner de l'objet personn et « AA1-TEST » comme admin-c de l'objet mntner. Ça fonctionne uniquement car nous avons le mot de passe de « TEST-DBM-MNT ». J'imagine qu'il existe une méthode plus pragmatique voire un bête formulaire web mais je ne l'ai pas trouvé. Notons quand même que cette base de données est purgée tous les jours à 0h.

Dernières remarques

On notera que les attributs « tech-c » et « admin-c » de tous les objets peuvent pointer sur un seul et même objet de type person.

Enfin, si l'on souhaite visualiser les objets de la base du RIPE sous un format graphique conviviale, l'onglet « Database » de l'outil Ripestat est là pour ça.

ÉDIT du 26/04/2014 à 22h00 :
L'attribut « changed » doit dire qui a modifié/créé un objet et quand. Qui est une adresse email. Quand est une date au format AAAAMMJJ. Il n'est pas nécessaire de remplir la date, elle est auto-complétée. L'attribut changed ne se met pas à jour tout seul lors d'une modification. 😉 Il peut y avoir plusieurs attributs changed pour un même objet, ce qui permet d'avoir un historique des modifications de cet objet.

Ne pas créer/modifier deux objets en parallèle dans deux onglets différents du même navigateur si les deux objets ne sont pas dans la même source (RIPE/TEST) sinon l'application web se prend les pieds dans le tapis et vous hurle dessus : « Unrecognized source: TEST ». Dans cet exemple, ça parle de TEST alors que je voulais soumettre un objet dans RIPE mais comme j'avais ouvert ce formulaire puis ouvert un autre onglet dans TEST pour faire un test ...

Ce formulaire permet d'avoir rapidement l'adresse mail à contacter en cas d'abus. Il suffit de saisir une adresse IP, un ASN ou un inet(6)num. Cela retourne la valeur de l'attribut « abuse-mailbox » d'un objet de type « role » (qui représente une personne/un groupe de personnes dédié à la même tâche (administratif, adminsys, ...) au sein d'une organisation) lui même associé à un objet de type « organisation » (avec un attribut « abuse-c ») lui même associé à la ressource recherchée (aut-num ou inet(6)num). Si cette recherche est infructueuse (car pas d'objet organisation, car pas d'attribut « abuse-c », ...), elle se poursuit récursivement sur les ressources englobantes (as-block englobant, inet(6)num englobant). Merci à Lulu pour la découverte de ce formulaire.
Fin de l'édit

Ce billet relate la mise en œuvre d'un pare-feu redondant en utilisant OpenBSD, Packet Filter, CARP et pfsync.

Même si cette mise en œuvre se situe dans un cadre scolaire (« bouh c'est nul, on ne fait pas comme ça en vrai ! » diront certains), je pense qu'elle vaut la peine d'être partagée. La partie installation d'OpenBSD est un peu bête mais quand c'est exigé ... L'installation d'un pare-feu redondant n'était qu'une partie d'un travail plus vaste, cela explique certaines choses (adressage IP, CPE, ...).

Ce travail a été réalisé avec Hamza Hmama et Frédéric Deveaux.

Il y a déjà une masse de tutoriels concernant une telle mise en œuvre sur le web. Celui-ci se démarquera en montrant, de manière assez précise, comment valider le bon fonctionnement du pare-feu.

Table des matières

Présentation détaillée

L'objectif est de déployer un pare-feu hautement disponible pour protéger des serveurs publics qui se situent dans une DMZ.

Le firewall est un service indispensable dans un réseau pour assurer la sécurité des connexions entrantes et sortantes. Utiliser un firewall redondant permet de diminuer énormément le risque que celui-ci soit hors service. En effet, il faut que toutes les machines physiques qui composent le cluster du firewall soient hors service (panne, maintenance, ...) à un instant précis, ce qui est moins probable qu'une seule machine.

Quelques exemples de déploiements d'une solution de ce type en entrée de gros sites : université de Rennes 1 (500 Mbps en entrée) et École normale supérieure. Voir : présentation « OpenBSD/Packet-Filter retour d'expérience » par Patrick LAMAIZIERE aux JRES 2013.

Pour ce faire, nous allons utiliser deux PC, sur lesquelles nous allons installer OpenBSD et configurer CARP et pfsync.

CARP est un protocole réseau qui autorise plusieurs machines à se partager un groupe d'IP dites virtuelles. Cela permet la haute disponibilité : pour que le service ne soit plus accessible, il faut que l'intégralité des machines qui se partagent le groupe d'IP soit hors service. CARP peut aussi servir à faire du partage de charge entre plusieurs machines (mode actif/actif (voir 4.2.2) en utilisant plusieurs groupes de redondance qui se partagent une même VIP). Nous n'utiliserons pas cette fonctionnalité mais resterons en mode actif/attente). CARP est l'équivalent du protocole propriétaire HSRP de Cisco et du protocole VRRP publié à l'IETF.

Pfsync permet de synchroniser les états de Packet Filter entre toutes les machines du cluster. Ainsi, une connexion qui a commencé en passant par FW1 n'est pas interrompue quand FW1 tombe (panne) et que FW2 prend la main.

Il n'existe aucun outil pour effectuer la synchronisation des règles du pare-feu entre les différentes machines du cluster. Un simple transfert sécurisé (scp) du jeu de règles puis un chargement des règles sur chaque machine avec ssh suffit amplement et s'automatise avec un simple script shell. Des scripts shell plus complets peuvent être trouvés sur le web. Exemple : Script to sync pf rules for CARP fws .

L'équivalent GNU/Linux serait keepalived (démon VRRP) ou ucarp (implémentation de CARP pour GNU/Linux) et conntrackd (équivalent pfsync). Exemple de mise en pratique : Firewall HA with conntrackd and keepalived.

Voici un schéma de l'infrastructure que nous allons déployer :

Schéma de notre pare-feu hautement disponible

Schéma de notre pare-feu hautement disponible.

Nous utilisons un préfixe d'interconnexion /30 entre FW1 et FW2 pour le trafic pfsync. Le CPE est tout bêtement un PC qui exécute Dynamips. Derrière lui se trouve le réseau des utilisateurs.

Mise en œuvre

Il existe plusieurs manières de faire à chaque étape, plusieurs valeurs sont possibles pour chaque paramètre, ... Référez-vous à la documentation OpenBSD pour plus d'informations.

Installation d'OpenBSD

D'abord, on crée un médium d'installation. Malgré le fait d'avoir suivi plusieurs tutoriels disponibles sur le web (sauf ceux impliquant l'utilisation d'une machine virtuelle et/ou une première installation sur la clé elle-même, manque de temps), nous n'avons pas réussis à obtenir une clé USB bootable d'installation d'OpenBSD. Dans ce cas, il faut récupérer et graver l'ISO du CD d'installation de la dernière version stable d'OpenBSD. À l'heure actuelle, elle est disponible à cette URL : ftp://ftp.fr.openbsd.org/pub/OpenBSD/5.4/amd64/install54.iso.

Ensuite, il faut installer OpenBSD sur FW1 et FW2. On peut aussi effectuer une seule installation puis cloner le disque dur sur la deuxième machine, via le réseau, en utilisant Clonezilla. Ci-dessous, la procédure d'installation d'OpenBSD :

  1. Il faut aller dans le BIOS pour configurer le boot à partir du CD d'installation. Ce réglage dépend du type de BIOS, de la version, ... Dans notre cas, pour accéder à l'interface de configuration, il faut appuyer sur la touche « Suppr » du clavier au début de la procédure de démarrage du PC. Il faut ensuite se déplacer dans « Advanced BIOS Features » puis positionner la valeur « USB-CDROM » pour le paramètre « First Boot Device ». Il faut vérifier que le paramètre « Second Boot Device » est bien configuré à la valeur « Hard Disk ». Enfin, il faut revenir au menu principal à l'aide de la touche « Echap » et choisir « Save & Exit Setup ». Au reboot automatique, la machine démarrera sur le CD d'installation OpenBSD.
  2. À l'invite « boot> », il faut appuyer sur la touche « Entrée » du clavier.
  3. « (I)nstall, (U)pgrade or (S)hell? » : Choisir « Install » en tapant « I » et « Entrée ».
  4. « Choose your keyboard layout » : taper « fr » et « Entrée ».
  5. « System hostname? » : taper « FW1 » ou « FW2 » et « Entrée ».
  6. Configuration réseau :
    • « Which one do you wish to configure? » : « done » et « Entrée ».
    • « DNS domain name? » : accepter la proposition par défaut en appuyant sur « Entrée ».
    • « DNS nameservers? » : accepter la proposition par défaut en appuyant sur « Entrée ».
  7. « Password for root account? » : taper le mot de passe de votre choix (ici : « toor ») et « Entrée ». « again » : retaper votre mot de passe et « Entrée ».
  8. « Start sshd by default? » : accepter la proposition par défaut (yes) en appuyant sur « Entrée ».
  9. « Start ntpd by default? » : accepter la proposition par défaut (no) en appuyant sur « Entrée ». La synchronisation de l'horloge est importante, notamment pour la cohérence des logs mais comme notre maquette n'est pas connectée à Internet, nous ne pouvons accéder à aucun serveur de temps.
  10. « Do you expect to run the X Window System ? » : « no » et « Entrée ».
  11. « Setup a user? » : accepter la proposition par défaut (no) en appuyant sur « Entrée ».
  12. Configuration des disques durs/partitionnement :
    • « Which disk is the root disk? » : accepter la proposition par défaut en appuyant sur « Entrée ».
    • « Use DUIDs in fstab? » : accepter la proposition par défaut (yes) en appuyant sur « Entrée ».
    • « Whole disk or edit the MBR? » : accepter la proposition par défaut (whole) en appuyant sur « Entrée ».
    • « (A)uto layout, (E)dit auto layout, or create (C)ustome layout? » : accepter la proposition par défaut (auto layout) en appuyant sur « Entrée ».
  13. Source de l'installation :
    • « Location of sets? » : accepter la proposition par défaut (cd) en appuyant sur « Entrée ».
    • « Which one contains the install media? » : accepter la proposition par défaut (cd0) en appuyant sur « Entrée ».
    • « Pathname to the sets? » : accepter la proposition par défaut (5.4/amd64) en appuyant sur « Entrée ».
    • « Set name(s)? » : accepter la proposition par défaut (done) en appuyant sur « Entrée ».
  14. L'installation s'effectue ...
  15. « Location of sets » : nous ne voulons rien installer de plus donc accepter la proposition par défaut (done) en appuyant sur « Entrée ».
  16. « What timezone are you in? » : taper « Europe/Paris » puis « Entrée ».
  17. Taper « reboot » puis « Entrée ».

Configuration réseau

Configuration des interfaces réseau de FW1
echo "inet 170.16.3.129 255.255.255.248 NONE" > /etc/hostname.em0
echo "inet 170.16.3.193 255.255.255.192 NONE" > /etc/hostname.em1
echo "inet 192.168.1.1 255.255.255.252 NONE" > /etc/hostname.em2
Configuration des interfaces réseau de FW2
echo "inet 170.16.3.130 255.255.255.248 NONE" > /etc/hostname.em0
echo "inet 170.16.3.194 255.255.255.192 NONE" > /etc/hostname.em1
echo "inet 192.168.1.2 255.255.255.252 NONE" > /etc/hostname.em2
Configuration de la route par défaut sur FW1 et FW2

On configure une route par défaut via le CPE :

echo "170.16.3.132" > /etc/mygate
Activation de l'IP forwarding sur FW1 et FW2

On active l'ip forwarding en décommentant la ligne « net.inet.ip.forwarding=1 » dans le fichier /etc/sysctl.conf.

Configuration de l'interface réseau du CPE
conf t
  int fastEthernet 0/3
    ip address 170.16.3.132 255.255.255.248
    no sh
Ajout d'une route sur le CPE

Sur le CPE, on ajoute une route pour joindre le réseau des serveurs (170.16.3.192/26) via les FW

conf t
  ip route 170.16.3.192 255.255.255.192 170.16.3.131
Configuration de l'interface de notre serveur de test

Pour cela, on ajoute les lignes suivantes au fichier /etc/network/interfaces :

iface eth0 inet static
  address 170.16.3.196
  netmask 26
  gateway 170.16.3.195

CARP

Configuration des paramètres généraux de CARP sur FW1 et FW2

Pour cela, il faut rajouter les lignes suivantes dans le fichier /etc/sysctl.conf :

net.inet.carp.allow=1
net.inet.carp.preempt=1

« net.inet.carp.allow=1 » permet d'accepter les paquets du protocole CARP qui arrivent par le réseau.

« net.inet.carp.preempt=1 » permet d'optimiser l'élection du maître dans un groupe de redondance. Cela permet également de basculer toutes les interfaces d'un même groupe de redondance sur un même hôte en même temps.

Configuration des deux interfaces CARP sur FW1
echo "inet 170.16.3.131 255.255.255.248 170.16.3.135 vhid 1 carpdev em0 advskew 1 pass toor" > /etc/hostname.carp0
echo "inet 170.16.3.195 255.255.255.192 170.16.3.255 vhid 2 carpdev em1 advskew 1 pass toor" > /etc/hostname.carp1

« vidh X » : Virtual Host ID. C'est un numéro unique par réseau qui permet d'identifier un groupe de redondance CARP sur ce réseau. Les valeurs possibles sont comprises dans l'intervalle [1,255].

« carpdev » permet de spécifier l'interface réseau physique à utiliser pour ce groupe de redondance CARP.

« advskew X » permet d'introduire un biais pour forcer le choix lors de l'élection du maître du groupe de redondance. Plus la valeur de ce paramètre est élevée, moindre sont les chances de cet hôte de devenir le maître. Dans notre cas, c'est utile pour que les deux IP virtuelles (une sur le réseau d'interconnexion, l'autre sur le réseau des serveurs) soient attribuées au même hôte.

« pass <mot_de_passe> » est le mot de passe à utiliser lors de la communication avec les autres machines du même groupe de redondance. Évidemment, ce mot de passe doit être identifique sur toutes les machines d'un même groupe de redondance.

Configuration des interfaces CARP sur FW2
echo "inet 170.16.3.131 255.255.255.248 170.16.3.135 vhid 1 carpdev em0 advskew 100 pass toor" > /etc/hostname.carp0
echo "inet 170.16.3.195 255.255.255.192 170.16.3.255 vhid 2 carpdev em1 advskew 100 pass toor" > /etc/hostname.carp1

Configuration de Packet Filter

Règles de filtrage

On crée notre propre jeu de règles de filtrage minimaliste puis on l'insère dans le fichier /etc/pf.conf (en supprimant l'existant) de FW1 et FW2 afin qu'il soit appliqué automatiquement à chaque démarrage des deux machines :

# On bloque tout sauf ce qui passe sur la loopback
block
set skip on lo
 
# CARP
pass on { em0 em1 } proto carp keep state
 
# SSH depuis le réseau utilisateur vers les serveurs
pass in on em0 inet proto tcp from 170.16.3.0/24 to 170.16.3.196 port 22 keep state
 
# ICMP partout
pass inet proto icmp from any to any keep state

pfsync

Configuration de l'interface pfsync sur FW1
echo "up syncpeer 192.168.1.2 syncdev em2" > /etc/hostname.pfsync0

Par défaut, les mises à jour des états se font en multicast. Le paramètre « syncpeer » permet de faire les mises à jour d'état en unicast avec uniquement l'hôte spécifié.

« syncdev » permet de spécifier l'interface réseau physique à utiliser pour envoyer/recevoir le trafic pfsync.

Il faut noter que pfsync est assez gourmand en fonction de l'usage et donc du type de trafic qui passe par le pare-feu redondant, c'est pour cela qu'on conseille un lien réseau dédié à pfsync. Dans l'exemple de l'université de Rennes 1 cité plus haut, ils ont une majorité de trafic web. Rappel : pour une même page web, il faut établir XX connexions pour récupérer le contenu (js (comme jquery) stocké ailleurs, polices stockées ailleurs, pubs, ...). Cela génère des états au niveau des firewall et donc, du trafic pfsync pour échanger ces états. En pointe, ils observent 150 Mbps de trafic sur leur lien dédié pfsync.

Configuration de l'interface pfsync sur FW2
echo "up syncpeer 192.168.1.1 syncdev em2" > /etc/hostname.pfsync0
On autorise le protocole pfsync dans Packet Filter, sur FW1 et FW2

Pour cela, on ajoute la règle suivante aux fichiers /etc/pf.conf :

# pfsync
pass on em2 proto pfsync keep state

Valider le bon fonctionnement de notre pare-feu redondant

Il suffit de rebooter toutes les machines (FW1, FW2, serveur de test) pour que la configuration soit appliquée. Ou, de manière plus pragmatique, on peut appliquer les changements à chaud. Sur FW1 et FW2 :

# Recharger le réseau
sh /etc/netstart
 
# IP forwarding et CARP
sysctl -w net.inet.ip.forwarding=1
sysctl -w net.inet.carp.allow=1
sysctl -w net.inet.carp.preempt=1 
 
# Charger le jeu de règles dans PF et activer PF
pfctl -f /etc/pf.conf 
pfctl -e

Sur la machine de test :

ifup eth0

On peut ensuite vérifier que tout fonctionne comme attendu.

Connectivité

On vérifie que le réseau est fonctionnel en faisant passer un ping depuis une machine de test dans le réseau des utilisateurs vers notre serveur de test :

toto@client: # ping 170.16.3.196
PING 170.16.3.196 (170.16.3.196) 56(84) bytes of data.
64 bytes from 170.16.3.196: icmp_req=1 ttl=63 time=1.99 ms
64 bytes from 170.16.3.196: icmp_req=2 ttl=63 time=1.45 ms
64 bytes from 170.16.3.196: icmp_req=3 ttl=63 time=1.16 ms
64 bytes from 170.16.3.196: icmp_req=4 ttl=63 time=1.45 ms

CARP

On constate que CARP est fonctionnel en regardant la sortie de l'outil ifconfig.

Sur FW1 :

# ifconfig carp
carp0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        lladdr 00:00:5e:00:01:01
        priority: 0
        carp: MASTER carpdev em0 vhid 1 advbase 1 advskew 1
        groups: carp
        status: master
        inet6 fe80::200:5eff:fe00:101%carp0 prefixlen 64 scopeid 0x6
        inet 170.16.3.131 netmask 0xfffffff8 broadcast 170.16.3.135
carp1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        lladdr 00:00:5e:00:01:02
        priority: 0
        carp: MASTER carpdev em1 vhid 2 advbase 1 advskew 1
        groups: carp
        status: master
        inet6 fe80::200:5eff:fe00:102%carp1 prefixlen 64 scopeid 0x7
        inet 170.16.3.195 netmask 0xffffffc0 broadcast 170.16.3.255

On constate que FW1 est bien le maître des deux groupes de redondances (« carp: MASTER »).

Sur FW2 :

# ifconfig carp
carp0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    lladdr 00:00:5e:00:01:01
    priority: 0
    carp: BACKUP carpdev em0 vhid 1 advbase 1 advskew 100
    groups: carp
    status: backup
    inet6 fe80::200:5eff:fe00:101%carp0 prefixlen 64 scopeid 0x6
    inet 170.16.3.131 netmask 0xfffffff8 broadcast 170.16.3.135
carp1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    lladdr 00:00:5e:00:01:02
    priority: 0
    carp: BACKUP carpdev em1 vhid 2 advbase 1 advskew 100
    groups: carp
    status: backup
    inet6 fe80::200:5eff:fe00:102%carp1 prefixlen 64 scopeid 0x7
    inet 170.16.3.195 netmask 0xffffffc0 broadcast 170.16.3.255

On constate que FW2 est bien en backup sur les deux groupes de redondances (« carp: BACKUP »).

Une capture réseau depuis FW2 nous montre que FW1 est le maître sur l'IP virtuelle 170.16.3.131 et qu'en conséquence, il diffuse des messages « CARP advertisement » à fréquence régulière (ici : 1 seconde, paramétrable en changeant la valeur du paramètre « advbase ») :

# tcpdump -tttttvvni em0
tcpdump: listening on em0, link-type EN10MB
1.019374 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 10529, len 56)
2.040085 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 19703, len 56)
3.060098 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 41787, len 56)
4.080206 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 55471, len 56)

Si nous coupons l'interface carp0 sur FW1, celui-ci cesse d'émettre, les FW restants émettent des advertisement, une nouvelle élection a lieu entre les FW restants (dans notre cas : uniquement FW2) et FW2 devient le nouveau maître pour l'IP virtuelle 170.16.3.131 :

# tcpdump -tttttvvni em0
tcpdump: listening on em0, link-type EN10MB
5.099795 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 16645, len 56)
6.119458 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=1 demote=0 (DF) [tos 0x10] (ttl 255, id 20322, len 56)
7.4294686187 carp 170.16.3.129 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=255 advskew=255 demote=0 (DF) [tos 0x10] (ttl 255, id 33756, len 56)
7.4294688491 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 22655, len 56)
8.129407 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 31522, len 56)
10.4294507802 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 35522, len 56)
11.4294916715 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 35096, len 56)
12.359366 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 63215, len 56)
14.4294736635 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 17132, len 56)
15.179471 carp 170.16.3.130 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=100 demote=0 (DF) [tos 0x10] (ttl 255, id 32836, len 56)

Les logs (/var/log/messages) de FW2 nous montrent également la transition :

fw2 /bsd: carp0: state transition: BACKUP -> MASTER
fw2 /bsd: carp1: state transition: BACKUP -> MASTER

Quand FW1 est remis en état, il reprend la main et, dans les logs de FW2, nous lisons :

fw2 /bsd: carp1: state transition: MASTER -> BACKUP
fw2 /bsd: carp0: state transition: MASTER -> BACKUP

Pfsync

On vérifie que les interfaces pfsync sont up.

Sur FW1 :

# ifconfig pfsync0
pfsync0: flags=41<UP,RUNNING> mtu 1500
        priority: 0
        pfsync: syncdev: em2 syncpeer: 192.168.1.2 maxupd: 128 defer: off
        groups: carp pfsync

Note : « defer » est un paramètre qui peut être positionné à la valeur « on » pour faire en sorte qu'une connexion n'est réellement acceptée que si le pair pfsync a acquitté la modification de la table des états ou que le délai d'attente pour cet acquittement a expiré. Cela permet une plus grande cohérence entre les pare-feu et est indispensable lorsque plus d'un pare-feu doit s'occuper activement des paquets (typiquement : répartition de la charge).

Sur FW2 :

# ifconfig pfsync0
pfsync0: flags=41<UP,RUNNING> mtu 1500
        priority: 0
        pfsync: syncdev: em2 syncpeer: 192.168.1.1 maxupd: 128 defer: off
        groups: carp pfsync

On constate aussi l'existence d'un trafic réseau pfsync sur le lien d'interconnexion entre les deux pare-feux :

# tcpdump -tttttvvni em2
tcpdump: listening on em2, link-type EN10MB
0.007794 192.168.1.1 > 192.168.1.2: PFSYNCv6 len 276
    act UPD ST COMP count 3
    ...
 (DF) [tos 0x10] (ttl 255, id 38708, len 296)
0.410334 192.168.1.2 > 192.168.1.1: PFSYNCv6 len 276
    act UPD ST COMP count 3
    ...
 (DF) [tos 0x10] (ttl 255, id 22377, len 296)
0.410597 192.168.1.2 > 192.168.1.1: PFSYNCv6 len 108
    act UPD ST COMP count 1
    ...
 (DF) [tos 0x10] (ttl 255, id 51022, len 128)
0.410692 192.168.1.2 > 192.168.1.1: PFSYNCv6 len 108
    act UPD ST COMP count 1
    ...
 (DF) [tos 0x10] (ttl 255, id 40898, len 128)
0.410730 192.168.1.1 > 192.168.1.2: PFSYNCv6 len 276
    act UPD ST COMP count 3
    ...
 (DF) [tos 0x10] (ttl 255, id 65488, len 296)
0.410774 192.168.1.2 > 192.168.1.1: PFSYNCv6 len 108
    act UPD ST COMP count 1
    ...
 (DF) [tos 0x10] (ttl 255, id 24885, len 128)

Enfin, en établissant une session SSH depuis le réseau des utilisateurs vers notre serveur de test, alors que FW1 est le maître sur les deux interfaces CARP, nous constatons que les tables d'états des deux pare-feux sont synchronisées.

Sur FW1 :

# pfctl -ss
all carp 170.16.3.129 -> 224.0.0.18       SINGLE:NO_TRAFFIC
all carp 170.16.3.193 -> 224.0.0.18       SINGLE:NO_TRAFFIC
all pfsync 192.168.1.1 -> 192.168.1.2       MULTIPLE:MULTIPLE
all carp 224.0.0.18 <- 170.16.3.129       NO_TRAFFIC:SINGLE
all carp 224.0.0.18 <- 170.16.3.193       NO_TRAFFIC:SINGLE
all tcp 170.16.3.196:22 <- 170.16.3.132:33607       ESTABLISHED:ESTABLISHED
all tcp 170.16.3.132:33607 -> 170.16.3.196:22       ESTABLISHED:ESTABLISHED

Sur FW2 :

# pfctl -ss
all carp 170.16.3.129 -> 224.0.0.18       SINGLE:NO_TRAFFIC
all carp 170.16.3.193 -> 224.0.0.18       SINGLE:NO_TRAFFIC
all pfsync 192.168.1.2 <- 192.168.1.1       MULTIPLE:MULTIPLE
all carp 224.0.0.18 <- 170.16.3.129       NO_TRAFFIC:SINGLE
all carp 224.0.0.18 <- 170.16.3.193       NO_TRAFFIC:SINGLE
all tcp 170.16.3.196:22 <- 170.16.3.42:33607       ESTABLISHED:ESTABLISHED
all tcp 170.16.3.42:33607 -> 170.16.3.196:22       ESTABLISHED:ESTABLISHED

Si l'on coupe brutalement les interfaces réseau de FW1 (ou FW1 lui-même), on constate que FW2 prend le relais et que la session SSH n'est pas interrompue car les états des deux pare-feux sont cohérents et synchronisés.

Sources

Sécuriser le routage sur Internet

Aujourd'hui, je vous propose un long billet sur le routage inter-domaine, sa sécurité actuelle et RPKI+ROA.

Attention : RPKI+ROA est encore un mécanisme tout jeune. Cela signifie que, bien que les concepts de base ne changeront pas et que ce travail date de mai 2013, certaines informations contenues dans ce travail vont devenir obsolètes à plus ou moins long terme.

Pour lire la version HTML, il suffit de cliquer sur le lien "Lire la suite" (et/ou de poursuivre ci-dessous). Pour ceux qui préfèrent lire un si gros pavé en PDF, c'est par là : Sécuriser le routage sur Internet.

Je mets également les sources LaTeX à votre disposition. Ces sources LaTeX peuvent servir de base à d'autres documents. Sources.

Vous pouvez également récupérer la maquette (vous comprendrez la raison de son existence en lisant le pavé). Elle peut servir pour mieux visualiser le fonctionnement de RPKI+ROA ou pour simplement tester ses différents composants. Elle repose sur LXC. Le tar contient la maquette compressée avec LZMA ainsi que les instructions d'utilisation au format texte. Maquette RPKI-ROA. Les fichiers de configurations principaux pour refaire une maquette from scratch sont disponibles ici : Fichiers de configuration principaux de ma maquette RPKI-ROA.

Et pour terminer, vous pouvez également récupérer le visuel projeté durant ma soutenance et ses sources. Support visuel soutenance | Sources LaTeX support visuel soutenance.

Si vous voulez tout récupérer (maquette, sources, pdf) en un seul coup, c'est par là : Sécuriser le routage sur Internet - Pack all-in-one.

Le tout (les sources LaTeX, les pdf, les images, la maquette, ...) est diffusé sous la licence habituelle de ce blog, à savoir : CC-BY-SA 3.0 France.

Lire la suite >>

Mettre en place un Route Reflector BGP avec Quagga pour s’amuser

Un court billet sur comment monter un Route Reflector avec Quagga sur une maquette.

Les termes Route Reflector (RR) et Route Server (RS) sont souvent confondus et on lit de tout sur le web, même sur les sites web qui disposent d'un bon crédit de fiabilité (univ-*.fr, *.edu) : « RR c'est pour monter un looking glass derrière, de la pure collecte passive, RS c'est pour redistribuer les routes », « RR c'est au contraire collecte + redistribution intelligente », « un RR fait du traitement, un RS redistribue "as-is", dans l'état ». Bref, personne ne semble être d'accord.

Pour y voir plus clair, je vous propose les deux ressources suivantes :

Personnellement, je retiens ce qui suit :

  • Les deux sont une solution (plus ou moins complexe, plus ou moins convenable en fonction de la topologie d'un réseau) au problème du full-mesh : que ce soit en interne d'un AS ou sur un GIX, établir des sessions iBGP (ou eBGP sur un GIX) entre tous les routeurs qui causent BGP en présence, c'est juste impossible, ça ne passe pas à l'échelle.
  • Un Route Reflector fait de la redistribution "as-is", dans l'état. Le choix des meilleures routes est donc centralisé, ce qui n'est pas toujours convenable selon l'emplacement du RR dans le cluster et dans l'AS (meilleure route pour un préfixe du point de vue du RR ne veut pas dire que ce soit forcement la meilleure route pour un client, possibilité de boucle, ...).
  • Un Route Server fait aussi de la redistribution mais effectue un traitement des annonces qu'il reçoit pour chacun de ses clients. On a donc plusieurs RIB, une par client du RS. Chacune contient les meilleures routes pour un client donné. Le client participe au choix de la meilleure route pour chaque préfixe connu grâce à ses filtres (route-map, ...) en entrée. La décision n'est donc pas centralisée.
  • Un RS est plus approprié pour un GIX, un RR est plus approprié pour de la redistribution interne à un AS.

Donc nous voulons bien mettre en place un Route Reflector : un truc qui redistribue bêtement (sans traitement) toutes les routes entre tous les routeurs d'un même AS, avec des sessions iBGP. On veut une topologie avec deux RR. Les deux RR sont reliés directement entre eux. Chaque autre routeur de cette topologie monte une session iBGP avec chacun des RR, pour la redondance.

Pour l'instant, nous n'avons pas de sessions eBGP entre nos RR et l'extérieur.

Pour ceux qui ont le cœur à creuser le paramètre « cluster-id » : Understanding BGP Originator-ID and Cluster-ID. Nous ne l'utiliserons pas ici.

Informations utiles pour s'y retrouver et comprendre les fichiers de config' :

  • IP du premier RR (loopback) : 198.18.3.1/32
  • IP du premier RR (loopback) : 198.18.3.15/32
  • Clients (loopbacks) : 198.18.3.2-30/32
  • Chaque client annonce un /30 subnetté dans 198.19.0.0/16
  • Les adresses des loopback sont prises dans 198.18.3/24. 198.18.0.0/16 est annoncé via un IGP. Mais ce n'est pas ce qui nous intéresse ici.

Voici les directives de configuration qu'il faut utiliser pour monter un Route Reflector avec Quagga. Source : Virtual Laboratory Networking Exercises – BGP Route Reflector Configuration.

Fichier « bgpd.conf » du premier RR :

hostname RR1
 
line vty
	no login
 
log syslog warnings
 
 
router bgp 64500
        # Qui suis-je ?
	bgp router-id 198.18.3.1
 
 
	## RR2
	neighbor 198.18.3.15 remote-as 64500
	neighbor 198.18.3.15 description RR2
	neighbor 198.18.3.15 update-source 198.18.3.1
 
 
	## Clients
	neighbor 198.18.3.2 remote-as 64500
	neighbor 198.18.3.2 description Client-1
	neighbor 198.18.3.2 route-reflector-client
	neighbor 198.18.3.2 update-source 198.18.3.1
 
        neighbor 198.18.3.3 remote-as 64500
	neighbor 198.18.3.3 description Client-2
	neighbor 198.18.3.3 route-reflector-client
	neighbor 198.18.3.3 update-source 198.18.3.1
 
        [...]

Fichier « bgpd.conf » du second RR :

hostname RR2
 
line vty
	no login
 
log syslog warnings
 
 
router bgp 64500
        # Qui suis-je ?
	bgp router-id 198.18.3.15
 
 
	## RR1
	neighbor 198.18.3.1 remote-as 64500
	neighbor 198.18.3.1 description RR1
	neighbor 198.18.3.1 update-source 198.18.3.15
 
 
	## Clients
	neighbor 198.18.3.2 remote-as 64500
	neighbor 198.18.3.2 description Client-1
	neighbor 198.18.3.2 route-reflector-client
	neighbor 198.18.3.2 update-source 198.18.3.15
 
	neighbor 198.18.3.3 remote-as 64500
	neighbor 198.18.3.3 description Client-2
	neighbor 198.18.3.3 route-reflector-client
	neighbor 198.18.3.3 update-source 198.18.3.15
 
        [...]

Fichier « bgpd.conf » d'un client (c'est pareil pour tous les autres clients) :

hostname C1
 
line vty
	no login
 
log syslog warnings
 
 
router bgp 64500
        # Qui suis-je ?
	bgp router-id 198.18.3.2
	network 198.19.0.0/30
 
 
        ## RR
	neighbor 198.18.3.1 remote-as 64500
	neighbor 198.18.3.1 description RR1
	neighbor 198.18.3.1 update-source 198.18.3.2
 
	neighbor 198.18.3.15 remote-as 64500
	neighbor 198.18.3.15 description RR2
	neighbor 198.18.3.15 update-source 198.18.3.2