Categorie: Administration réseau
notice
api

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 »)

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 serais 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 regrouper 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 ? ».

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.

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

home
suggest

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). 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 .

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

search

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 >>

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
notice
api

Maquetter des réseaux avec LXC

Ce billet va tenter de résumer la manière dont j'utilise LXC pour créer et utiliser des maquettes de grande taille pour tester des choses (protocoles, logiciels, ...) ayant trait aux réseaux informatiques.

Table des matières

Pourquoi maquetter ?

Cela peut être utile de simuler la présence et les interactions d'un nouvel élément avant sa mise en production effective sur le réseau physique voire de maquetter un nouveau réseau complet pour voir s'il répond aux objectifs fixés avant d'acheter le matériel.

Maquetter permet aussi de découvrir et d'apprendre : mettre en œuvre des techniques, des protocoles, ... dans une maquette permet de les découvrir plus efficacement que d'étudier simplement la théorie.

Maquetter est aussi utile dans la recherche, pour faire des expériences et les rendre reproductibles, savoir quels facteurs sont aggravants (en les isolant les uns après les autres), trouver des solutions et les tester.

Les avantages de LXC pour maquetter

Bien que LXC puisse être utilisé pour isoler des applications, notamment pour un usage serveur, il présente également des avantages pour maquetter les machines d'un réseau.

LXC reste plus léger que de la virtualisation de type KVM : un ordinateur portable relativement modeste avec 3G de RAM fait tourner une maquette de 40 routeurs qui font de l'OSPF, du BGP et s'échangent un peu de trafic.

