Aller au contenu | Aller au menu | Aller à la recherche

mercredi, avril 4 2018

Mon nouvel hyperviseur Xen et ses petites subtilités

Disclaimer

Comme pour à peu près n'importe quel billet de la catégorie "Vieux con de hacker old school", ce billet parle de trucs techniques. J'ai glissé des liens ici et là pour que ma sœur puisse presque suivre les très grandes lignes (les autres personnes peuvent aussi cliquer, je suis de bonne humeur aujourd'hui), mais je n'expliquerai pas la plupart des concepts en détails.

Comme pour à peu près n'importe quel billet de la catégorie "Vieux con de hacker old school", tout ceci a été fait par un Vieux con de hacker old school (ah mais c'est donc ça l'explication du nom de la catégorie !!!!!!), dans des conditions de sécurité optimales, sur des machines consentantes, avec un vrai réseau de prod fonctionnel à coté pendant tout le temps de la préparation pour pouvoir aller buller sur facebook chercher les infos au fur et à mesure des problèmes qui se sont posés.

Comme pour à peu près n'importe quel billet de la catégorie "Vieux con de hacker old school", il n'y aura aucune photo de chaton dans ce billet. Il n'y aura d'ailleurs probablement pas du tout de photo, image ou tout autre truc du genre.......

Comme pour à peu près n'importe quel billet de la catégorie "Vieux con de hacker old school", libre à vous de suivre les indications de ce billet pour reproduire la même chose chez vous, je n'essaierai même plus de vous en dissuader, mais ne venez pas vous plaindre en cas de désagrément plus ou moins douloureux pour vos données, voire pour votre intégrité physique (et je ne veux pas savoir comment c'est arrivé dans ce cas !).

Comme pour à peu près n'importe quel billet de la catégorie "Vieux con de hacker old school", il y a très peu de chances pour que le contenu de ce billet vous aide d'une quelconque façon à pecho en boite.......

Comme pour n'importe quel billet de la catégorie "Vieux con de hacker old school", aucun animal mignon n'a été blessé, tué ou mangé pendant la réalisation des opérations.

Le contexte

Jusqu'à présent, j'avais toujours séparé mon NAS et mon hyperviseur Xen. Du coup, d'un coté, j'avais un serveur avec de gros disques (pour l'époque) en RAID miroir dédiés au stockage de mes photos données et peu d'autres contraintes, à coté un autre serveur (aussi avec des disques en RAID miroir, mais nettement plus petits) qui faisait tourner plusieurs machines virtuelles. Les problématiques étaient donc distinctes.

Un hyperviseur pour les gouverner tous....

Mais j'ai entre temps récupéré (gratos, du coup ca serait dommage de se priver) un serveur un peu ancien mais cependant tout à fait apte à gérer l'ensemble:

  • 2 sockets, 4 coeurs hyperthreadés par CPU, soit un total de 16 unités logiques de traitement d'après mon /proc/cpuinfo (nous ne débattrons pas ici de l'exactitude de ce calcul, en réalité j'ai bien 8 coeurs qui sont plus ou moins capables de chacun traiter 2 trucs en parallèle).
  • Du SATA 2. Je n'ai pas le budget pour acheter des disques SSD de plusieurs To, donc j'utilise des disques mécaniques, donc le SATA 2 est suffisant d'un point de vue performances.
  • 48Go de RAM. Bon, en vrai au départ, je n'avais que 8Go de RAM, ce qui est déjà correct, mais comme j'ai trouvé un lot de 6x8Go (de la DDR3 ECC Registered) à pas cher, j'en ai profité !
  • 10 Interfaces Ethernet Gigabit (dont 4 en SFP), et un connecteur disponible pour venir brancher des cartes 10Gbs.....

Du coup, même mon NAS va passer en machine virtuelle (après quelques mesures de performances disques qui confirment que les débits depuis une VM sont quasi identiques à ceux depuis le Dom0), et mes anciennes stratégies d'utilisation des disques deviennent difficilement applicables....

En plus, je me retrouve à utiliser de très gros disques (8To) sur un serveur dont le BIOS est un peu ancien, ce qui m'a posé quelques petits problèmes supplémentaires.

Un peu de stratégie en amont....

Une des questions existentielles qu'on se pose quand on déploie de nouveaux serveurs, c'est le partitionnement des disques.....
Jusqu'à présent, j'avais toujours utilisé des partitionnements assez classiques: un MBR, une partition principale pour le système de base, et une partition étendue qui contient le reste, dont un /usr en lecture seule avec de la marge pour d'éventuelles installations ultérieures d'outils/services/etc...., très souvent un /var séparé, un /tmp soit en RAMFS soit en partition séparée également, et un /home qui occupe tout le reste.

Sur un NAS dédié, ca fonctionne assez bien (on perd quelques Go sur quelques To, pas bien grave.....).
Sur mon ancien serveur de VMs, ca fonctionnait assez bien aussi, vu que toutes les VMs avaient leurs disques stockés en fichiers dans le /home.

Sur mon nouveau précieux serveur unique, c'est plus compliqué. Si j'avais décidé de laisser le /home géré par le Dom0, j'aurais pu rester plus ou moins sur ce modèle, mon /home gigantesque aurait stocké mes données ET les images de mes VMs. Sauf que, pour faire propre, l'hyperviseur ne fera vraiment pratiquement QUE hyperviser (il fera aussi ce qu'il est vraiment le seul à pouvoir faire, comme surveiller les disques, la température, et tant qu'à faire l'onduleur), donc le NAS sera assuré par une VM. Donc le gros /home de plusieurs To ne sera pas directement accessible par l'hyperviseur.

En vrai, là, j'aurais pu faire un truc un peu dégueulasse, genre un /home gigantesque sur le Dom0, et dedans une image presque aussi gigantesque utilisée par la VM qui fera le NAS....... Sauf que je veux un NAS qui dépote, et c'est vraiment une très très mauvaise base pour avoir de bonnes performances disques par la suite ! Accessoirement, j'aurais aussi eu la question de la taille de l'image: trop et je n'ai plus assez de place pour d'autres usages, pas assez et un jour je déborde sur cette partition (même si à priori, j'ai de la marge cette fois ci, mais je croyais déjà que c'était le cas sur les anciens disques de 2To.......).

Du coup, premier élément de solution: je vais (enfin ?) me mettre à utiliser Logical Volume Management, LVM pour les intimes (et on va rapidement devenir assez intimes.....). Dans mon cas, la possibilité d'ajouter des disques/partitions plus tard ne m'intéresse pas vraiment: j'ai déjà de la marge (enfin, pour l'instant), et surtout, parmi les rares contraintes de mon prochain serveur, je n'ai que 2 emplacements propres pour disques durs 3 1/2, qui seront donc d'entrée de jeu utilisés (par mes 2 disques de 8To en RAID miroir, pour les 2 du fond qui ne suivent pas.....).
Ce qui m'intéresse vraiment, c'est avant tout de pouvoir créer un volume logique ("logical volume", que nous appellerons simplement "lv" par la suite, ou "lvs" quand il y en aura plusieurs) avec une quantité d'espace allouée, et de pouvoir augmenter cet espace plus tard. Du coup, je n'alloue pas tout pour l'instant, et je pourrai rajouter un peu au fur et à mesure, là où ça sera nécessaire. Notez que, techniquement parlant, LVM permet aussi de faire l'inverse (réduire la taille d'un lv, et du système de fichiers dedans), mais c'est quand même plus ou moins déconseillé par plein de monde de faire dans ce sens......
Je suis un peu tenté aussi par d'autres fonctionnalités de LVM, comme la possibilité d'utiliser un disque SSD en cache pour le gros lv du /home, ou le fait de pouvoir faire des clichés (que nous appellerons "snaps" par la suite).

Mon plan de partitionnement se simplifie sacrément d'un coup: une énoooooorme partition entièrement dédiée à LVM, et je crée au fur et à mesure de mes besoins de petits lvs, pour le Dom0 comme pour les VMs. Bon, parmi ces "petits lvs", il y en aura un de plusieurs To dès le départ, mais l'idée reste la même, et à la fin de l'installation initiale, il y aura au moins 1 ou 2 TOs non alloués.

Il reste donc juste à organiser le partitionnement de chaque système.....

Diviser pour mieux régner

Le plus simple serait d'avoir une partition par système. Cette approche présente historiquement le gros avantage de ne pas trop se poser de questions de répartition d'espace entre les partitions, mais j'ai donc désormais LVM pour ne plus être coincé de ce coté là. En contrepartie, cette approche présente également au moins 2 problèmes potentiels:

Un /var/log ou un /tmp qui se remplissent trop peuvent saturer tout l'espace disque. Un /home aussi, mais dans mon cas, il est déjà fourni à part.

Comme les mêmes /var, /tmp et /home doivent être en lecture/écriture, tout le système doit l'être aussi. Or, avoir une partie de son système en lecture seule présente des avantages: réduction des risques d'erreurs de manipulation, (un peu) plus difficile pour un attaquant de poser durablement un rootkit ou équivalent, peu/pas de risques de casser le système de fichiers même en cas d'arrêt brutal du serveur. Bien sur, pour les opérations de maintenance (mises à jour, etc.....) on peut/doit repasser temporairement le système de fichiers en lecture/écriture.


Bref, je veux partitionner, et avoir une partie de mon système en lecture seule..... Dans un premier temps, du coup, j'ai envisagé de faire plein de partitions par système, comme d'habitude, dont /usr en lecture seule..... pour le Dom0, pas trop de problèmes, mais pour les VMs, c'est plus lourd: soit je crée un seul lv au niveau du Dom0, et je fais du LVM dans du LVM (le 2eme niveau étant géré au niveau de la VM), niveau perfs ca semble ne pas changer grand chose, mais le redimensionnement des lvs devient plus lourd. Soit je crée plein de lvs au niveau du Dom0, je passe tout ca à la VM, ca fait un fichier de conf Xen un peu compliqué, mais surtout ça va me compliquer la vie le jour où je voudrai faire des snaps de mes VMs !

Donc j'ai fait un état des lieux par répertoire. Je ne tiens pas compte des répertoires particuliers (/sys, /run, /dev, etc...... mais pas /etc !) qui sont de toutes façons gérés dynamiquement.
En gros, /var, /tmp, /root et /home doivent être en lecture/écriture. /root et /home contiendront très peu de données dans mon cas (un .bashrc, un .ssh/authorizedkeys et peut être 2-3 trucs temporaires de temps en temps). A vrai dire, une fois l'installation initiale d'un système terminée, le seul intérêt pour moi de les avoir dans la catégorie "lecture/écriture", c'est pour pouvoir enregistrer le .bashhistory........ du coup, j'ai une solution simple à base de liens symboliques pour regrouper ces répertoires dans un seul système de fichiers:

/var
/home -> var/home
/root -> var/root
/tmp -> var/tmp (ou en tmpfs)

Notez les liens relatifs (il n'y a pas de '/' au début), qui éviteront de mauvaises surprises si je me retrouve à monter les partitions en dehors de leur contexte normal.....

Selon le service fourni par les VMs, je voudrai éventuellement un /srv dédié, dans lequel j'irai stocker sites webs, base LDAP, base SQL, etc....

Idéalement, tout le reste devrait être en lecture seule, dont en particulier:

/
/bin
/sbin
/etc
/usr
/lib
/boot

En plus, tout ce reste en question représente assez bien le système (l'ensemble des programmes et de la configuration) pour une bonne partie de mes serveurs (hyperviseur inclus), et je serai bien content de pouvoir en faire un snap unique avant une grosse mise à jour des paquets.

Je me retrouve donc avec 3 voire 4 partitions par système (en comptant la partition SWAP), avec un découpage logique tout à fait cohérent pour faire des snaps: root, var, swap et srv.

Il reste juste un petit détail à régler...........

Debian en Read-only Root

Bah oui, un système avec /usr en lecture seule, c'est un peu trop facile, mais avec carrément / en lecture seule, on a un peu plus de trucs à gérer......

Premier point, /root, /var, /home et /tmp. On a déjà réglé ce problème par un /var dédié en lecture/écriture qui contient tout le reste et des liens symbolique depuis la racine.

Le second gros problème, c'est une partie du contenu de /etc. Et il y a déjà une page debian qui regroupe les questions relatives à un système avec / en lecture seule !

A partir de cette liste, que je pense être basée sur des versions plus anciennes de Debian, on peut déjà faire un peu de tri: la plupart des fichiers de configuration problématiques ne seront pas utilisés sur mes serveurs, d'autres sont déjà des liens tels qu'indiqués sur cette page, et une autre partie ne sont pas présents sur mes installations. Voyons ce qu'il reste:

courier imap

Sur la plupart de mes serveurs, il n'y aura pas d'IMAP. Sur le serveur de mail, la configuration sera normalement gérée via LDAP, donc pas besoin d'aller triturer dans /etc.

lvm

Les guests n'utiliseront pas eux même LVM, comme nous allons le voir un peu après. Pour le Dom0, il faudra remonter temporairement / en lecture/écriture pour certaines opérations LVM.

nologin

A tester !

resolv.conf

Mes serveurs auront une configuration DNS statique, donc un resolv.conf qui ne bougera pas, tout va bien.

passwd/shadow

Une fois les systèmes installés, soit les authentifications se feront via LDAP, soit les serveurs auront une configuration très très minimaliste de ce coté là (un compte d'administrateur qui se connecte en SSH par certificats puis qui passe en root), donc les rares changements de mots de passes nécessiteront de remonter temporairement / en lecture/écriture.

udev

Les fichiers indiqués sur la page Debian ne sont plus générés sur Stretch, donc pas de problèmes.

Test

Premier essai sur une VM sur l'ancien hyperviseur (quand je vous dit que tout est fait dans des conditions de sécurité optimales !). Après l'installation (partitions /, /var et swap), on déplace les répertoires nécessaires:

cd /
mv root /var/ && ln -s var/root
mv home /var/ && ln -s var/home
rm -rf /tmp && ln -s var/tmp  # /var/tmp existe déjà et doit avoir les mêmes permissions que /tmp

Notez l'absence de "/" pour le ln, comme je l'avais indiqué plus haut. Notez aussi que le "/" final après var pour les mv ne sert pas vraiment, c'est pour bien illustrer le fait qu'on déplace bien les répertoires à l'intérieur de /var.

Ensuite, on édite /etc/fstab:

UUID=[uuid de votre partition ROOT]    /       ext4    errors=remount-ro      0    1

devient:

UUID=[uuid de votre partition ROOT]    /       ext4    defaults,ro,noatime   0    1

(je ne suis pas certain que le "noatime" soit vraiment nécessaire)

Reboot, et ......... ça fonctionne ! Limite un peu trop facile, en fait........

Bien sur, pour se simplifier un peu la vie, l'édition de fstab aura lieu quand le système est complètement installé et configuré.

apt full-upgrade

Pour ne pas avoir à faire les manips à la main à chaque installation / mise à jour par apt, on va aller configurer un remontage à la volée, en créant /etc/apt/apt.conf.d/00RWRoot:

DPkg {
    // Auto re-mounting of a readonly /
    Pre-Invoke { "mount -o remount,rw /"; };
    Post-Invoke { "test ${NOAPTREMOUNT:-no} = yes || mount -o remount,ro / || true"; };
};

un joli prompt

Pour savoir d'un coup d'oeil si on a un / en lecture ou en écriture, il suffit de se créer un prompt sympa dans son .bash_profile:

function prompter(){
        if [ -z "$(findmnt / |egrep 'ext4\s+rw')" ];then
                # Read only
                PS1='\[\033[01;36m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
        else
                PS1='\[\033[01;31m\]\u@\[\033[01;36m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
        fi
}
export PROMPT_COMMAND=prompter

J'ai mis un peu de temps à trouver un test fiable pour déterminer si / est en lecture ou lecture/écriture (les tests simples à base de "grep ro" ne fonctionnent pas dans tous les cas). Je ne peux pas garantir que ce test là fonctionne en permanence, on pourrait l'améliorer en vérifiant qu'on a bien soit "ro" soit "rw" (et gérer un 3eme état "sais pas").

Ah, et la notion de joli est subjective, l'important, c'est le principe de prompt dynamique et la technique pour détecter un / en ro ou rw.......

Partition en lecture seule

Pour le Dom0, on en restera là, mais pour les VMs, j'aurais aimé pouvoir vraiment bloquer les accès en écriture au niveau de l'hyperviseur, et les débloquer juste quand j'en ai besoin. Une solution simple consiste à déclarer la partition en lecture seule dans la configuration Xen de la VM, sauf que du coup, "débloquer les accès en écriture" implique de rebooter la VM (je n'ai pas trouvé de commande xl pour faire le changement à la volée).

J'ai également trouvé une option "-p r" / "-p rw" de lvchange, qui permet de changer les permissions du lv. Après quelques essais rapides, je constate bien le changement dans lvdisplay, mais ma VM peut toujours écrire sur le lv (et le changement est bien enregistré). Soit il y a un bug, soit les permissions en question sont autre chose (j'ai trouvé très très peu de documentation sur ce point).

Au passage, si j'en étais resté avec des fichiers pour mes disques de VMs, j'aurais peut être pu facilement faire un chmod........

Master et Snap

LVM permet une autre possibilité intéressante: avoir une partition de référence (le master), et faire fonctionner la VM sur un snap (un dérivé, dans ce cas). Aucun changement sur le snap n'est durable: il suffit de re-générer le snap à partir du master. Redoutable pour nettoyer un serveur compromis, temps de maintenance record (tout peut être préparé en amont, d'un point de vue de la machine de production, c'est juste un reboot), mais assez complexe à gérer au quotidien. Les mises à jour doivent être faites sur le master (soit en démarrant une autre copie de la VM, soit directement depuis le Dom0 à coup de chroot, par exemple), et j'ai déjà vu passer des mises à jour qui font des manipulations sur les données (base de données, etc.....), ce qui complique le suivi.

Bref, l'idée est jolie, je ferai probablement un test un jour sur un serveur secondaire (ou un serveur critique et super statique), mais pour l'instant, je laisse l'idée de coté.

Installation de l'hyperviseur

Le plan de bataille est prêt, il ne reste plus qu'à l'appliquer, à commencer par le Dom0.

Raid miroir, LVM, ROOT et GPT sont dans un bateau.....

Comme je l'avais évoqué au dessus, le BIOS de mon serveur est un peu ancien. Il ne sait donc pas du tout ce que veut dire EFI ou GPT, et c'est déjà bien qu'il détecte mes disques ..... sauf qu'il est à peu près convaincu qu'ils font environ 1.4To chacun.......

Comme le noyau Linux va rapidement lui même tout re-détecter sans se préoccuper du BIOS, y compris les disques (avec leur bonne capacité !), j'ai donc juste besoin que le BIOS réussisse à faire démarrer le noyau. Ma première idée a donc été de réviser légèrement mon plan de partitionnement, avec un petit /boot en début de disque.
Après avoir perdu pas mal de temps à essayer de faire installer GRUB2, j'ai finalement réalisé que l'installeur Debian m'avait créé des tables de partition GPT sur mes disques (alors qu'il m'avait juste demandé si je voulais des tables de partitions, oui/non). Pour que GRUB puisse s'installer sur des disques avec de telles tables, vous devez commencer par créer, au début de chaque disque concerné (les 2 dans mon cas, donc), une petite partition (la littérature sur le net parle de 1Mo comme étant déjà confortable, j'en ai mis 2 pour être installé en VIP.....) de type "BIOS Boot Partition" (disponible dans la liste des types de partitions durant l'installation). Cette partition n'est pas en RAID miroir: GRUB n'ira pratiquement jamais la modifier (il y a juste une partie du code de GRUB, pas de configuration), et si jamais il le fait, nous allons de toutes façons lui indiquer par la suite qu'il doit s'installer sur les 2 disques.

Avec cette petite diversion, j'en ai un peu oublié de créer mon /boot dédié, et je m'en suis rendu compte assez tard dans l'avancement du nouveau serveur..... En pratique, mon installation démarre. D'après lvdisplay -m, ma partition dom0root est physiquement au début du disque et devrait y rester, en tout cas tant que je ne l'étends pas, donc l'image du noyau et de l'initrd devraient rester accessibles (tant que je n'étends pas dom0root, donc.......). Comme j'ai quand même pris un peu de marge sur la taille de cette partition et que je prévois vraiment de ne pas avoir grand chose du tout d'installé directement sur l'hyperviseur, je prends le risque de rester avec ce découpage. Si ça se trouve, GRUB est peut être assez intelligent pour savoir adresser l'ensemble des disques durs, même s'ils ont mal été détectés par le BIOS..... Si vous vous retrouvez vous aussi à jouer avec des disques partiellement reconnus par votre BIOS...... faites un /boot dédié !

Ce petit problème étant résolu, voici le résumé de ce qu'il faut faire pour préparer ses disques dans l'installeur Debian:

  • Création d'une table des partitions sur les 2 disques s'ils sont neufs. Créer si nécessaire la partition "BIOS Boot Partition", donc.....
  • En cas de nécessité, créer un tout petit RAID en tout début de disque pour /boot, donc......
  • Créer une partition qui occupe tout le reste de l'espace sur chaque disque (elles s'appelleront probablement /dev/sdaX et /dev/sdbX, avec X=3 si vous avez créé la partition BIOS Boot Partition et la partition raid /boot, X=2 si vous n'avez créé que l'une des deux partitions citées à l'instant, et X=1 si vous n'en avez créé aucune des deux), et indiquer comme utilisation "physical volume for RAID".
  • Configure software RAID: RAID1, 2 devices actives, 0 spare, sélection des 2 partitions créées au dessus (probablement /dev/sdaX et /dev/sdbX, donc).
  • Configurer cette partition RAID (probablement /dev/md0, ou /dev/md1 si vous avez créé un /boot aussi en RAID1) comme "use as physical volume for LVM"
  • Configure LVM:
    • Create volume group (que nous appellerons vg0 dans le reste du billet, mais vous pouvez nommer le votre toto89324xfza si ça vous fait plaisir......), sélection de la partition md créée juste au dessus.
    • Create logical volume sur vg0 pour root, var et swap. Pour plus de lisibilité, j'ai nommé les miens dom0root, dom0var et dom0_swap, mais là aussi, vous pouvez mettre les noms arbitraires de votre choix....
  • Retour au menu de partitions, affectation des lv sur /, /var et swap
  • Pendant la conf de grub, choisir l'installation sur le secteur de démarrage, et indiquer le premier disque (sda)
  • après le reboot, dpkg-reconfigure grub-pc (ou dpkg-reconfigure grub-efi-amd64 si vous bootez en EFI, d'après ce que j'ai lu sur certaines docs) et sélection de sda et sdb (pour des mises à jour futures de grub). Là, je suppose qu'il s'installe sur sdb sans commande supplémentaire, mais comme je l'avais fait durant l'installation, je n'ai pas pu vérifier !

Et c'est parti pour ...... environ 10h de synchro du miroir dans mon cas !

Avec lvm, on peut faire plus subtil: créer un premier md relativement petit (20Go suffisent très très largement pour une install minimaliste d'une Debian serveur), qui va se synchroniser très rapidement durant l'installation (alors que la synchro du mien a plombé très sévèrement la vitesse d'installation de base du système !), et plus tard, à un moment tranquille, on crée un ou plusieurs autres gros md qu'on ajoute à vg0..... En utilisant cette technique, on pourrait aussi forcer dom0_root à être dans md0, qui est au début du disque, ce qui serait une autre façon de s'assurer que l'image du noyau restera dans la partie du disque accessible via le BIOS.

Configuration du Dom0

Quitte à détailler l'installation, petit rappel express pour transformer une "install Debian" en "install Debian Dom0":

apt install xen-system-amd64

dpkg-divert --divert /etc/grub.d/08linuxxen --rename /etc/grub.d/20linuxxen

Edition de /etc/default/grub (dans l'exemple, je lui réserve 2Go de RAM, vous ajusterez selon votre cas, j'ai le vague souvenir qu'il ne faut pas lui mettre trop peu):

GRUBCMDLINEXEN="dom0_mem=2048M,max:2048M"

/etc/xen/xend-config.sxp

(dom0-min-mem 2048)
(enable-dom0-ballooning no)

Ces 2 changements disent en gros que le Dom0 aura 2Go de mémoire dédiée pour lui, mais qu'il ne touche pas au reste, c'est pour les potes....

On applique les changements de la configuration de grub:

update-grub

Un petit reboot et on est bon !

Création des disques d'une VM

lvcreate -n guest1_root -L 2G vg0 /dev/md0
lvcreate -n guest1_var -L 2G vg0 /dev/md0
lvcreate -n guest1_swap -L 1G vg0 /dev/md0

A la fin de la ligne, /dev/md1 sert à s'assurer que ces LVs seront bien sur md0 (chez moi, c'est md0, chez vous, ca sera donc peut être md1, voire autre chose), même si plus tard on rajoute d'autres disques, partitions RAID ou je ne sais quoi d'autre.
L'option -n permet d'indiquer le nom du lv.
L'option -L permet de spécifier la taille, ici 2Go pour les partitions root et var (ce qui sera largement suffisant pour la plupart de mes installs serveurs, et il sera toujours temps d'agrandir les lv plus tard si besoin), et 1Go pour le swap.
Et vg0 pour indiquer le vg sur lequel on souhaite créer tout ça (oui, on pourrait avoir plusieurs vgs, et même que ça pourrait être pertinent dans certains cas).

Le fichier de conf Xen de la VM va donc contenir (j'ai viré ce qui ne concerne pas directement les disques):

boot = "pygrub"

# Uniquement pour le boot d'install depuis l'ISO
bootloader_args = [ 
         "--kernel=/install.amd/xen/vmlinuz",
         "--ramdisk=/install.amd/xen/initrd.gz"
 ]
 
disk = [
 # uniquement pour l'install
 'file:/home/Xen/debian-9.3.0-amd64-netinst.iso,hdc:cdrom,r',

 'phy:/dev/vg0/guest1_root,xvda1,rw',
 'phy:/dev/vg0/guest1_var,xvda2,rw',
 'phy:/dev/vg0/guest1_swap,xvda3,rw',
 ]

Et ca ne fonctionne pas: l'installer Debian voit bien qu'il s'agit de partitions, mais ne comprend pas leur type, et veut désespérément leur générer un MBR comme s'il s'agissait de disques....

Il faut donc d'abord créer les filesystem depuis le dom0:

mkfs.ext4 /dev/vg0/guest1_root
mkfs.ext4 /dev/vg0/guest1_var
mkswap /dev/vg0/guest1_swap

puis recommencer l'installation (ou me croire sur parole, et exécuter ces commandes avant la première installation.......).

Avec cette configuration, grub ne s'installe pas. A vrai dire, ce n'est pas très grave, vu qu'il n'est pas directement utilisé (c'est pygrub qui fait le boulot depuis le Dom0). J'ai trouvé chez Debian une solution, qui consiste à créer le fichier /boot/grub/menu.lst suivant dans la VM:

default         0
timeout         0

title           Debian GNU/Linux
root            (hd0,0)
kernel          /vmlinuz root=/dev/xvda1 ro
initrd          /initrd.img

Soit vous montez la partition depuis le Dom0 après l'installation, soit, à la fin de l'installation, vous le créez dans un shell:

mkdir /target/boot/grub
cat >/target/boot/grub/menu.lst

(et vous faites un copier/coller du fichier juste au dessus).

Comme le but est de filer les infos à pygrub, on peut aussi s'en sortir avec les paramètres root et bootloader_args du fichier de configuration de la VM:

bootloader_args = [ "--kernel=/vmlinuz", "--ramdisk=/initrd.img" ]
root        = '/dev/xvda1 ro'

Une VM en img vers LVM

Pour ma VM de firewall, la méthode décrite au dessus ne fonctionne pas: je dispose d'une image de VM au format OVA toute prête, et je ne peux pas faire ce que je veux au niveau des partitions, donc je vais utiliser un lv pour stocker directement cette image:

root@dom0:~ $ tar xf firewall.ova
root@dom0:~ $ ls *.vmdk
firewall.vmdk
root@dom0:~ $ qemu-img convert -O raw firewall.vmdk firewall.img
root@dom0:~ $ qemu-img info firewall.img
image: firewall.img
file format: raw
virtual size: 10G (10738466816 bytes)
disk size: 843M

Nous disposons maintenant d'une image RAW de la VM (bien sur, selon votre format d'origine, vous adapterez les premières étapes), et de sa taille exacte à l'octet près. Il ne reste plus qu'à créer un lv de cette taille (notez bien le b à la fin de la taille, par défaut lvcreate considère une autre unité) et à copier l'image:

root@dom0:~ $ lvcreate -n firewall_root -L10738466816b /dev/vg0
root@dom0:~ $ dd if=firewall.img of=/dev/vg0/firewall_root bs=1M

Et voilà !

Opérations de maintenance

Remplacement de disque physique

Ca fait pas mal de temps que je n'ai pas eu besoin de remplacer un disque (mais j'ai déjà eu plusieurs fois à le faire depuis que je stocke mes données en RAID1.....), du coup, pour l'instant, je consigne ici ce que j'ai trouvé comme infos sur le net.

Dites "33"

Première chose à faire, repérer le disque qui pose problème:

root@dom0:~$ cat /proc/mdstat 
Personalities : [raid1]
md0 : active raid1 sdb2[0]
     7813892096 blocks super 1.2 [2/1] [U_]
     bitmap: 0/59 pages [0KB], 65536KB chunk

Dans ce cas, c'est sda qui serait défaillant, nous partirons de ce principe pour tout le reste de l'exemple.

Attention chérie, ca va couper....

Maintenant qu'on connaît le disque fautif, nous allons confirmer à mdadm qu'il doit le considérer comme tel:

root@dom0:~$ mdadm --manage /dev/md0 --fail /dev/sda2

Dans mon cas, je n'ai que md0, et il est constitué de sda2 et sdb2. Si vous avez plusieurs md qui utilisent le disque défectueux, l'opération est à recommencer pour chaque partition. Les opérations suivantes aussi.....

Il est maintenant possible de l'enlever du RAID:

root@dom0:~$ mdadm --manage /dev/md0 --remove /dev/sda2

Une fois le disque complètement sorti du RAID, vous pouvez éteindre le serveur, remplacer le disque par un nouveau, et redémarrer (si votre configuration est bonne, vous saurez booter sur le disque encore actif du RAID, ce qui nécessitera peut être quelques manips au niveau du BIOS/EFI dans le cas où c'est justement sda qui a laché).

Le nouveau disque

Commencez par vérifier que vos disques ont toujours les mêmes noms ! Il semblerait que, dans certains cas, votre ancien sdb puisse être votre nouveau sda, et ca serait vraiment vraiment une mauvaise idée d'appliquer les manipulations ci après à l'envers !! Supposons cependant que l'ancien sdb soit toujours sdb, et que le disque tout neuf ait pris le nom de celui qu'il remplace: sda.

Première étape, il faut partitionner le nouveau disque. Plusieurs options sont possibles, y compris une construction manuelle de la table des partitions, mais si votre nouveau disque fait exactement la même taille que l'ancien, le plus rapide est de faire:

root@dom0:~$ sfdisk -d /dev/sdb | sfdisk /dev/sda

Nous pouvons maintenant ajouter la nouvelle partition au RAID1:

root@dom0:~$ mdadm --manage /dev/md0 --add /dev/sda2

A partir d ce moment, vous pouvez aller vérifier que le md a commencé à se synchroniser en faisant un cat /proc/mdstat. Et vous pouvez du coup aussi vous installer pour quelques heures d'angoisse, en attendant que la synchro soit finie (donc que toutes vos données soient à nouveau à l'abri sur 2 disques......).

Grub

Et il faudra aussi installer Grub sur le nouveau disque. A priori, ca devrait se faire via la commande suivante:

root@dom0:~$ grub-install /dev/sda

J'espère qu'il se passera plusieurs années avant que je ne puisse venir éditer ce billet et confirmer que cette séquence fonctionne.........

Extension de la taille d'une partition de VM

Déjà, on va vérifier qu'il reste de la place dans le vg:

root@dom0:~$ vgdisplay /dev/vg0
[.......]
    Free  PE / Size        xxxxx / Place libre

Ok, disons qu'il reste de la place, ajoutons donc 2Go sur la partition root de guest1:

root@dom0:~$ xl shutdown guest1
Shutting down domain X
Waiting for 1 domains
Domain X has been shut down, reason code 0
root@dom0:~$ lvresize -r -L +2G /dev/vg0/guest1_root
[.......]
root@dom0:~$ xl create /etc/xen/guest/guest1.xl

Et ...... c'est ....... tout !!!!! (c'est le -r qui dit à lvresize de faire faire dans la foulée le boulot de redimensionnement du filesystem en dessous). Enfin, presque: si vous avez le / de votre dom0 en lecture seule, il faut le repasser en lecture/écriture pour le lvresize, ce qui donne au final les commandes suivantes:

root@dom0:~$ xl shutdown -w guest1 && mount -o remount -w / && lvresize -r -L +2G /dev/vg0/guest1_root && mount -o remount -r / && xl create /etc/xen/guest/guest1.xl

Avec une bonne politique de nommage des volumes logiques, ca doit même pouvoir se scripter très facilement, en fait....... d'un autre coté, si le script est rentable, c'est que vous passez votre temps à redimensionner vos partitions, et vous pouvez vous poser d'autres questions, dans ce cas !

Snapshot d'une VM

Un snapshot LVM ne fait pas une véritable copie complète du lv d'origine: il retient son état au moment du snapshot, puis va uniquement stocker les changements (enfin, les anciennes versions avant changement, pour être précis). Nous allons donc allouer au snapshot une taille nettement inférieure à celle du volume d'origine s'il s'agit juste de faire une sauvegarde avant un changement de configuration un peu risqué. Par contre, s'il s'agit d'une sauvegarde juste avant une très très grosse mise à jour de version, par exemple, il faudra prévoir une taille assez conséquente pour le snapshot !

Créons donc un snapshot d'1Go de la partition root de guest1 (la VM est éteinte depuis quelques secondes, je ne sais pas trop s'il est possible/fiable de faire un snap d'une VM en cours de fonctionnement):

root@dom0:~$ lvcreate -L1G -s -n guest1_root_snap1 /dev/vg0/guest1_root

Et voilà..... l'opération ne prend que quelques secondes, on peut redémarrer guest1 de suite.

Si vous voulez faire une bonne vieille sauvegarde à l'ancienne de votre partition, dans un bon vieux fichier que vous pouvez stocker ailleurs, vous pouvez le faire sur le snap pendant que la VM a été redémarrée:

root@dom0:~$ dd if=/dev/vg0/guest1_root_snap1 bs=1M | bzip2 -9 > guest1_root_save.raw.bz2

Ce fichier sera restaurable sur le lv d'origine (VM stoppée) avec la commande suivante:

root@dom0:~$ bunzip2 -c guest1_root_save.raw.bz2 | dd of=/dev/vg0/guest1_root bs=1M

Dans les 2 cas, vous pouvez jouer avec l'option bs de dd (block size), d'autres valeurs pourraient vous permettre de gagner pas mal de temps sur l'opération (ou d'en perdre pas mal.....).

Quand votre snapshot ne sert plus à rien, il est détruit comme n'importe quel autre lv:

root@dom0:~$ lvremove /dev/vg0/guest1_root_snap1

Si par contre vous avez bien fait de faire un snap et que vous avez besoin de le restaurer, l'opération est un peu plus longue, et se fait via la commande suivante (avec la VM stoppée):

root@dom0:~$ lvconvert --merge /dev/vg0/guest1_root_snap1

N'apparaissent pas au générique de fin.......

Inévitablement, j'ai fait des choix à certains moments. Quelques uns méritent un peu plus d'explications.

LVM dans du LVM

Au début, on m'avait proposé l'idée de faire une partition LVM au niveau de l'hyperviseur pour chaque guest, et de considérer au niveau du guest cet ensemble comme un disque, donc de faire des partitions dessus, et tant qu'à faire en LVM aussi. Le fait d'empiler 2 niveaux de LVM semblait ne pas poser de gros problèmes de performances, mais c'est quand j'ai regardé comment étendre les partitions des VMs que j'ai décidé de renoncer à cette solution. En gros, il fallait étendre le lv coté hyperviseur (jusque là, ca me parait prévisible......), démarrer la VM, créer une partition sur l'espace supplémentaire désormais disponible, rajouter cette partition au vg du guest, et seulement après on pouvait à nouveau faire un lvextend au niveau du guest...... Pour les éventuelles réductions de taille de partition, même si ca ne devrait pas m'arriver en pratique, je n'ai même pas osé trop réfléchir à la séquence de trucs à faire !

Sur ce point, il semble cependant y avoir une alternative qui réduit un peu les manipulations: au niveau de la VM, ne pas faire de partition, mais embarquer l'ensemble du disque comme un PV, ce qui permettrait semble-t-il de redimensionner directement le vg dessus avec un pvresize après avoir étendu le lv au niveau du Dom0 (si vous avez compris cette phrase du premier coup, ne prenez pas le volant !). Notez que je n'ai pas testé, et qu'il s'agit donc d'une solution théorique à ma connaissance.


Dans tous les cas, ca m'aurait aussi un peu compliqué la vie au moment de faire des snaps: soit j'aurais du faire des snaps complets des VMs (root + var + swap + srv) au niveau de l'hyperviseur, soit ...... euh ..... j'aurais du réfléchir à comment balancer de l'espace en plus à la VM pour qu'elle fasse elle même son snap.

xen-tools

Plusieurs tutoriels font de la création de VMs avec xen-tools. Vu que je ne prévois pas de faire régulièrement de création de nouvelles VMs, je n'ai pas trop cherché à utiliser cette solution.

debootstrap

Il est également possible de créer une VM Debian sans la démarrer via debootstrap. Un peu comme pour les xen-tools, j'ai été plus vite à court terme en créant mes VMs en mode "classique", mais je m'intéresserai clairement à cette possibilité si je me rends compte que j'ai à fabriquer plus ou moins régulièrement de nouvelles VMs à l'avenir.

Thin provisioning

LVM supporte (depuis peu ?) le principe de thin provisioning. En gros, c'est la même idée que le surbooking, mais pour l'espace disque..... Je suppose que c'est assez pratique dans certains cas, mais dans le mien, je n'aime pas trop l'idée de vivre à crédit sur mon espace disque, surtout que je n'en ai pas besoin.

RAID1 via LVM

LVM gère depuis pas mal de temps une option "mirror". Pour le peu que j'en ai lu sur le net, l'objectif de ces mirrors est différent, et nettement moins pertinent que mdadm pour gérer un RAID1 de disques.


Depuis beaucoup moins de temps, LVM gère également un mode RAID1 (entre autres), qui semble lui très très proche d'un point de vue fonctionnel du RAID1 avec mdadm. J'ai repéré cette possibilité alors que j'avais déjà pas mal avancé sur mon installation, et le coté un peu nouveau de la solution a aussi contribué à ce que je laisse cette idée de coté.

Pour une version ultérieure, la question se posera peut être, mais j'avoue que pour l'instant, j'aime bien l'idée d'avoir rapidement une vue purement RAID d'un coté, et une gestion d'un seul "truc" (le md0) de l'autre.

FreeNAS

Pour le NAS, à un moment, je me suis posé la question d'utiliser FreeNAS, son fork Nas4Free ou une autre distrib dédiée. Au début, je n'avais pas mes 48Go de RAM, donc j'avais laissé tomber.
En plus, dans mon cas d'usage, les avantages de ZFS ne sont pas flagrants, mon install de VM NFS finale est vraiment réduite au strict nécessaire, et je n'ai pas creusé l'empilage LVM/ZFS.

Des trucs annexes....

Là, on sort des problématiques d'organisation de disques, mais si ces infos peuvent servir à des gens plus tard (dont potentiellement moi), alors tant mieux !

Ordre d'arrêt des VMs

Par défaut, lors de l'arrêt de l'hyperviseur, le script /etc/init.d/xendomains est appelé, et stoppe les VMs dans l'ordre inverse de leur création. Tant que vos VMs ne rebootent pas, l'idée peut sembler pertinente......

Une solution absolument propre consisterait à connaître un graphe de dépendance des VMs, et à stopper par lots en fonction de ces dépendances. Par exemple:

  • je stoppe le firewall HA passif en tache de fond
  • je stoppe tous les clients (qui dépendent du firewall, du NFS, du DNS, etc....)
  • puis je stoppe les serveurs publics
  • puis je stoppe les serveurs privés
  • puis je stoppe le firewall
  • puis je reboote.

Bien évidemment, dans ce monde parfait, les dépendances sont listées dans les fichiers de configuration.....

A défaut de ce monde parfait, voyons quelles solutions nous pouvons mettre en place.

Intercepter l'ordre de la liste

L'idée est de disposer de notre propre script, que j'ai dans mon cas placé en tant que /usr/lib/xen-common/bin/xen-list-domains (pensez à lui donner les droits d'exécution, bien sur !).

Nous allons alors détourner l'appel dans xendomains pour qu'il obtienne sa liste (et donc l'ordre) de notre script:

--- /etc/init.d/xendomains.old
+++ /etc/init.d/xendomains
 -185,12 +185,12  dostopshutdown()
     logactionbegin_msg "Shutting down Xen domain $name ($id)"
     xen shutdown $id 2>&1 1>/dev/null
     logactionend_msg $?
-  done < <(/usr/lib/xen-common/bin/xen-init-list)
+  done < <(/usr/lib/xen-common/bin/xen-list-domains)
   while read id name rest; do
     logactionbegin_msg "Waiting for Xen domain $name ($id) to shut down"
     timeoutdomain "$name" "$XENDOMAINSSTOP_MAXWAIT"
     logactionend_msg $?
-  done < <(/usr/lib/xen-common/bin/xen-init-list)
+  done < <(/usr/lib/xen-common/bin/xen-list-domains)
 }
 
 do_stop()

Nous avons toujours un arrêt en masse des VMs, mais au moins dans l'ordre qui nous arrange..... un xen shutdown -w serait potentiellement dangereux, vu qu'il n'existe apparemment pas d'option pour dire "attends x secondes maximum".

Il ne nous reste plus qu'à voir ce qui est faisable avec xen-list-domains

Nommage des VMs

Ma première tentative a été de mettre l'ordre en tant que nom des VMs: 01-firewall, 55-client, 99-firewall-slave, etc....

Du coup, xen-list-domains est assez simple:

#!/bin/bash

while read name id mem cpu state time ; do
        if [ "$name" != "Name" -a "$name" != "Domain-0" ];then
                echo "$id $name"
        fi
done < <(xl list|sort -r)

Cette approche présente 2 inconvénients:

  • Ca devient vite lourd de devoir se souvenir des numéros de priorité des VMs, qui sont du coup leurs noms aussi.....
  • Le script ne fonctionne pas au reboot.... Apparemment, xen-init-list se comporte un peu différemment pendant le reboot.

Script autonome

Du coup, j'ai fait une 2eme tentative en appelant xen-init-list, mais je ne peux plus trier directement via sort (on doit pouvoir le faire via awk ou un truc du genre, mais je ne suis pas expert). Donc je me suis retrouvé à faire mon nouveau xen-list-domains en perl (là, je sais faire). Et quitte à le faire en perl, je peux embarquer les priorités dans le script:

#!/usr/bin/perl

my %vmorder = (
    "firewall" => 99,
    "firewall-slave" => 0,
    "guest1" => 2,
    "srvnfs" => 98,
    "guest2" => 3,
    "srvlan" => 97,
    "srvpub" => 80,
    "machin" => 40,
    );

my %vmreal;

open(LIST, "/usr/lib/xen-common/bin/xen-init-list|");
while($line=<LIST>){
    my $order;
    my ($id, $name, @rest) = split(/ /, $line);
    chomp $name;
    if(defined($vmorder{$name})){
         $vmreal{$line}=$vmorder{$name};
    }else{
        $vmreal{$line}=1;
    }
}
close(LIST);

foreach (sort { $vmreal{$a} <=> $vmreal{$b} } keys %vmreal) {
   print ;
}

Les VMs non connues ont un poids de 1. Une VM qui a un poids de 0 explicite recevra son signal d'arrêt en premier, puis les VMs non connues, puis les autres connues dans l'ordre de poids (donc dans mon cas, 99 sera la dernière à recevoir son signal).

Dans une version ultérieure, je pourrais stocker les priorités ailleurs (dans les fichers de conf des VMs ?), voire les calculer à partir de dépendances. Vu que mes VMs et leurs dépendances sont assez statiques, je n'ai pas passé plus de temps sur cette partie.


En pratique, quand j'ai besoin de redémarrer l'hyperviseur, je préfère stopper manuellement toutes les VMs, et il est important de faire ca dans un screen, tmux, ou toute autre solution qui permettra que les dernières commandes soient effectivement lancées même quand votre terminal se fera couper le ssh sous le cable !

lundi, avril 2 2018

Déployer un firewall en VM sous Xen pour ma soeur II, le retour

Il y a une éternité très très longtemps, dans une galaxie très lointaine quelques temps, nous avions vu comment déployer un firewall en VM sous XEN. Aujourd'hui, nous allons faire la même chose, mais en mieux.......

Y'a quelqu'un ?

Si vous êtes particulièrement attentifs, vous aurez peut être remarqué que le dernier billet sur ce blog datait d'il y a environ 4 ans (on ne va pas pinailler pour quelques semaines). Et maintenant que je viens de le dire clairement, même si vous avez le niveau d'attention d'un chaton, il faudra au moins quelques bonnes secondes et une baballe qui passe pour vous faire oublier ce fait..... Vous me croyez si vous voulez, mais il semblerait que des gens (je veux dire, de vrais gens, pas seulement des bots) viennent encore sur ce blog de temps en temps. Ne me demandez pas pourquoi, je n'en ai aucune idée, mais les logs sont formels !

Et comme ces billets techniques me servent avant tout de notes pour le jour où je devrai moi même tout refaire en urgence, ca ne change de toutes façons pas grand chose à ma décision de dépoussiérer un peu et faire de nouveaux billets..... et oui, je viens de parler de nouveaux billets au pluriel.......

Ah, et maintenant que je vais cliquer sur "publier", je me dis que, si je l'avais finalisé un poil plus vite, ça aurait pu être une bonne blague !

Disclaimer

Ma soeur a beau avoir fait quelques progrès avec son mac (et je parle bien ici d'un ordinateur), ce billet contient quand même plein de morceaux de trucs techniques plus ou moins dégoulinants qui traînent un peu partout (mais a priori, ni OGMs ni aucune trace de noix). En particulier, il est très nettement préférable d'avoir déjà fait un plan à trois avec une Debian et un Xen au moins une fois dans sa vie.....
Si vous êtes expert pour dézinguer tous les aliens au pied de biche sur Xen, c'est très bien, mais ca ne vous sera d'aucune utilité ici.

Comme toujours sur ce blog, les actions décrites ci après ont été réalisées par un Vieux Con de Hacker Old School (tm) professionnel, dans des conditions de sécurité optimales, et avec un safeword pour rebasculer sur l'ancien hyperviseur+firewall en cas de problème.

Et aucun animal mignon n'a été blessé, tué ou mangé pendant l'intervention.

Et donc, le même truc en mieux, c'est quoi ?

Oui, c'est bon, j'y arrive, on dirait que certains n'ont pas appris la patience avec le temps !!!

L'idée de base reste la même: une machine physique, une Debian qui fait tourner un hyperviseur Xen (d'où les prérequis......), et parmi les VMs, un firewall (dans mon cas, toujours un Stormshield SNS, bien évidemment, mais je suppose que le principe fonctionnera aussi avec des distribs spécialisées comme pfSense ou une autre distrib firewall|) qui doit pouvoir gérer des interfaces physiques comme s'il était un firewall physique avec ces interfaces, ainsi que d'autres interfaces purement virtuelles pour les autres VMs. Un truc dans cette idée là: (oui, c'est exactement le même schéma que pour le billet précédent, maintenant que vous l'avez remarqué, vous savez où vous pouvez aller lire les explications un peu plus détaillées..........)

Mais en mieux..............

Premier point, tout utilise des versions plus récentes ! En 4-5 ans, vous imaginez ??? Rien que chez Debian, par exemple, bah il y a ....... 2 versions d'écart...... ok, c'est pas forcément le meilleur exemple.......
La version de Xen, par contre, fait un assez grand saut, puisqu'on était à l'époque en 4.1, et que nous allons cette fois-ci utiliser la 4.9 embarquée dans Stretch. Et là on commence à avoir des différences sérieuses: nous allons enfin utiliser le toolstack xl de Xen, et profiter de toutes les améliorations de performances et de support de fonctionnalités qui ont été faites pendant tout ce temps.

Ensuite, j'ai une nouvelle machine physique pour l'hyperviseur. Entre autres, la façade réseau de l'ancienne ressemble à ça:


alors que la façade de la nouvelle ressemble à ça:

Ne vous méprenez pas, ce n'est ici pas la taille qui compte (les 2 photos ne sont pas à la même échelle, déjà....), mais bien le nombre de trous d'interfaces réseau physiques, à savoir 24 sur le nouvel hyperviseur...... et on va vouloir s'en servir (éventuellement avec un peu de mauvaise foi si nécessaire, comme le nombre de RJ 45 réellement branchés sur la photo le laisse deviner......):

  • Le bloc de 8 interfaces à droite sera considéré comme un simple switch pour le firewall.
  • Les 4 interfaces avec des RJ45 bleus du milieu seront configurées en LACP. Note pour ma soeur: il n'est pas nécessaire d'avoir des RJ45 bleus pour faire du LACP, mais on s'y retrouve un peu plus facilement si on utilise des câbles de couleur différente......
  • Ce LACP sera en bridge avec les 4 interfaces au dessus, pour avoir au final un gros switch dont la moitié en LACP.
  • Les interfaces du bloc de gauche vont être groupées par paires (celle du dessus, et celle du dessous).

(j'expliquerai l'intérêt de chaque configuration au moment de la mettre en œuvre).

Jusqu'à récemment, je n'arrivais pas à démarrer une VM avec plus de 8 interfaces ethernet déclarées au total, et ma VM de firewall n'en gérait pas plus de 10. J'étais donc obligé de gérer toutes ces configurations directement sur l'hyperviseur.
Aujourd'hui, ces 2 limitations semblent avoir disparues, ou au moins avoir été repoussées: j'ai désormais une configuration fonctionnelle avec 20 interfaces pour la VM du firewall. Cependant, il me parait plus pertinent de gérer les topologies réseau au plus près des connecteurs, tant d'un point de vue performances que pour la simplicité de la configuration: coté hyperviseur, on gère le câblage, et coté firewall on s'occupe du découpage logique du réseau, de la sécurité, etc.... Mais libre à vous de prendre uniquement la version simple de la configuration que nous allons voir, de relier chaque interface au firewall et de gérer les topologies complexes directement sur le firewall.

Autres solutions possibles

Eh oui, ce point n'a pas changé en 4 ans, il y a toujours d'autres solutions possibles ! Au delà du fait que, si j'avais choisi d'autres solutions, je me serais quand même retrouvé à écrire ce paragraphe, passons en revue les alternatives qui méritent un peu plus de détails

Xen ?

Pour mon firewall, je ne pouvais pas partir sur les technos de containers logiciels genre LXC ou Docker, il me fallait un vrai hyperviseur. Du coup, Xen fonctionne bien, je connais et j'ai l'habitude, il est officiellement supporté par les VMs SNS, et je n'ai toujours pas trouvé d'autre hyperviseur qui justifie le changement depuis le temps. Pour être tout à fait exact, je n'ai pas plus que ça cherché non plus.......

PCI Passthrough

On pourrait toujours passer les interfaces réseau directement à la VM du firewall, la techno semble plus mature qu'à l'époque. Cela ne permet pas de brancher entre elles des VMs. Dans mon cas, le firewall n'embarque pas les pilotes nécessaires, donc la solution ne fonctionnerait pas. Et le fait de séparer le câblage de la configuration réseau pure me permet d'avoir une vue plus simple coté firewall.

MACVLAN / MACVTAP

Je n'ai vu ces technologies que très rapidement, elles semblent être des alternatives potentielles pour le cas des interfaces physiques. Pour le peu que j'en ai vu, ce type d'interfaces n'est pas adapté pour raccorder d'autres VMs à la VM du firewall (si tu as des informations pertinentes qui contredisent cette affirmation, ça m'intéresse !).

OpenVSwitch

Cette fois ci, j'ai vraiment essayé OpenVSwitch.... une version intermédiaire de ce billet expliquait même que j'utilisais au final cette solution en production.... et puis je me suis retrouvé à essayer d'avoir des bridges openvswitch fonctionnels et configurés assez tôt au démarrage de mon hyperviseur pour pouvoir démarrer la VM firewall automatiquement...... et je suis revenu aux bridges classiques.....

Du coup, j'ai assez avancé dans mes tests, et il n'y a finalement pas beaucoup de différences au niveau de la configuration:

  • il faut installer le paquet openvswitch-switch au lieu d'installer bridge-utils......
  • il faut modifier /etc/xen/xl.conf pour lui dire d'utiliser le script vif-openvswitch par défaut (ou forcer ce script pour chaque interface de chaque VM, si vous aimez vous compliquer les fichiers de configuration pour pas grand chose......)
  • la configuration des interfaces est un peu différente...... je vous aurais bien résumé comment on configure un bridge avec openvswitch, mais je viens un peu de vous dire que je n'ai pas réussi à avoir une configuration vraiment fonctionnelle en prod........
  • on a les mêmes problématiques d'isolation des interfaces
  • apparemment, on ne peut pas faire de tcpdump directement sur l'interface du bridge (mais j'ai repéré ce point peu de temps avant de rebasculer sur les bridges, donc l'info n'est peut être pas fiable......).

Installation de base

Dans le billet précédent, je n'avais pas du tout détaillé l'installation de base de l'hyperviseur, ni le fait de déployer une VM classique. Cette fois ci, il y a assez de subtilités techniques dans mon cas pour non seulement expliquer certains aspects de mon déploiement, mais en plus pour les expliquer dans un billet dédié.

Mais si vous avez fait une installation classique en suivant les consignes avec un hyperviseur Xen dessus (pas la peine de suivre toute la partie configuration réseau), c'est bien aussi.

C'est bon ? Ok, nous pouvons donc passer aux choses sérieuses !

Configuration réseau du Dom0

Y'a pas, j'ai beau retourner le billet dans tous les sens, on en revient à la configuration réseau à un moment, vu que c'est un peu Ze point technique qui fait que je m'[CENSURE] à rédiger ce billet......

Renommage des interfaces

Avec la nouvelle version de Debian, je me retrouve avec des noms d'interfaces modernes, prédictibles et poétiques comme "ens160" ou "enp11s0f1"...... ok, c'est stable même si on joue au bonneteau avec les interfaces réseau, mais pour s'y retrouver quand on est connecté en ssh sur le serveur pour vérifier un truc sur le réseau, c'est ....... enfin, c'est pas ....... disons que je ne suis pas hyper fan.....

Depuis quelques années sur d'autres installations, j'étais habitué à modifier le quasi magique /etc/udev/rules.d/70-persistent-net.rules...... Sauf que sur mon installation toute fraiche de Debian Stretch, il n'existe plus par défaut, le script tout aussi magique /lib/udev/write_net_rules qui est censé le fabriquer n'existe plus non plus, et les explications à propos de fichiers /etc/systemd/network/xx-nom.link ne fonctionnent pas non plus (je n'ai pas trop creusé pourquoi).

La seule solution qui fonctionne dans mon cas a donc été de recréer manuellement un /etc/udev/rules.d/76-netnames.rules (le numéro et le nom doivent venir du nième lien sur lequel j'ai cliqué pendant mes essais et reboots.......):

SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="ma:ca:dd:re:ss", NAME="ethlan1"

Vous noterez le nom: autant je ne suis donc vraiment pas fan des nommages prédictibles mais pas pratiques du tout, autant nous allons avoir ici pas mal d'interfaces réseau différentes, du coup, quitte à les renommer, autant utiliser des noms qui me permettront par la suite de facilement m'y retrouver, quitte à avoir des noms un peu plus longs. Donc toutes mes interfaces auront un nom du genre "type" "fonction" "numéro optionnel". Donc là, c'est une interface ethernet (eth), que j'utiliserai pour mon LAN, et je prévois d'en avoir au moins 2, donc je lui colle le numéro 1.
J'utiliserai cette convention dans tout le reste du billet sans forcément détailler à chaque fois.

Un cycle complet sera une série de 100 La ligne est bien sur à reproduire et à ajuster autant de fois que d'interfaces à renommer. Les adresses MAC sont à priori les identifiants les plus fiables pour correctement repérer les interfaces réseau, jusqu'au jour où on en rajoutera une, bien sur !

Voilà, il ne reste donc plus qu'à brancher un RJ45 sur chaque prise, repérer quelle interface a été détectée comme ayant un link up, noter son adresse MAC et mettre à jour dans le fichier......... Un petit reboot plus tard et j'ai plein de jolies interfaces avec des noms stables ET compréhensibles pour moi !

Un peu de config pour améliorer le débit

Fût un temps où je vous aurais donné la conf en 24,359 fois (et la 17eme vous aurait vraiment étonné !) et vraiment vraiment en kit. Sans aller jusqu'à dire qu'il vous suffit ici de copier/coller pour que tout fonctionne, je vais de suite poser une info simple: on va passer le MTU à 9000 partout, vu que le support des Jumbo Frames est désormais annoncé comme fonctionnel dans Xen. Avec un test simple (iperf) entre 2 VMs qui traversent le firewall (configuré en mode "laisse passer, cherche pas à analyser"), le simple fait de passer le mtu à 9000 (sur l'ensemble du chemin !!!) permet d'avoir plus ou moins x2 en performances.....

Voilà, voilà....... et à la fin, le Titanic coule.....

/etc/network/interfaces.d

Sur une ancienne version de ma configuration, je me suis retrouvé avec un /etc/network/interfaces difficile à maintenir. Et sur la nouvelle, le nombre total d'interfaces est plus élevé...

Du coup, pour cette nouvelle configuration toute vierge et encore un peu naïve, j'ai décidé de faire propre en utilisant quelques possibilités de ce fichier de configuration:

D'abord, nous allons utiliser les fonctions d'héritage de configuration. Pour prendre un exemple simple qui ne spoile pas trop la suite (parce que c'est pas du tout mon genre, de spoiler la suite):

iface ethtemplate inet static
       mtu 9000

iface ethlan1 inet manual inherits ethtemplate

Isolé, cet exemple ne sert pas à grand chose. Mais en rajoutant quelques autres options de configuration au template, et en se souvenant que nous allons avoir plus de 20 interfaces réseau qui vont utiliser ça, si un jour je veux modifier globalement le MTU de toutes mes interfaces (par exemple si je me rends compte à la fin que c'est encore un peu ambitieux d'utiliser les Jumbo Frames sous Xen ?), je serai content d'avoir utilisé un template !

Cet héritage a également grandement simplifié la version openvswitch de ma configuration, surtout quand j'ai découvert qu'il était possible de faire de l'héritage d'héritage. J'avais alors un truc dans le genre:

iface ovs_eth_template inet manual
            [de la conf très générique pour toutes mes ethernet]
 
iface bridgespecial_t template inet manual inherits ovs_eth_template
            ovs_bridge  bridgespecial

iface ethmembredubridge1 inet manual inherits bridgespecial_t
iface ethmembredubridge2 inet manual inherits bridgespecial_t
iface ethmembredubridge3 inet manual inherits bridgespecial_t
iface ethmembredubridge4 inet manual inherits bridgespecial_t

(parce que forcément, quand il y a une seule interface, c'est un peu moins pertinent.....).


Ensuite, presque tout sera réparti dans de petits fichiers posés dans /etc/network/interfaces.d, à part la configuration du loopback. Les configurations de templates auront des noms de fichiers qui commenceront par 00- (1 fichier par template), et ensuite les autres éléments de configuration seront groupés par bloc logique, également avec des préfixes numérotés pour s'assurer de l'ordre de configuration.


Enfin, plus pour faire joli qu'autre chose, et comme le nommage de mes interfaces me permettra de le faire, il y aura de temps en temps des utilisations d'expressions régulières.

Ca y est, on peut faire la conf réseau, maintenant ?

Oui, on peut, et même qu'on va le faire. Bien évidemment, chaque configuration réseau est unique, voyons donc un peu les différents cas de figures possibles, après libre à vous d'assembler comme il vous plaira.

My name is Bond.....

Ne vous plaignez pas du titre approximatif de cette section, avec un peu d'imagination vous pourrez trouver par vous même à quoi de pire vous avez échappé......

Le principe du bonding, également connu en tant que LACP ou IEEE_802.3ad, comme nous l'avons vu au dessus, est de regrouper plusieurs interfaces ethernet pour n'en faire qu'une seule virtuelle. Les avantages sont en particulier d'avoir un lien qui continue de fonctionner en cas de problème sur l'un des liens (câble coupé, interface défectueuse, etc.....), et pouvoir répartir (plus ou moins efficacement) la bande passante entre les interfaces. A ne pas confondre avec le principe du bondage, donc, qui parle de liens aussi, mais dont le but n'est pas le même !
La répartition de charge est cependant surtout efficace sur de multiples petites connexions de nombreuses sources/destinations différentes. Autant dire que dans mon cas, sur 4 liens, si j'arrive de temps en temps à en utiliser un peu 2, c'est déjà magnifique...... Je parle bien à nouveau du bonding.........

Mais nous n'allons pas nous arrêter pour si peux, et nous allons configurer une interface de type bond, dans /etc/network/interfaces/05-bondlink:

auto ethbond0 ethbond1 ethbond2 ethbond3 

auto bondlink

iface ethbond0 inet manual
iface ethbond1 inet manual
iface ethbond2 inet manual
iface ethbond3 inet manual

iface bondlink inet manual
   slaves ethbond0 ethbond1 ethbond2 ethbond3
   bond_mode 802.3ad
   lacp_rate fast
   bond_miimon 100
   bond_updelay 100
   xmi_hash_policy layer2+3
   mtu 9000

J'aurais bien aimé mettre une expression régulière pour slaves, vu que j'ai intelligemment nommé mes interfaces, mais je n'ai pas trouvé de syntaxe qui fonctionne (si quelqu'un a, ça m'intéresse !).

Les options suivantes sont là pour paramétrer plus précisément le mode d'agrégation de lien, vous pouvez les copier comme des sauvages ou aller lire la documentation pour déterminer quels paramètres vous voulez utiliser. Il est important d'utiliser les mêmes paramètres (en particulier la policy et le bond_mode) sur l'équipement en face !

Je n'ai pas installé le package "ifenslave" (et d'ailleurs, l'exécutable ifenslave est un script qui fait essentiellement passe-plat vers le binaire ip), et pourtant mon interface bondlink s'active correctement.

C'est peut être les soldes, mais j'ai aussi une interface bond0 qui traîne, alors qu'elle n'existe nulle part dans ma configuration (et le fait d'installer le package ifenslave n'y changera rien) !

Je n'utilise pas le template eth, qui sert pour l'instant seulement à fixer le MTU: c'est du coté de l'interface de bonding qu'il faut mettre le MTU à 9000, et c'est reporté automatiquement à toutes les interfaces.
Je n'ai pas créé non plus de template pour les interfaces de bonding, vu que je vais avoir une seule configuration de ce type sur mon serveur, mais si vous avez prévu d'en avoir plusieurs, vous pouvez bien évidemment regrouper la plupart des paramètres dans un template dédié.

D'après la doc, il y a d'autres possibilités de "bond_mode", pas forcément standardisées, mais qui pourrait être intéressantes si vous avez également un Linux en face (qui connaît ces modes, du coup).

Voilà, nous avons maintenant 4 interfaces agrégées en LACP..... sans IP, voyons tout de suite pourquoi.......

Bridges ethernet

Mais quelle transition magique !!!!
Chaque interface ou groupe d'interface (un LACP, par exemple.....) physique sera mise à disposition séparément de la VM firewall via un bridge (littéralement, un pont, donc) dédié. Les interfaces ou groupes n'auront donc jamais d'IP configurée: c'est ainsi que fonctionne une configuration réseau sous Linux, les IPs sont portées par les interfaces bridge (ce qui me parait assez logique, en fait.....).
La plupart des bridges n'auront pas non plus d'adresse IP pour autant: le Dom0 fait uniquement passe-plat (enfin, passe-paquets) entre des RJ45 (ou d'autres VMs) et les interfaces réseau du firewall. Voyons ca.....

Le cas de figure le plus basique est celui d'un bridge sans interfaces ethernet, qui est créé pour que des interfaces virtuelles (celles des VMs, donc) puissent plus tard y être raccrochées.
Par exemple, mon /etc/network/interfaces.d/80-brvpub (le bridge des VMs publiques) contient:

auto brvpub

iface brvpub inet manual inherits brtemplate
   bridge_ports none

La dernière ligne sert à bien confirmer que non, on est pas de gros boulets qui ont oublié la conf, oui on veut vraiment démarrer un bridge qui pour l'instant n'a aucune interface, merci: que ce soit le firewall ou les serveurs de l'autre coté, c'est dans la configuration des VMs que sera indiqué le fait de raccorder leurs interfaces virtuelle (vif) à ce bridge en particulier, pour une raison bien simple: ces interfaces n'existent pas encore au moment de la création du bridge !
Nous aurons le même cas dans tous les exemples ci-dessous, vous serez du coup bien aimables de revenir à chaque fois relire la ligne ci-dessus pour avoir l'explication, qui est la même.....

A peine plus complexe, un bridge qui relie (pour l'instant, donc) une seule interface ethernet. Voyons le contenu de /etc/network/interfaces.d/50-guest (une interface réseau dédiée chez moi aux équipements "invités", donc):

auto ethguest
auto brguest

iface ethguest inet manual inherits ethtemplate

iface brguest inet manual inherits brtemplate
  bridge_ports ethguest

Notez que ici, c'est dans le ethtemplate qu'il y a la configuration du MTU: le bridge prendra toujours forcément le plus petit MTU parmi les interfaces qui le composent.


Nous allons également créer le bridge dédié pour le LACP créé juste avant (oui, au début du billet, il devait être regroupé avec des interfaces en switch, mais au final, j'ai décidé de le mettre sur un bridge dédié, il n'y a que les imbéciles qui ne changent pas d'avis, je l'ai toujours dit et je n'en démordrai pas !), en rajoutant à la fin de /etc/network/interfaces/05-bondlink:

auto brbond

iface brbond inet manual inherits brtemplate
   bridge_ports bondlink


Et pour finir, nous allons "exploiter" le bloc de 8 interfaces pour créer un switch au niveau de l'hyperviseur, via /etc/network/interfaces/10-switch:

auto ethsw1 ethsw2 ethsw3 ethsw4 ethsw5 ethsw6 ethsw7 ethsw8

auto brswitch

iface ethsw1 inet manual inherits ethtemplate
iface ethsw2 inet manual inherits ethtemplate
iface ethsw3 inet manual inherits ethtemplate
iface ethsw4 inet manual inherits ethtemplate
iface ethsw5 inet manual inherits ethtemplate
iface ethsw6 inet manual inherits ethtemplate
iface ethsw7 inet manual inherits ethtemplate
iface ethsw8 inet manual inherits ethtemplate

iface brswitch inet manual inherits brtemplate
   bridge_ports regex ethsw.*

A mon grand regret, le seul endroit où j'ai réussi à trouver une syntaxe de gestion d'expressions régulières, c'est pour le bridge_ports. J'aurais aimé faire un truc genre:

auto ethsw.*
iface ethsw.* inet manual inherits ethtemplate

mais ca ne fonctionne pas (en tout cas sur ma version actuelle !), et mes recherches sur le net pour trouver une autre syntaxe permettant ce genre de choses sont restées infructueuses......


Si vous voulez finalement faire quand même un bridge qui regroupe switches et LACP, c'est facile: il suffit de configurer chaque LACP séparément, et vous pourrez finir par:

iface brenorme inet manual inherits brtemplate
   bridge_ports bondana bondjames ethsw1 ethsw2 ethsw3 ethsw4

On doit même pouvoir faire un truc classouille avec les regex, mais j'ai la flemme de faire des essais pour une configuration que je n'utiliserai finalement pas dans la vraie vie......

On pourrait accélérer le démarrage, stp ?

Oui, on l'avait à vrai dire déjà vu dans l'épisode précédent, ca met un temps fou à démarrer, à cause du Spanning Tree Protocol, STP, donc... Il suffit de le désactiver en indiquant dans le template de bridge:

iface brtemplate inet manual
       bridge_stp 0
       bridge_waitport 0
       bridge_fd 0

Ah, et donc, assurez vous de ne pas faire de boucles réseau, vu que vous venez de désactiver le truc qui les aurait détectées et plus ou moins gérées pour vous.........

Bypass de netfilter

A la création de chaque vif, les scripts Xen rajoutent des entrées de forwarding dans iptables, du genre:

0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            PHYSDEV match --physdev-out vifxx --physdev-is-bridged
0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            PHYSDEV match --physdev-in vifxx --physdev-is-bridged

Mais dans notre cas, l'analyse des paquets en transit est le boulot de la VM firewall, donc nous allons nous assurer que la pile IP de l'hyperviseur en fait le moins possible, en rajoutant ces 3 lignes dans /etc/sysctl.conf (ou dans un fichier dédié de /etc/sysctl.d si vous voulez faire propre..... ce qui est mieux......):

net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

Sur les versions relativement récentes des noyaux Linux, il sera également nécessaire de forcer le chargement du module br_netfilter au démarrage:

echo br_netfilter >> /etc/modules

(là aussi, vous pouvez faire un fichier dédié dans /etc/modules-load.d)

Petit reboot (ou actions équivalentes à la main si vous préférez), et on pourra constater que les paquets qui doivent transiter sur les bridges circulent sans soucis, même avec un ip_forward=0 au niveau de la configuration de l'hyperviseur, même avec une configuration de filtrage en BLOCK/DROP.

On pourrait du coup aller modifier les scripts de Xen pour qu'il ne génère pas les règles de forward.

Un peu d'isolation réseau.....

A ce moment de la configuration, que ca soit avec bridge ou avec openvswitch, j'ai des bridges sans IP qui se permettent de répondre à des requêtes ARP avec leur propre adresse MAC, ce qui fout rapidement un sacré bordel dans le réseau..... Si j'en crois ce que j'ai lu ici, le problème vient du noyau Linux, qui a une notion pas très exclusive des couples Adresse MAC / Adresse IP correspondante. Sur le principe, tant que c'est entre adultes consentants et fait de façon safe, je ne suis pas le genre de gars à critiquer ce genre de relations libres, sauf que là, justement, ca n'est pas safe du tout, il faut donc y remettre un peu d'ordre.

La solution proposée par Vincent (oui, style genre on est potes de longue date, alors que j'ai découvert son blog il y a quelques jours) est d'activer une fonctionnalité relativement récente du noyau Linux, qui est le filtrage des VLANs sur les ports d'un bridge (je ne fais quasiment que recopier.......). Comme je n'aime pas appliquer une commande sans comprendre ce qu'elle active vraiment, j'ai cherché un peu d'autres infos sur cette fonctionnalité, et à peu près à chaque fois, ca parlait de vraiment faire passer des VLANs sur un bridge, ce qui n'est pas du tout mon cas d'usage.

Toujours d'après Vincent, ebtables n'a pas l'air d'être une solution méga fiable, donc j'ai un peu mis de coté.

A un moment je vais arrêter de citer Vincent, mais pas encore là maintenant de suite, il évoque aussi la possibilité de résoudre le truc avec tc(8). Ce truc a l'air bien, faudra que je regarde......

En pratique, j'ai résolu au moins la partie émergée de mon problème avec une commande assez simple:

ip link set <dev> arp off

Et ce, sur toutes mes interfaces qui n'ont pas d'adresse IP. C'est à dire à peu près toutes mes interfaces, donc, sauf celle d'admin et le loopback.

Pour les bridge et les ethernet, il suffit de modifier les templates, qui deviennent:

iface brtemplate inet manual
       bridge_stp 0
       bridge_waitport 0
       bridge_fd 0
       post-up ip link set $IFACE arp off

iface ethtemplate inet manual 
       mtu 9000
       post-up ip link set $IFACE arp off

Et donc, on fera attention à bien déshériter l'interface d'admin et le loopback, en espérant qu'ils ne fassent pas de procès.......

Pour les vif, je n'ai pas trouvé mieux que modifier /etc/xen/scripts/xen-network-common.sh, et en particulier _setup_bridge_port():

--- /etc/xen/scripts/xen-network-common.sh
+++ /etc/xen/scripts/xen-network-common.sh
@@ -92,6 +92,8 @@ _setup_bridge_port() {
         # stolen by an Ethernet bridge for STP purposes.
         # (FE:FF:FF:FF:FF:FF)
         ip link set dev ${dev} address fe:ff:ff:ff:ff:ff || true
+        ip link set dev ${dev} arp off mtu 9000 || true
+       ethtool -K ${dev} tx off gso off gro off tso off
     fi
 
     # ... and configure it
@@ -133,6 +135,7 @@ add_to_bridge () {
 
 # Usage: set_mtu bridge dev
 set_mtu () {
+    return
     local bridge=$1
     local dev=$2
     mtu="`ip link show dev ${bridge}| awk '/mtu/ { print $5 }'`"

Notez que je ne suis pas tout à fait certain que tout ceci soit vraiment nécessaire: il suffit peut être de mettre cette option uniquement sur les bridges. Mais il n'y a vraiment vraiment aucune raison pour que le noyau s'occupe de quoi que ce soit lié à ARP (voire même de quoi que ce soit en général, d'ailleurs) sur ces interfaces, donc autant le lui dire. Et j'en profite pour glisser aussi une MTU à 9000 sur ces interfaces, n'ayant pas trouvé de méthode qui fonctionne via /etc/network/interfaces pour le faire. Et je désactive donc la fonction set_mtu(), qui ne réagit pas correctement dans le cas d'un bridge qui n'a pas déjà un MTU à 9000 (donc un bridge sans interface physique avec un MTU à 9000, soit plus de la moitié de mes bridges.....).

Je désactive aussi des trucs avec ethtool, l'explication est plus bas, et comme je suis un gars cool je vous livre le patch en une seule fois......

Un peu plus d'isolation réseau

A priori, ca ne devrait pas être nécessaire avec toute cette configuration, et c'est le rôle de la VM firewall d'assurer la sécurité réseau, mais un vieux dicton dit Ceinture, bretelles et calecon propre, donc je vais mettre en place un petit script de filtrage, qui autorise en entrée le ssh, le NTP et le ping:

#!/bin/sh

ADMINIF=bradmin

IPTABLES=/sbin/iptables

# Set filter default policies to "Drop"
# Done first for security reasons
$IPTABLES -t filter -P INPUT DROP
$IPTABLES -t filter -P FORWARD DROP
$IPTABLES -t filter -P OUTPUT DROP

$IPTABLES -t filter -F INPUT
$IPTABLES -t filter -F OUTPUT

# Creating table for Established / related packets.
# Create Table
$IPTABLES -t filter -N stateful

$IPTABLES -t filter -F stateful

# Accept all related packets
$IPTABLES -A stateful -m state --state ESTABLISHED -p tcp ! --syn  -j ACCEPT
$IPTABLES -A stateful -m state --state ESTABLISHED -p udp -j ACCEPT
$IPTABLES -A stateful -m state --state ESTABLISHED -p icmp -j ACCEPT
$IPTABLES -A stateful -m state --state RELATED -j ACCEPT

$IPTABLES -A stateful -m state --state INVALID -j NFLOG --nflog-threshold 20 --nflog-prefix "Fw: invalid state"
$IPTABLES -A stateful -m state --state INVALID -j DROP

$IPTABLES -A INPUT -i lo -j ACCEPT

$IPTABLES -A INPUT -j stateful
$IPTABLES -A INPUT -p tcp -i $ADMINIF --dport 22 --syn -j sshguard
$IPTABLES -A INPUT -m state --state NEW -p tcp -i $ADMINIF --dport 22 --syn -j ACCEPT
$IPTABLES -A INPUT -m state --state NEW -p udp -i $ADMINIF --dport 123 -j ACCEPT
$IPTABLES -A INPUT -m state --state NEW -p icmp -i $ADMINIF -j ACCEPT

$IPTABLES -A OUTPUT -o lo -j ACCEPT

# for the moment, allow all traffic from this host
$IPTABLES -A OUTPUT -j stateful
$IPTABLES -A OUTPUT -m state --state NEW -j ACCEPT

Ce script sera appelé en post-up dans la configuration du bridge d'admin (et cette version suppose que vous avez sshguard d'installé, mais si ce n'est pas le cas cela générera juste une erreur à la création de la règle qui fait référence à la chaine sshguard, le reste fonctionnera normalement).

Ca y est, on est assez isolés, là ?

Dans le cas décrit par Vincent, probablement pas. Sauf que, dans notre cas, l'hyperviseur ne fait aucun boulot de routage. D'ailleurs, le routage est désactivé: net.ipv4.ip_forward et net.ipv6.conf.all.forwarding sont à 0.
Du coup, une tentative de routage forcée consommera probablement quelques cycles CPU en trop avant de se faire jeter dehors, mais entre le routage désactivé, le script de filtrage et la désactivation de tout ce qui est ARP presque partout, je n'ai pas réussi à faire passer des paquets en douce en utilisant les techniques décrites par Vincent (ou en tentant quelques autres variantes).

La VM Firewall

Voilà son fichier de configuration:

name = "Firewall"

builder = 'hvm'

vcpus = 2
maxvcpus = 2
cpu_weight = 256

memory = 1024
maxmem = 1024

#on_poweroff = "restart"
on_watchdog = "restart"
on_crash = "restart"
on_reboot = "restart"

disk = [ 'file:/home/Xen/Firewall.img,hda,w' ]

usb = 0

vif = [
       'type=vif,vifname=vfwout,bridge=brout, mac=00:16:3e:xx:yy:0',
       'type=vif,vifname=vfwlan,bridge=brlan, mac=00:16:3e:xx:yy:1',
       'type=vif,vifname=vfwserver,bridge=brserver, mac=00:16:3e:xx:yy:2',
       'type=vif,vifname=vfwswitch,bridge=brswitch, mac=00:16:3e:xx:yy:3',
       'type=vif,vifname=vfwpub,bridge=brvpub, mac=00:16:3e:xx:yy:4',
       'type=vif,vifname=vfwwifi,bridge=brwifi, mac=00:16:3e:xx:yy:5',
       'type=vif,vifname=vfwbond,bridge=brbond, mac=00:16:3e:xx:yy:6',
       'type=vif,vifname=vfwadmin,bridge=bradmin, mac=00:16:3e:xx:yy:7',
    ]

serial = 'pty'

Même avec le passage au toolstack xl, pas beaucoup de différence avec l'ancienne configuration.
Quitte à mettre des noms prédictibles, stables et explicites à toutes les interfaces réseau de la configuration, autant le faire aussi avec les vif créées, via l'argument vifname=.
00:16:3e: est toujours le préfixe MAC (à peu près rien à voir avec l'ordinateur homonyme de ma soeur, sauf que son MAC a une MAC, et même probablement 2......) réservé pour XEN, vous mettez ce que vous voulez pour xx:yy (mais je vous conseille cependant fortement de mettre des valeurs hexadécimales valides..... et je vous conseille aussi de mettre la même valeur xx:yy pour toutes les lignes, ainsi que de numéroter le dernier octet dans l'ordre, mais là c'est plus pour faire joli et s'y retrouver facilement).
Chaque interface est donc reliée à un bridge différent, et avec les jolis noms de bridges utilisés avant, ca en devient assez lisible.
L'ancienne configuration précisait model=e1000, la nouvelle type=vif, pour des raisons de performances: la VM doit tourner en HVM (virtualisation complète), mais embarque les drivers "PV-HVM" de Xen qui sont plus efficaces qu'un mode complètement émulé (type=ioemu, ce qui est le cas par défaut, et model=e1000, qui est un driver assez efficace quand même en mode ioemu).

Le fichier de conf d'une VM

Là, on est dans du assez simple, la partie réseau de la conf d'une VM classique ressemblera à ça:

vif = [
        'vifname=vifnomdemavm,bridge=brnomdemavm,mac=00:16:3e:00:00:00',
     ]

Vous penserez à mettre autre chose que 00:00:00 à la fin de la MAC, et vous ajusterez le nom de l'interface et du bridge. Notez qu'il est bien sur possible de raccorder plusieurs VMs sur un seul bridge si besoin est.

accélération réseau et vifs

J'avais déjà eu le problème lors de mes anciennes installations: TCP Segmentation Offload et ses potes ne font pas trop bon ménage avec les interfaces Vif...

Ca semblait mieux sur mes premiers tests, mais mes interfaces étaient à ce moment toutes raccordées à des bridges qui contenaient aussi une interface ethernet (disposant elle d'un support de ces accélérations). Et dans mes premiers tests, laisser le support de ces trucs faisait gagner en performances, du coup, cette fois, j'ai essayé de ne pas être aussi bourrin qu'il y a quelques années, et j'ai fait quelques autres essais pour mieux comprendre...... J'ai cru à un moment pouvoir laisser activé l'accélération matérielle sur les bridges qui incluaient une interface ethernet physique. Si ca peut vous aider, j'avais alors fait cette modification (version openvswitch), qui profite du nommage de mes interfaces pour pouvoir déterminer facilement si une ethernet fait partie du bridge:

--- /etc/xen/scripts/vif-openvswitch.old
+++ /etc/xen/scripts/vif-openvswitch
@@ -79,6 +79,12 @@ add_to_openvswitch () {
 
     local vif_details="$(openvswitch_external_id_all $dev)"
 
+    # check if a physical ethernet is connected to the bridge
+    local breth="$(ovs-vsctl list-ifaces $bridge |grep '^eth')"
+    if [ -z "$breth" ]; then
+        ethtool -K $dev tx off gso off gro off tso off
+    fi 
+
     do_or_die ovs-vsctl --timeout=30 \
          if-exists del-port $dev \
         -- add-port "$bridge" $dev $tag_arg $trunk_arg $vif_details

Il y a probablement moyen de l'écrire de façon plus classouille.......

Sauf que ca ne fonctionne toujours pas dans tous les cas: en particulier, chez moi, l'interface d'administration de l'hyperviseur embarque une interface ethernet (pour faire de l'administration réseau urgente avec un portable et un RJ45 si besoin est), mais les paquets émis localement passent par l'interface Vif en fonctionnement normal. Et mon hyperviseur s'est retrouvé joignable en ping, mais ni en TCP, ni en UDP......

J'en suis donc revenu à une désactivation systématique de la fonction sur les vifs, en allant remodifier _setup_bridge_port() dans /etc/xen/scripts/xen-network-common.sh (extrait du patch précédent):

--- /etc/xen/scripts/xen-network-common.sh.old
+++ /etc/xen/scripts/xen-network-common.sh
@@ -92,6 +92,8 @@ _setup_bridge_port() {
         # stolen by an Ethernet bridge for STP purposes.
         # (FE:FF:FF:FF:FF:FF)
         ip link set dev ${dev} address fe:ff:ff:ff:ff:ff || true
+        ip link set dev ${dev} arp off mtu 9000 || true
+        ethtool -K ${dev} tx off gso off gro off tso off
     fi
 
     # ... and configure it

Au début, j'avais aussi tenté de désactiver rx (vérification du checksum en réception), mais il ne semble pas désactivable, et ca ne semble pas poser de problèmes...... Sur le coup, je n'ai apparemment même pas besoin de faire les mêmes désactivations dans mes VMs (à part le firewall, mais c'est fait automatiquement), ce qui m'arrangera quand je ferai d'autres installations en vraies conditions de prod (donc en utilisant le réseau dès l'installation).

Après mes premiers vrais benchs, je me suis retrouvé avec des vif désactivées dans le Dom0 et le message suivant dans le dmesg:

vif vif-xxx viftruc: txreq.offset: 18, size: 9014, end: 9032
vif vif-xxx viftruc: fatal error; disabling device

En cherchant une nouvelle fois un peu sur le net, en faisant quelques essais et en redémarrant à chaque fois la VM concernée par l'interface bloquée (je n'ai pas trouvé mieux comme solution pour réactiver ces interfaces), j'ai fini par constater que le problème ne se pose plus si je fais la même commande ethtool sur les interfaces des VMs. Et vu ce que j'ai eu le temps de mesurer avant d'avoir ces désactivations d'interfaces, les différences de performances sont à peu près inexistantes.

Edit: Après quelques mois d'utilisation, en fait, le problème arrive toujours de temps en temps. Les dernières fois où c'est arrivé, c'était lors d'un classique apt install <un truc>. J'ai eu le même problème en installant le même paquet sur 2 VMs différentes, mais le plantage n'a pas eu lieu sur le même paquet de dépendance en cours de téléchargement. En général, il me suffit d'effacer le paquet "partial" (qui avait fait planter) et de relancer le apt install pour que ça fonctionne. Par contre, une fois, je n'ai pas effacé le partial et relancé le apt install, et j'ai eu à nouveau le problème.

Enfin, quand je dis "il suffit de", ce n'est pas tout à fait vrai: je dois d'abord récupérer une interface fonctionnelle coté hyperviseur ! Si j'en crois ce que j'ai dit dans la première version de ce billet, je pouvais apparemment la récupérer juste en redémarrant la VM. Peut être à cause du nommage des interfaces, je me retrouve maintenant à devoir redémarrer l'ensemble de l'hyperviseur pour récupérer l'interface fonctionnelle, ce qui est franchement désagréable !
Même stoppant complètement la VM, en renommant son interface dans le .xl et en la redémarrant, je ne récupère pas l'interface coté hyperviseur.....

Démarrage et redémarrage....

ordre de démarrage des VMs

En prod, au moins la VM firewall doit démarrer automatiquement. Dans mon cas, la plupart des autres VMs aussi. En plus, certaines VMs peuvent dépendre d'autres au niveau du réseau. Par expérience sur mes anciens hyperviseurs, je réduis au maximum les dépendances réseau de mes VMs au démarrage: pas de DHCP, montages NFS en autofs ou équivalent, etc.... Il est cependant également possible de spécifier l'ordre de démarrage des VMs, en s'assurant de l'ordre des fichiers dans /etc/xen/auto (faites comme tout le monde, mettez des numéros au début des noms de vos fichiers).

Le réseau que vous demandez n'est pas encore disponible....

Dans ma version actuelle, tout fonctionne, mais pas au boot: les VMs ne démarrent pas, et en creusant un peu, c'est tout simplement parce que les bridges ne sont pas encore prêts au moment où le système veut démarrer les VMs. Après avoir creusé un peu, j'ai trouvé une solution assez simple qui consiste à préciser que le démarrage des domaines Xen nécessite un réseau déjà établi:

--- /etc/init.d/xendomains.old
+++ /etc/init.d/xendomains
@@ -1,8 +1,8 @@
 #!/bin/bash
 ### BEGIN INIT INFO
 # Provides:          xendomains
-# Required-Start:    $syslog $remote_fs xen
-# Required-Stop:     $syslog $remote_fs xen
+# Required-Start:    $syslog $remote_fs xen $network
+# Required-Stop:     $syslog $remote_fs xen $network
 # Should-Start:      drbd iscsi openvswitch-switch
 # Should-Stop:       drbd iscsi openvswitch-switch
 # X-Start-Before:    corosync heartbeat libvirtd

A propos de réseau pas disponible et de boot, dans ce genre de configurations, pensez à vérifier le contenu de /etc/network/if-up.d (et if-pre-up.d): dans mon cas, après avoir installé openntpd, je me suis retrouvé avec un script de redémarrage du démon à chaque activation d'interface réseau...... j'ai plus de 80 interfaces réseau, dont 30 activées pendant le démarrage...........

Redémarrage de l'hyperviseur

Par défaut, lors de l'arrêt de l'hyperviseur, les scripts (au moins chez Debian ?) stoppent les VMs dans l'ordre inverse de démarrage (la plus récemment démarrée en premier, donc). L'idée n'est pas mauvaise, jusqu'au moment où vous allez vouloir redémarrer une VM, pour une raison ou une autre..... Après quelques bricolages, j'en suis arrivé à la conclusion que le plus simple serait d'intercepter le script /usr/lib/xen-common/bin/xen-init-list (appelé par les scripts d'arrêt des domaines Xen) pour changer l'ordre de son affichage. Pour l'instant, je n'ai pas encore de solution générique propre, du coup il n'y a pas d'intérêt à partager mon bricolage qui ne fonctionne que chez moi.........

vendredi, juin 27 2014

Déployer un firewall sous Xen pour ma soeur.....

Il y a quelques temps, j'ai commencé à faire mumuse avec la virtualisation (sous Xen), y compris pour un cas de figure un poil complexe sur lequel je n'avais trouvé aucune doc sur le net (si vous voulez juste déployer quelques VMs classiques dans un hyperviseur Xen, vous n'êtes pas au bon endroit, il existe plein de tutoriels pour ce cas de figure basique).

Ca tombe bien, vu que je n'avais pas fait de nouveau billet aujourd'hui cette semaine ces derniers temps depuis au moins un an, et comme j'ai quelques potes qui envisagent de faire une conf similaire, c'est un prétexte que je ne pouvais pas louper.

En plus, ca me servira aussi probablement le jour ou mon disque dur va me lâcher et que je vais devoir tout refaire.......


Disclaimer

Attention: contrairement à ce que le titre pourrait laisser croire, ce billet contient de nombreux extraits naturels de trucs techniques plus ou moins compliqués. En particulier, des connaissances de base en virtualisation sont à peu près indispensables, et une expérience de Xen en particulier est vivement souhaitée.

En cas de dépressurisation cérébrale suite à la lecture de ce billet, des masques à oxygène ne tomberont probablement pas automatiquement du plafond. Vous êtes invités à repérer à l'avance les issues de secours, qui ne seront probablement pas non plus indiquées par des consignes lumineuses en cas d'incident.

Les actions décrites ci-après ont été réalisées par un Vieux Con de Hacker Old School (tm), qui porte des t-shirts de geek, qui va généralement plus vite en utilisant un shell ou emacs qu'avec une interface graphique, un gars qui a survécu à la Grande Guerre (entre l'Amiga et l'Atari ST), tout ça....

Et bien évidemment, tout a été réalisé dans des conditions optimales de sécurité, avec un bouton rouge, une équipe d'intervention d'urgence prête à réagir à tout moment, 8 litres de coca (note pour moi même: en rester là, j'ai déjà fait la reprise de la blague de Naheulbeuk avec la liste de courses.....), et un vrai réseau de prod à coté pendant toute la durée de l'opération.

Donc les enfants, NE FAITES SURTOUT PAS CA CHEZ VOUS, et si vous le faites quand même (bah oui, on ne va pas se mentir: dire à quelqu'un "ne fais surtout pas ça", c'est quand même un excellent moyen de l'inciter à le faire, juste pour voir.....), vous serez seul(e) responsable de toute conséquence dramatique qui en découlerait, comme une instabilité réseau, la perte de données, la création d'une brêche dans le continuum spatio-temporel, le départ de votre copine, un contrôle fiscal, l'arrivée de votre belle mère, une halitose subite, ou tout autre désagrément non listé ci-dessus.
Notez cependant que, à l'exception d'une légère instabilité réseau lors de la première mise en prod, aucun des autres symptômes cités en exemple n'est survenu lors de ma mise en prod !

Ah, et aucun animal mignon n'a été blessé, tué ou mangé pendant l'intervention.


Ok, et on voulait pas faire un vrai truc, à un moment ?

Ne soyez pas impatients, comme ça, si, effectivement, on voulait faire un vrai truc, que j'ai essayé de transcrire en un (magnifique, je sais) schema (cliquez dessus pour l'agrandir):

SchemaReseauDom0


En admettant que vous n'ayez pas tout à fait compris la configuration, malgré la profusion de couleurs et mon extraordinaire maîtrise de l'outil de création du truc, l'idée est donc, comme le titre du billet le suggérait quand même déjà relativement clairement, de déployer un firewall virtuel (ais-je vraiment besoin de préciser la marque du firewall dans mon cas ???) dans un hyperviseur Xen.

Ce qui pourrait paraître relativement classique (un hyperviseur, une VM, du réseau) pose cependant une problématique particulière dans mon cas: je veux pouvoir gérer à ma convenance un ou plusieurs bridges au niveau du firewall (donc avec filtrage, IPS, tout ça...), sans devoir faire de modifications de configuration sur le Dom0 à chaque fois, et évidemment sans perturbations par le Dom0, sans paquets qui passent discrètement d'une interface physique une autre en glissant un petit biffeton à l'hyperviseur, ou autres trucs pas recommandables dans un réseau de bonne famille et propre sur lui.

Dit autrement, tout doit réagir exactement comme si les interfaces réseau physiques étaient directement reliées au firewall.

Et, accessoirement, si ça peut fonctionner, c'est mieux, y compris avec d'autres VMs sur le même hyperviseur, également branchées sur des interfaces dédiées du firewall.

Dans tout le reste de l'explication, on considèrera 8 interfaces pour le firewall, dont 4 reliées à des interfaces physiques, si votre configuration est différente, l'idée reste la même, il suffit d'adapter....


Autres solutions possibles


Un autre hyperviseur

Euh, oui, on aurait pu, effectivement..... mais en gros, VMWare n'est pas OpenSource, Xen est officiellement supporté par ma VM de firewall (ok, la version commerciale, mais dans les faits, je sais que ça fonctionne bien), et aucune autre solution de virtualisation ne m'a paru avoir le "truc en plus" qui aurait justifié le changement de techno.

Et j'en vois deux dans le fond qui auraient de toutes façons fait la remarque "mais y'avait d'autres solutions" si j'en avais choisi une autre.....


OpenVSwitch

Pour la configuration réseau au niveau de l'hyperviseur, j'ai utilisé les bridges Linux (brctl, tout ça.....).
On aurait pu également utiliser OpenVSwitch, j'ai le souvenir d'avoir un peu regardé cette solution, et de l'avoir mise de coté pour une bonne raison à l'époque...... bonne raison dont je ne me souviens plus, évidemment....

Il est également possible que cette bonne raison soit devenue obsolète depuis.


Un firewall physique

Euh, oui, on peut, mais c'est pas écolo, c'est pas tendance (virtualisation, nuages, blabla.....), et c'est un peu trop facile pour être fun.
Accessoirement, ca ne répond pas non plus au cas de figure "autres VMs à filtrer via le firewall", en tout cas pas simplement, si j'ai plusieurs VMs que je veux segmenter entre elles.


Passer directement les interfaces réseau à la VM

Sur le papier (enfin, sur le site web, ne commencez pas à pinailler comme ça), Xen sait faire, ca s'appelle le PCI Passthrough.
Et c'est fort logiquement la première option que j'avais étudié quand j'ai commencé à bricoler ma configuration.

Sauf que, dans les faits, ca n'est pas aussi simple, il faut déjà avoir une carte mère qui supporte l'IOMMU, ce qui n'était pas mon cas.
Si la carte mère de votre Dom0 le supporte, cependant, c'est probablement une possibilité intéressante à tester !

Et cette solution ne gère pas le branchement entre le firewall et d'autres machines virtuelles, donc ne serait applicable que pour les interfaces physiques.


DomO, VM, etc...


Installation de l'hyperviseur Xen

L'installation de base est une procédure relativement simple, et allègrement documentée sur le net.

Dans notre exemple, il s'agira d'une Debian, je ne vous ferai pas l'affront d'expliquer ici comment on installe une Debian de base, et la mise en place de l'hyperviseur Xen est également documentée sur le wiki Debian sur Xen (n'allez pas trop loin dans les manips du wiki, en particulier pour tout ce qui est configuration réseau, c'est pour le cas "classique").

Note: il existe plusieurs facades pour configurer/administrer un Xen. J'étais parti pour une administration avec le toolstack Xl, mais j'ai eu divers problèmes avec, entre autres à cause de la version de Xen fournie par ma Debian. Je me suis donc rabattu sur le toolstack xm, raison pour laquelle les exemples de configuration seront fournis sous ce format.

Cependant, xm/xend est marqué DEPRECATED, il est donc préférable de convertir les configurations au format xl si vous utilisez une version récente de Xen.


Préparation et configuration de la VM Firewall

Dans mon cas, la VM Firewall fait tourner un dérivé de FreeBSD, ce qui nécessite une configuration un poil différente pour le démarrage:

name = "Firewall"

kernel = "/usr/lib/xen-4.1/boot/hvmloader"
builder = 'hvm'

vcpus = 1
maxvcpus = 1
cpu_weight = 512

memory = 1024
maxmem = 1024

#on_poweroff = "restart"
on_watchdog = "restart"
on_crash = "restart"
on_reboot = "restart"

serial = 'pty'

disk = [ 'file:/home/Xen/Firewall.img,hda,w' ]

Pour l'essentiel, cependant, cette partie de la configuration est très classique (vous penserez à mettre à jour le chemin de la ligne kernel, si votre version de Xen est différente, évidemment.....), je lui réserve 1 VCPU et 1Go de RAM dans mon cas, et comme c'est un firewall, je décrète que presque tous les cas d'arrêt de la VM nécessitent un redémarrage (sauf un arrêt explicite et volontaire).

Le serial='pty' me permettra également d'avoir un accès facile à la console de ma VM depuis un shell de l'hyperviseur en cas de soucis (note pour moi: KernelMsg=1). Selon votre VM firewall, la méthode sera peut être différente.


Configuration réseau


A un moment, on veut faire des trucs pour le réseau, il va bien falloir le configurer.....

Configuration du Dom0

D'un point de vue Dom0, on va créer plein de bridges à 2 interfaces: un pour chaque interface physique du Dom0 (à chaque fois couplé à une interface du firewall), et un pour chaque autre VM (ou éventuellement groupe de VMs) présente dans l'hyperviseur.
Il faut donc installer le package bridge-utils:

apt-get install bridge-utils

Les interfaces du Dom0 n'auront pas d'adresse IP (les bridges non plus, d'ailleurs), il va donc falloir forcer leur activation manuellement.
Dans /etc/network/interfaces, pour une interface physique ethx donnée, on aura donc:

auto ethx
auto brx

iface ethx inet manual

iface brx inet manual
       bridge_ports ethx

Dans le cas d'un bridge non relié à une interface physique, la seule différence est qu'on indiquera:

      bridge_ports none

(vous aurez la présence d'esprit de remplacer à chaque fois x par le numéro de l'interface, ça reste valable dans toute la suite de l'explication)

Notez que vous pouvez à priori remplacer "inet" par "inet6", pour faire tendance, ce qui ne devrait absolument rien changer: il ne s'agit pas ici de configurer des interfaces, mais juste de les activer et de les relier à des bridges qu'on crée au passage.
Selon le niveau de support et de configuration d'IPv6 par votre VM firewall, il sera peut être nécessaire de désactiver pas mal de choses au niveau IPv6 sur votre Dom0, y compris (surtout ?) les adresses Lien Local (celles en fe80::), ce qui vous évitera des boucles réseau étranges en IPv6...

Pour pouvoir accéder au Dom0 depuis le réseau, une interface aura un status un peu particulier: elle aura une adresse IP, la veinarde !
Enfin, plus précisément, le bridge sur lequel elle est attachée aura une IP, puisque c'est le fonctionnement normal sous Linux: une interface reliée à un bridge n'est plus utilisable directement.

Par rapport aux autres bridges, tout simplement, on va remplacer "inet manual" par "inet static" (et/ou par inet6 static si vous êtes tendance), et rajouter address, netmask, gateway (l'IP du firewall sur le LAN, en configuration finale, l'IP de votre passerelle actuelle pendant la préparation). Une configuration type DHCP, autoconf en v6 ou autre est à éviter: votre Dom0 démarrera logiquement avant tout le reste, y compris le firewall (bah oui, logique....), et ne saura donc peut être pas accéder à un serveur DHCP...
J'ai également du forcer son adresse MAC (hwaddress, j'ai mis l'adresse MAC de l'interface physique), vous verrez pourquoi après.

Idéalement, si votre niveau de parano est assez élevé (et surtout si vous avez un moyen assez facile d'accéder à votre Dom0 en console en cas de problèmes), dans la configuration finale, ce bridge sera reliée à une interface dédiée de la VM firewall (et donc à aucune interface physique).

  • avantage: permet de filtrer et protéger le Dom0
  • inconvénient: en cas de problème avec la VM firewall (erreur de configuration, reboot, mauvaise manip, etc...), la console sera le seul recours !

Une interface physique dédiée (et toujours reliée à une interface dédiée du firewall) vous permettra de venir brancher en urgence un ordinateur portable pour réparer d'éventuels problèmes, tout en bénéficiant de la même protection par le firewall en usage normal.

A défaut, si vous ne laissez pas n'importe qui se brancher sur votre LAN, et si vous mettez en place un filtrage au niveau du Dom0 n'autorisant que les accès ssh vers celui-ci (en certificats uniquement, évidemment), le niveau de sécurité restera tout à fait correct, et permettra une intervention simplifiée en cas de soucis.

Si vous êtes vraiment cool et partageur, un accès telnet en openbar sur une IP publique (et sans mot de passe pour root, on ne va pas commencer avec des mesquineries du genre) fera probablement plein d'heureux sur le net......

Selon votre topologie réseau, vous serez peut être amenés à configurer ainsi plusieurs interfaces/bridges sur le Dom0 (si vous avez plusieurs plans d'adressages qui peuvent servir à administrer en urgence), il suffira de reproduire la même idée pour chacune de ces interfaces.


Configuration réseau de la VM Firewall

Pour notre VM, nous utiliserons donc le script vif-bridge, ce qui donne dans la configuration de la VM firewall, pour la partie réseau:

vif = [
       'bridge=br0,model=e1000,mac=00:16:3e:xx:0',
       'bridge=br1,model=e1000,mac=00:16:3e:xx:1',
       'bridge=br2,model=e1000,mac=00:16:3e:xx:2',
       'bridge=br3,model=e1000,mac=00:16:3e:xx:3',
       'bridge=br4,model=e1000,mac=00:16:3e:xx:4',
       'bridge=br5,model=e1000,mac=00:16:3e:xx:5',
       'bridge=br6,model=e1000,mac=00:16:3e:xx:6',
       'bridge=br7,model=e1000,mac=00:16:3e:xx:7',
    ]

Ici, on force le type d'interface à e1000 (le type par défaut était moins performant au niveau de la VM, de mémoire), et on va également forcer les adresses MAC pour éviter d'avoir un bordel ambiant à chaque redémarrage de la VM. Le préfixe 00:16:3e est réservé pour Xen, ce qui évitera des collisions avec d'autres cartes ethernet du réseau, vous mettez ce que vous voulez à la place de :xx: (qui est d'ailleurs :xx:xx:, vu qu'une adresse MAC est codée sur 6 octets), et tant qu'à faire, on va numéroter le dernier octet dans l'ordre pour faire propre.


Bricolage et magie noire sur le Dom0

Si toutes vos interfaces ont des plans d'adressages différents, cette configuration devrait presque suffire à faire fonctionner le truc.
Sauf que, dans mon cas, je veux pouvoir gérer des bridges au niveau du firewall, et c'est là que ca devient poilu, vu que, en l'état, ça ne fonctionnera pas (ou en tout cas pas correctement).


Gestion des paquets entrants

Le premier problème, c'est que les interfaces du Dom0 n'accepteront pas les paquets à destination d'autres adresses MAC (ou en tout cas pas toujours, selon la configuration de plein de trucs). Il faut donc forcer toutes les interfaces avec l'adresse MAC de broadcast.

Nous allons donc rajouter la ligne suivante dans la configuration de toutes les ethx:

up /sbin/ifconfig ethx -arp hw ether fe:ff:ff:ff:ff:ff

Pareil pour les bridges, sauf qu'il est nécessaire de le faire en post-up (en phase "up", apparemment, le bridge n'existe pas encore assez pour l'opération):

post-up /sbin/ifconfig brx -arp hw ether fe:ff:ff:ff:ff:ff

C'est à ce moment que l'intérêt de forcer l'adresse MAC de l'interface d'administration apparaît....
Maintenant que j'y repense, je me rends également compte que, sur cette interface, l'option hwaddress fonctionne très bien, y compris sur une interface de type bridge...... je n'ai pas fait le test (j'étais déjà en prod quand j'y ai pensé), mais l'option devrait aussi logiquement fonctionner ici, pour les eth comme pour les br:

iface <un ethx ou un brx> inet manual
    hwaddress fe:ff:ff:ff:ff:ff
    bridge_ports ethx # seulement pour les interfaces brx


Bypass de netfilter

Le second obstacle, c'est que tous les paquets passent par le Netfilter du Dom0, qui peut donc les bloquer, les router, ou faire d'autres choses avec, que la morale n'approuve pas forcément...
Nous allons donc indiquer au système que les paquets qui passent sur un bridge ont une bouteille à l'intérieur, connaissent bien le patron, et seront gérés par le service de sécurité de la salle VIP s'ils ont des baskets ou une casquette.
Ca se fait en ajoutant à /etc/sysctl.conf:

net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

Activez à la main ces sysctl si vous voulez économiser un reboot.


Accélération du démarrage

A un moment, vous remarquerez que le démarrage du Dom0 est très long, curieusement depuis qu'on a modifié la configuration réseau pour rajouter tous les bridges....
Pour revenir à une vitesse de démarrage normale, il suffit d'ajouter les paramètres suivantes dans chaque section brx du fichier /etc/network/interfaces:

       bridge_stp off
       bridge_waitport 0
       bridge_fd 0

Evidemment, si vous avez un réseau avec plein de boucles, désactiver le protocole STP partout est probablement une très très mauvaise idée.....


Amélioration des performances

A ce moment, votre configuration devrait fonctionner, mais avec des performances réseau complètement pourries dès que ça traverse le firewall, voire des sessions TCP qui ne finissent jamais vraiment.

Une enquête approfondie réalisée conjointement entre Les Experts et NCIS révèle des trucs très étranges sur le réseau, dont par exemple des paquets qui dépassent allègrement la MTU, voire qui arrivent plus gros qu'ils ne sont partis.

Le grand méchant de cette histoire est finalement l'accélération matérielle des cartes réseau, en particulier la segmentation déportée des gros paquets et/ou le déport des sommes de contrôle.
Votre moteur de recherche préféré vous confirmera rapidement que ces trucs là ne font pas forcément bon ménage avec la virtualisation, en tout cas sous Xen, en tout cas aujourd'hui.

Nous allons donc désactiver tout ça sur les interfaces vif (le pendant sur le Dom0 des interfaces des VMs), à l'aide de la commande ethtool (donc oui, apt-get install ethtool).
Au passage, tant qu'on est là et qu'on parle de performances, réseau, on va également augmenter la taille de la file d'attente des interfaces vif, qui est à une valeur très faible par défaut.

Pour que les modifications soient faites automagiquement à chaque création d'interface vif, nous allons donc modifier le script /etc/xen/scripts/vif-bridge:

[....]
case "$command" in
   online)
       setup_virtual_bridge_port "$dev"
       mtu="`ip link show $bridge | awk '/mtu/ { print $5 }'`"
       if [ -n "$mtu" ] && [ "$mtu" -gt 0 ]
       then
               ip link set $dev mtu $mtu || :
       fi
       add_to_bridge "$bridge" "$dev"
       ;;

devient:

[....]
case "$command" in
   online)
       setup_virtual_bridge_port "$dev"
       mtu="`ip link show $bridge | awk '/mtu/ { print $5 }'`"
       if [ -n "$mtu" ] && [ "$mtu" -gt 0 ]
       then
               ip link set $dev mtu $mtu || :
       fi
       ethtool -K "$dev" tso off gso off gro off tx off rx off
       ifconfig "$dev" txqueuelen 1024
       add_to_bridge "$bridge" "$dev"
       ;;

Sur ma configuration, ces deux petites lignes permettront de faire passer le débit de "3-4 Mb/s quand ça fonctionne" à "environ 400Mb/s et ça fonctionne tout le temps", ce qui est quand même assez appréciable (c'est apparemment la limite de mon serveur de fichier qui commence à se faire vieux), il faut bien le reconnaître, et sans aucune substance illégale, en plus !

Il sera probablement nécessaire de désactiver les mêmes fonctions au niveau de votre VM firewall, si celle-ci ne le fait pas automatiquement. Dans mon cas, c'est fait nativement.

En théorie, sur un réseau Gb, on gagnerait encore en performances en utilisant des jumbo frames, sauf que, toujours sur ma version actuelle de Xen, le support des jumbo frames est expérimental....


Configuration réseau finale du Dom0

Pour résumer la configuration finale (oui, j'aurais pu directement vous donner cette configuration, mais l'intensité narrative y aurait perdu, et je me trouve à vrai dire déjà assez sympa comme ça de vous faire un résumé) de /etc/network/interfaces va donner, pour une interface ethx (oui, vous ferez à nouveau l'effort de remplacer x par le bon numéro, et de faire la conf pour chaque interface):

auto ethx brx

iface ethx inet manual
       hwaddress fe:ff:ff:ff:ff:ff

iface brx inet manual
       bridge_ports ethx
       bridge_stp off
       bridge_waitport 0
       bridge_fd 0
       hwaddress fe:ff:ff:ff:ff:ff

Et vous mettrez "bridge_ports none" au lieu de "bridge_ports ethx" pour les bridges non reliés à une interface physique, et vous ferez la modification de configuration qui va bien pour le bridge qui aura une adresse IP.


Configuration réseau de la VM

A partir de là, c'est un fonctionnement et une configuration normales au niveau de la VM firewall. Dans mon cas, j'ai une interface externe sur un plan d'adressage dédié (zone encadrée en rouge sur le schéma du début), et toutes les autres interfaces du firewall (chacun encadré en bleu sur le même schéma) sont mises dans un bridge commun (au niveau du firewall, donc).

Il reste à faire la configuration réseau de la VM, les règles de filtrage, la configuration IPS, le NAT, et tout ce qui est à configurer lors d'un déploiement de firewall.


D'autres VMs pendant qu'on y est...

A partir de ce moment, installer d'autres VMs sur l'hyperviseur est une activité classique:
On récupère de quoi faire une install, comme par exemple l'image ISO Debian pour une installation de DomU, on fait "ce qu'il faut" pour avoir un espace disque (dans mon cas, donc, qemu-img create, mais il parait que c'est plus tendance d'utiliser lvm), et on lance l'install en suivant un tutoriel classique d'installation de VM pour Xen.

Pour les archives, je rajoute à la configuration Xen de cette VM:

 extra = "console=hvc0"

Cette commande me permettra d'accéder à la console de la VM depuis un shell de l'hyperviseur (il me semble qu'il fallait aussi faire une modif dans la conf grub de la VM......).

La seule subtilité de notre configuration par rapport aux tutoriels standards est fort logiquement au niveau de la configuration réseau. Si on veut relier la VM à l'interface x du firewall (et comme on a fait un numérotage propre et homogène sur toute la ligne), on aura:

vif = [
       'bridge=brx,model=e1000,mac=00:16:3e:<de l'hexa>'
    ]

I am the king of the wooooorld !!!

Bon, ok, du monde, peut être pas, mais de mon hyperviseur et de ma VM firewall, en tout cas, oui, du moins tant que je conserve des mots de passe et une configuration de filtrage corrects.

Ca n'est pas forcément hyper efficace pour pecho en boite, et c'est déjà parfois assez limite comme sujet de discussion quand des amis passent à la maison et demandent naivement "quoi de neuf".
Ca pourrait faire le café, mais comme je n'en bois pas, je n'ai pas testé.

Mais au moins, ca permet d'avoir un firewall/IPS/UTM qui protège le réseau physique ET les machines virtuelles, tout en conservant une quantité de serveurs limitée, et si vous relisez bien le début du post, je n'avais pas promis plus !