Dans un milieu plus "apprentissage/découverte", LXC est meilleur que Netkit (et ses frontend graphiques comme Marionnet), à mon avis, car LXC est maintenu activement et il offre la possibilité d'utiliser un noyau plus récent (2.26 avec Netkit, 2.19 avec Marionnet), ce qui n'est pas négligage quand on veut tester des fonctionnalités nouvelles ou quand certaines applications (notamment des démons) ont trop évoluées et ne sont plus capables de se binder sur un vieux noyau (il me semble avoir vu un cas avec Apache httpd). Sans compter la facilité de modification du système de base dans le cas de LXC (alors que c'est un véritable calvaire avec Netkit). Je ne sais pas ce qu'apporte (ou non) LXC comparé à User Mode Linux utilisé directement, sans passer par Netkit. ÉDIT du 19/09/2013 à 19h05 : Comme le mentionne Kartoch dans les commentaires, on peut utiliser Netkit sans avoir les droits root (modulo que /dev/shm ne doit pas être en noexec), ce qui a son intérêt dans une salle de TP à la fac/IUT/autre. Fin de l'édit

Néanmoins, autant dans un usage d'isolation des applications sur un serveur, je peux entrevoir les limites de LXC, la sécurité (un conteneur compromis qui permettrait de remonter au noyau et de mettre le zouk dans le reste du système/des autres conteneurs, cela ne me surprendrait pas), autant j'avoue ne pas savoir dans quels cas le fait que ce soit le même noyau dans l'hôte et dans tous les conteneurs est nuisible à un maquettage. Je peux simplement dire que cela me semble être utile dans les expériences aux timings serrés ou pour faire des mesures car la vision du temps est identique dans tous les conteneurs : l'instant T est le même dans tous les conteneurs.

Mettre des conteneurs LXC en réseau

Pour relier les conteneurs entre eux, on peut utiliser un ou plusieurs bridges. C'est intégré de base dans LXC et cela permet de voir tout le trafic de la maquette en dumpant simplement le trafic qui passe sur le bridge avec tcpdump/wireshark. Très pratique pour débugger, faire des stats, ... De plus, comme un bridge mémorise les MAC, comme un switch physique, le trafic unicast n'est pas transféré aux conteneurs qui n'en sont pas les destinataires.

Si l'on a besoin de fonctionnalités plus avancées (VLAN, agrégation de liens, ...), on peut utiliser Open vSwitch. J'avais testé pour découvrir : la communication entre conteneurs LXC juste marche. J'ai entendu dire que VDE ne fonctionnait pas avec les interfaces TAP de LXC. N'ayant jamais eu besoin de plus qu'un bridge, je n'ai jamais vérifié.

J'ignore s'il est possible d'interfacer LXC avec des routeurs Cisco/Juniper émulés. Dynamips semble pouvoir utiliser VDE mais comme LXC semble ne pas pouvoir ...

Adressage

Pour adresser/nommer votre maquette, je vous conseille fortement d'utiliser les ressources réservées à l'IANA : IPv4/v6, ASN, TLD, ... Cela fait sérieux et évite les collisions avec le réseau local (quand vous aurez eu à dépatouiller un tel cas, vous vous mettrez à utiliser les ressources réservées, je vous le garantit !).

Rendre la maquette plus réaliste

Au niveau réseau, Netem (network emulator) permet de rajouter de la latence, de la perte, de la corruption, ... sur les liens entre les différents conteneurs LXC. Pour que cela ne fasse pas trop artificiel, il est possible de faire varier la latence, la perte, ... en utilisant une distribution à la normale. On trouve plein d'exemples sur le web : Netem | The Linux Foundation, Simuler un lien WAN sous Linux chez NicoLargo, ... Netem est juste indispensable dès lors que l'on essaye de reproduire des réseaux réels ou, au moins, des conditions réelles.

Au niveau de chaque conteneur, les cgroups (control groups) permettent d'affecter des priorités d'accès aux ressources (CPU, réseau, ...) différentes pour chaque conteneur. Cela permet de favoriser un conteneur plutôt qu'un autre. Dans le cadre de maquettes réseau, cela permet de tenir compte des disparités que l'on trouve sur un réseau physique : on n'a des modèles de routeurs différents avec des capacités de traitement différentes ... Cela permet aussi de mesurer le comportement d'algorithmes de routage dans des situations défavorables. LXC lui-même est basé sur les cgroups. Je n'ai pas encore utilisé les cgroups dans une maquette donc je n'ai pas de ressources à conseiller plus que d'autres.

Automatiser le tout

Je vais vous montrer comment j'automatise la création et la gestion d'une grosse maquette avec des conteneurs LXC. L'ennui, c'est qu'il y a du besoin spécifique dedans (routing, forward, ...). Je vais essayer de tout bien vous expliquer pour que vous puissiez adapter à vos projets.

Preseed-file

Mes conteneurs sont tous basés sur Debian puisque Debian, c'est la vie. Pour créer des dizaines de conteneurs LXC à la chaîne, il est nécessaire d'avoir un preseed-file bien foutu qui configurera une bonne partie du conteneur (login, logiciels supplémentaires à installer, ...) lors de la création dudit conteneur sans poser aucune question dans une interface semi-graphique (ça ne passe pas à l'échelle ce genre de chose). Or, un preseed-file bien foutu, qui juste marche, bah ça ne court pas le web.

Voici celui que j'utilise, issu de quelques trouvailles personnelles minoritaires et de la fusion de ces deux modèles : Creating a Debian sliver template sur wiki.confine-project.eu et Debian wheezy preseed file for lxc-create -t debian lxc version 0.9.0.alpha2.

Pour le résumer :

  • Les conteneurs auront l'architecture amd64 et seront créés à partir du dépôt sid ;
  • Packages supplémentaires : less iputils-ping iptables telnet traceroute wget nano rsyslog bash-completion quagga et openssh-server ;
  • Utilisation d'un bridge nommé br0 ;
  • MAC fixe (on la remplacera plus loin vu que LXC ne sait pas faire tout seul) ;
  • On ne supprime pas la capabilitie « sys_admin » car elle est nécessaire à Zebra ;
  • On ne crée pas un compte utilisateur normal ;
  • Le mot de passe root est « toor » ;
  • On utilise le fuseau horaire « Europe/Paris » ;
# # Debian preseed file for CONFINE sliver template
# Tested on lxc 0.9.0~alpha3-2 and live-debconfig 4.0~a17.
 
# ## Distribution and packages
lxc-debconfig lxc-debconfig/distribution string sid
lxc-debconfig lxc-debconfig/architecture string amd64
lxc-debconfig lxc-debconfig/archives multiselect none
lxc-debconfig lxc-debconfig/mirror string ftp://ftp2.fr.debian.org/debian/
lxc-debconfig lxc-debconfig/archive-areas multiselect main
lxc-debconfig lxc-debconfig/packages string less iputils-ping iptables telnet traceroute wget nano rsyslog bash-completion quagga
 
# ## Network
# Please adjust to the name of the bridge used in your host.
lxc-debconfig lxc-debconfig/eth0-bridge string br0
# Private MAC address, to be replaced on sliver creation.
lxc-debconfig lxc-debconfig/eth0-mac string 52:C0:A1:AB:BA:1A
# Private veth interface name, to be replaced on sliver creation.
#lxc-debconfig lxc-debconfig/eth0-veth string veth-sliver
 
# ## Other container options
lxc-debconfig lxc-debconfig/auto boolean false
# Use live-debconfig to further configure the container.
lxc-debconfig lxc-debconfig/lxc-debconfig-with-live-debconfig boolean true
lxc-debconfig lxc-debconfig/apt-recommends boolean false
# Avoid debconf questions.
lxc-debconfig lxc-debconfig/debconf-frontend select noninteractive
## (default value)
##lxc-debconfig lxc-debconfig/debconf-priority string medium
 
# For running commands in the container and host at the end.
#lxc-debconfig lxc-debconfig/late-command string
#lxc-debconfig lxc-debconfig/late-host-command string 
 
 
# Capabilities to be dropped from the container.
lxc-debconfig lxc-debconfig/capabilities string \
    audit_control audit_write ipc_lock mac_admin mac_override \
    sys_module sys_pacct sys_rawio sys_resource sys_time \
    syslog wake_alarm
 
# For mounting filesystems in container.
#lxc-debconfig lxc-debconfig/mount0/entry string
##lxc-debconfig lxc-debconfig/mount0/comment string \
##    Bind mount host path in container
 
# ## Live-debconfig scripts configuration
 
# (For some reason live-debconfig options must be on a single line
# or the following options are not interpreted correctly.)
 
live-debconfig live-debconfig/components multiselect openssh-server, passwd
 
# ### LXC (sysvinit)
# Perform LXC tweaks in the container.
live-debconfig live-debconfig/sysvinit/lxc-enable boolean true
## (default values)
##live-debconfig live-debconfig/sysvinit/lxc-consoles string 6
##live-debconfig live-debconfig/sysvinit/lxc-disable-services string checkroot.sh hwclockfirst.sh hwclock.sh kmod module-init-tools mountall.sh mountkernfs.sh umountfs umountroot
 
### Hardware clock access (util-linux)
live-debconfig live-debconfig/util-linux/hwclockaccess boolean false
 
# ### Host name (hostname)
# Host name, to be replaced on sliver creation.
live-debconfig live-debconfig/hostname/hostname string sliver
 
# ### Network configuration (ifupdown)
live-debconfig live-debconfig/ifupdown/lo-comment string The loopback interface
live-debconfig live-debconfig/ifupdown/lo-enable boolean true
 
# Private interface method, to be replaced on sliver creation.
live-debconfig live-debconfig/ifupdown/eth0-ipv4-comment string The private interface
live-debconfig live-debconfig/ifupdown/eth0-ipv4-method select dhcp
 
# For static configuration of network interfaces.
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-method select static
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-address string 1.2.3.4
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-netmask string 255.255.255.0
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-gateway string 1.2.3.1
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-network string 1.2.3.0
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-broadcast string 1.2.3.255
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-mtu string 1500
##live-debconfig live-debconfig/ifupdown/eth0-ipv4-post-up string post-command
 
# For static configuration of DNS.
##live-debconfig live-debconfig/ifupdown/nameserver-addresses string 5.6.7.8 9.10.11.12
##live-debconfig live-debconfig/ifupdown/nameserver-domain string example.com
##live-debconfig live-debconfig/ifupdown/nameserver-search string lan example.com
##live-debconfig live-debconfig/ifupdown/nameserver-options string debug
 
# ### Users (passwd)
live-debconfig live-debconfig/passwd/shadow boolean true
live-debconfig live-debconfig/passwd/root-login boolean true
live-debconfig live-debconfig/passwd/make-user boolean false
live-debconfig live-debconfig/passwd/root-password string toor
live-debconfig live-debconfig/passwd/root-password-again string toor
 
# FUSEAU HORAIRE
tzdata tzdata/Areas select Europe
tzdata tzdata/Zones/Etc select UTC
tzdata tzdata/Zones/Europe select Paris

Depuis peu, il n'est plus possible d'utiliser un preseed-file avec le template Debian sauf à utiliser le package « lxc » qui se trouve dans le dépôt « experimental » de Debian. Voir : 0.9.0~alpha3-2+deb8u1 : “lxc” package : Debian .

Scripts

Ensuite, il faut des scripts pour automatiser la création, le démarrage, l'arrêt et l'éventuelle destruction des conteneurs. Ci-dessus, vous trouverez les scripts que j'utilise. Ce n'est pas du grand art mais ça fait ce qui doit être fait.

Commun

Commun est un fichier qui sert à positionner les variables et les tests utiles à plusieurs scripts comme le nom de tous les conteneurs de la maquette LXC ou vérifier les droits.

MACHINES="R1 R2 R3 R4"
 
if [ $USER != "root" ]
then
	echo "Vous devez exécuter ce script en étant root."
	exit 1
fi
Créer

Pour que vous puissiez comprendre ce script, je dois vous expliquer comment je hiérarchise les différents éléments qui permettent de créer la maquette.

Racine de la maquette
|-- network
|-- routing
|-- scripts
|-- tests

Network contient des fichiers nommés « config.$NOM_DU_ROUTER ». Chacun d'entre eux contient les instructions qui permettent de mettre en place le réseau sous LXC (« lxc.network.type », « lxc.network.ipv4 », « lxc.network.hwaddr », ...). Elles seront ajoutées au fichier « config » du conteneur correspondant lors de la création de la maquette. C'est aussi dans ce dossier que je stocke un fichier « hosts » qui fait la correspondance entre le nom de mes machines (voir des interfaces quand j'ai besoin d'une telle granularité (exemple : « 198.18.0.1 eth0.r1.local. »)) et leur adresse IP "principale" (une loopback, par exemple).

Routing contient, quand j'en ai besoin, les fichiers de configuration de Quagga. Exemple : bgpd.conf, ospfd.conf, zebra.conf, ... Tous sont suffixés avec le nom du conteneur LXC sur lequel devra être mis le fichier. Exemple : « zebra.conf.R1 ». Ils seront copiés dans le dossier /etc/quagga lors de la création de la maquette.

Scripts contient tous les scripts shell de gestion de la maquette. Typiquement ceux que je suis en train de vous présenter. :P

Tests contient des scripts ou des binaires qui permettent d'effectuer des tests sur la maquette. Le script bash « pingall » (voir plus bas) est typiquement un de ces moyens de test. Ils seront copiés dans /root.

Ceci étant dit, voici le script le plus complet que j'ai écrit pour construire une maquette avec LXC :

#!/bin/bash
 
source commun
 
HERE=`pwd`
PARENT=$HERE/..
 
for i in $MACHINES
do
	# Création du conteneur
	lxc-create -n $i -t debian -- --preseed-file=$HERE/preseed-file
 
 
	# Proc doit être en RW pour permettre l'activation de l'ip_forward
	sed -i 's/proc proc proc ro/proc proc proc rw/' /var/lib/lxc/$i/config
 
 
	# Pour que SSH accepte les connexions
	sed -i 's/UsePAM yes/UsePAM no/' /var/lib/lxc/$i/rootfs/etc/ssh/sshd_config
 
 
	# Mise en place du réseau
	head -n-6 /var/lib/lxc/$i/config > /var/lib/lxc/$i/config.tmp
	mv /var/lib/lxc/$i/config.tmp /var/lib/lxc/$i/config
	cat $PARENT/network/config.$i >> /var/lib/lxc/$i/config
 
	# Toutes les interfaces sont sur le même bridge donc les réponses ARP ne venant 
	# pas de la bonne interface peuvent être gênantes. Pour eviter ça, on active arp_filter
	echo "net.ipv4.conf.all.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf
	echo "net.ipv4.conf.default.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf
 
	intSurCeRouteur=`grep -Eo "eth[0-9]" /var/lib/lxc/$i/config`
 
	for j in $intSurCeRouteur
	do
			echo "net.ipv4.conf.$j.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf
	done
 
 
	# Associations IP<->nom
	cat $PARENT/network/hosts >> /var/lib/lxc/$i/rootfs/etc/hosts
 
 
	# Routing (on veut zebra + ospf + bgp)
	cp $PARENT/routing/zebra.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/zebra.conf
	cp $PARENT/routing/ospfd.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/ospfd.conf
	cp $PARENT/routing/bgpd.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/bgpd.conf
	sed -i 's/^zebra=no/zebra=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons
	sed -i 's/^ospfd=no/ospfd=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons
	sed -i 's/^bgpd=no/bgpd=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons
 
 
	# Forward
	sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /var/lib/lxc/$i/rootfs/etc/sysctl.conf
 
 
	# Tests
	cp $PARENT/tests/pingall.sh /var/lib/lxc/$i/rootfs/root/
	chmod u+x /var/lib/lxc/$i/rootfs/root/pingall.sh
done
Lancer

On met en place ce qu'il faut (cgroups, création du bridge, on ne drop pas les paquets qui passent d'un conteneur à l'autre, ..) puis on lance les conteneurs et on vérifie qu'ils sont bien lancés.

#!/bin/bash
 
source commun
 
mount -t cgroup none /sys/fs/cgroup
 
brctl addbr br0 
ip link set up dev br0
 
iptables -P FORWARD ACCEPT
ip6tables -P FORWARD ACCEPT
 
for i in $MACHINES
do
	lxc-start -d -n $i
done
 
sleep 10
 
for i in $MACHINES
do
	echo "$i :"	
	lxc-info -n $i
	echo -e "\n"
done

Alors oui, ce script causera l'affichage d'erreurs lors d'utilisations successives car il n'y a pas de contrôles et que le bridge existe déjà et que les cgroups sont déjà montés. Rien de grave donc, juste trois lignes de textes sur la console. :P

Stopper

On arrête les conteneurs et on vérifie qu'ils ont bien été arrêtés.

#!/bin/bash
 
source commun
 
for i in $MACHINES
do
	lxc-stop -n $i
done
 
sleep 10
 
for i in $MACHINES
do
	echo "$i :"	
	lxc-info -n $i
	echo -e "\n"
done

Je sens que je vais me faire troller sur l'utilisation de « lxc-stop » qui fait un arrêt violent des conteneurs en lieu et place de « lxc-halt » qui laisse les conteneurs s'éteindre par eux-mêmes. L'explication est simple : sur chacune de mes maquettes, lxc-halt fonctionne pour quelques conteneurs très minoritaires même en attendant et je dois lancer lxc-stop ensuite ... Et comme je n'ai remarqué aucun dégât en utilisant uniquement lxc-stop ... bah j'utilise uniquement lxc-stop.

Détruire

Permet de détruire tous les conteneurs LXC de la maquette.

#!/bin/bash
 
source commun
 
for i in $MACHINES
do
	lxc-destroy -n $i
done
Netem

Pour ajouter une latence calculée et différente entre chaque lien sans utiliser de distribution à la normale :

#!/bin/bash
 
# À lancer sur l'hôte, après que toutes les interfaces du lab 
# aient été créées ... soit environ start.sh + 10 secondes
 
modprobe sch_netem
 
# Un groupe de lignes = un routeur. Ici R1
tc qdisc add dev lxc_R1_R2 root netem delay 1.180890283ms
tc qdisc add dev lxc_R1_R3 root netem delay 2.3495148631ms
 
# R2
tc qdisc add dev lxc_R2_R1 root netem delay 1.180890283ms
 
# R3
tc qdisc add dev lxc_R3_R1 root netem delay 2.3495148631ms
tc qdisc add dev lxc_R3_R4 root netem delay 0.8994545122ms
 
# R4
tc qdisc add dev lxc_R4_R3 root netem delay 0.8994545122ms

Pour appliquer d'autres caractéristiques (perte, corruption) sur un ou plusieurs liens, il suffit d'ajouter des lignes à ce script.

Pingall

Pour vérifier que le réseau de mes maquettes est opérationnel, j'utilise, en premier lieu, un bête script qui ping toutes les adresses IPs allouées (en utilisant les noms, comme ça, on teste aussi la validé du fichier /etc/hosts ;) ) et qui s'arrête à la première erreur rencontrée.

J'utilise souvent une loopback adressée sur chaque routeur. Cela permet de considérer les routeurs comme des destinations, ce qui est toujours utile (qui à dit MPLS ? :) ). Cela permet aussi "d'accrocher" les protocoles de routage dynamiques (exemple : BGP et « update-source ») dessus.

#!/bin/bash
 
INTERFACES_PHY="eth0.r1.local eth1.r1.local eth0.r2.local eth0.r3.local eth1.r3.local eth0.r4.local "
 
INTERFACES_LO="lo.r1.local lo.r2.local lo.r3.local lo.r4.local"
 
 
echo -e "\033[0;32m\n\n---------- Testons d'abord chaque lien ----------\n\033[0;39;49m"
j=0
for i in $INTERFACES_PHY
do
	ping -c 2 -w 2 $i
 
	if [ $? -ne 0 ]
	then
		echo -e "\033[0;31m\n\n---------- ÉCHEC ----------\n\033[0;39;49m"
		exit 5
	fi
 
	echo -e "\n"
done
 
 
echo -e "\033[0;32m\n\n---------- Testons ensuite chaque loopback ----------\n\033[0;39;49m"
j=0
for i in $INTERFACES_LO
do
	ping -c 2 -w 2 $i
 
	if [ $? -ne 0 ]
	then 
		echo -e "\033[0;31m\n\n---------- ÉCHEC ----------\n\033[0;39;49m"
		exit 5
	fi
 
	echo -e "\n"
done
 
 
echo -e "\033[0;32m\n\n---------- SUCCÈS ----------\n\033[0;39;49m"
exit 0
home
suggest
search
Bear