La virtualisation est omniprésente dans tous les secteurs de l'informatique. Elle se présente sous plusieurs formes [1] (virtualisation complète, para-virtualisation, etc.). Cependant, est-il toujours nécessaire de déployer un hyperviseur pour isoler les services partageant une même machine physique ? Nous allons voir deux méthodes de confinement de processus : Jail et OpenVZ respectivement sous FreeBSD et Linux.
1. Historique
Au commencement était chroot. Chroot est un appel système : il prend comme unique argument un répertoire et y fixe la racine du système de fichiers pour le processus qui l'invoque. Il existe deux moyens pour une application de se chrooter : soit elle le fait nativement (argument sur la ligne de commande), soit elle est lancée avec la commande chroot.
Illustrons le cas d'un programme qui effectue nativement un chroot : named (un composant de la suite DNS BIND). La page de manuel de ce service mentionne un option -t qui attend un répertoire où se chrooter. S’il est utilisé dans la commande, l'argument est passé à la fonction ns_os_chroot qui contient le code suivant :
ns_os_chroot(const char *root) {
[...]
if (root != NULL) {
#ifdef HAVE_CHROOT
if (chroot(root) < 0) {
ns_main_earlyfatal("chroot(): %s", strbuf);
}
#else
ns_main_earlyfatal("chroot(): disabled");
#endif
[...]
}
}
Cette fonction effectue donc le chroot et traite les erreurs. Si on poursuit la lecture du man, un point intéressant est soulevé : « This option should be used in conjunction with the -u option, as chrooting a process running as root doesn't enhance security on most systems; the way chroot() is defined allows a process with root privileges to escape a chroot Jail ». Sur tous les systèmes (même les plus mauvais), un programme tourne sous une certaine identité associée à un jeu de privilèges. Par défaut, named tourne avec l'identité de root pour utiliser le port 53 (les ports inférieurs à 1024 sont privilégiés et ne peuvent être ouverts que par root). Or, un programme qui tourne en root dans un chroot, cela ne doit tout simplement pas arriver. Être root dans un chroot, c'est être root sur le système complet. Sur les UNIX, un appel système nommé setuid fixe l'identité (passée en paramètre) sous laquelle doit tourner le programme qui l'invoque. Justement dans le main.c de named, la fonction qui suit le ns_os_chroot est ns_os_minprivs. Cette fonction appelle dans son code source une autre fonction ns_os_changeuser qui effectue le setuid (et le setgid pour le groupe) :
ns_os_changeuser(void) {
[...]
if (setgid(runas_pw->pw_gid) < 0) {
ns_main_earlyfatal("setgid(): %s", strbuf);
}
if (setuid(runas_pw->pw_uid) < 0) {
ns_main_earlyfatal("setuid(): %s", strbuf);
}
[...]
}
Un compte utilisateur doit donc être associé à l'exécutable named. En utilisant la combinaison des deux arguments (-t racine et -u utilisateur) au lancement de la commande, on a un processus chrooté qui tourne sous un compte banalisé. Est-ce suffisant ? Dans le cas de named, oui, car le programme est développé avec un minimum de soin (ou pas selon certains [2]). Un commentaire lors de l'appel de ns_os_changeuser par ns_os_minprivs dit « Call setuid() before threads are started ». Pourquoi est-ce si important ? Le changement d'identité doit s'opérer le plus tôt possible dans l'exécution d'un programme. Sinon, une attaque de type « race condition » [3] risque de compromettre les privilèges de root (et donc l'étanchéité du chroot).
Une commande chroot existe sur les systèmes UNIX. Elle prend comme argument un répertoire et éventuellement une commande (avec ses arguments). Voyons le code source de cette commande :
if (chroot(*argv) || (chdir("/"))) {
execvp(*argv, argv);
execlp(prog, prog, NULL);
On voit donc l'appel à l'appel système chroot (comme avec named) et aussi que la partie commande est traitée par les fonctions execvp si un programme est fourni en argument ou execlp sinon. Ces deux fonctions sont des surcouches de l'appel système execve. Le programme passé en paramètre va donc recouvrir les segments de textes, les données et la pile du programme appelant. Il hérite cependant du PID (identifiant de processus) et des descripteurs en cours d'utilisation (un descripteur est un fichier ouvert). Les signaux en attente sont effacés. Le changement d'identité n'est donc pas effectué par la commande chroot : il est de la responsabilité du programme appelé.
Nous avons survolé les aspects système du chroot : voyons maintenant les implications au niveau administration système. Nous savons qu'un programme exécutable utilise des bibliothèques (les fameux .so des répertoires /lib). Pour connaître les bibliothèques utilisées par un programme, il faut utiliser ldd :
Linux-gate.so.1 => (0xb7f50000)
libncurses.so.5 => /lib/libncurses.so.5 (0xb7ef3000)
libseLinux.so.1 => /lib/libseLinux.so.1 (0xb7ed9000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7d7a000)
libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7d76000)
/lib/ld-Linux.so.2 (0xb7f36000)
Cet utilitaire donne les bibliothèques utilisées par vi. Si on voulait chrooter vi, il faudrait inclure ces fichiers ainsi que les fichiers de configuration au même endroit relatif dans le chroot. Si on ajoute à cela le fait qu'il va falloir mettre à jour le chroot, la situation devient assez compliquée. Après une mise à jour, un programme risque d'avoir besoin de nouvelles bibliothèques. D'autres, par contre, sont peut être obsolètes. En tout cas, il faut reconsidérer le chroot à chaque mise à jour. Cet aspect est traité dans de nombreux howto. Il existe même des solutions plus ou moins automatiques : happy googling !
Cette introduction à chroot n'a traité que d'un point : le confinement dans un emplacement du système de fichiers. Chroot n'est fait que pour cela. Comme le rappelle Alan Cox : « chroot is not and never has been a security tool ». Voilà c'est dit : chroot n'est pas orienté sécurité. Mis à part les éléments du système de fichiers extérieurs au chroot, le programme a accès à tout : réseau, noyau et IPC sans restrictions particulières. Ces moyens d'interactions offrent quelques biais pour sortir du chroot. Quelques possibilités de renforcement existent bien sous Linux. GRSecurity [4] se présente sous la forme d'un patch noyau et propose (entre autres) de modifier le comportement de l'appel système chroot pour y inclure un certain nombre de contrôles [5] (double chroot, envoi de signaux depuis le chroot vers l'extérieur, pivot de racine etc.). D'autres systèmes existent pour réaliser un confinement plus efficace et simple à administrer. Nous allons étudier Jail sous FreeBSD et OpenVZ sous Linux.
2. Jail
Jail est fourni avec le système FreeBSD depuis la version 4. Cet utilitaire se présente sous la forme d'un appel système pour la partie noyau et d'un jeu de commandes pour la partie utilisateur (jail, jls et jexec). À la création du jail, on a deux choix : on crée un « thin jail » ou un « fat jail ». Un « thin jail » contient juste une application (avec ses dépendances) un peu à la manière d'un chroot classique. Un « fat jail » contient un système complet. Quoi qu'il en soit, un jail est défini par la structure suivante :
Cette structure est utilisée par l'appel système jail afin d'initialiser la prison. L'attribut version contient la version de l'API utilisée par Jail (à zéro pour l'instant), le path donne la racine de la prison au niveau système de fichiers. Enfin, hostname et ip_number fixent les paramètres réseau. Déjà, on voit que, par rapport à chroot, un jail dispose de son propre environnement réseau. À la fin de son exécution, l'appel système jail renvoie un entier. Cet entier est ce que l'on appelle le JID (Jail Identifier). Ce numéro est unique pour chaque jail en cours d'exécution. La commande jls permet de les lister (on retrouve bien les informations de la structure) :
JID IP Address Hostname Path
3 192.168.89.2 jailtest.garnett.fr /jail/jailtest
2 192.168.89.3 jailfoo.garnett.fr /jail/jailfoo
1 194.168.89.4 jailbar.garnett.fr /jail/jailbar
Le système de base accueillant les jails possède le JID 0. Il lui est réservé. Si on veut faire exécuter un programme, il faut utiliser la commande jexec. Cette commande prend comme arguments un JID et un programme à exécuter dans le jail.
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 1181 0.0 0.0 3184 920 ?? SsJ Fri08AM 0:00.42 /usr/sbin/syslogd -s -s
root 1266 0.0 0.1 3212 1064 ?? IsJ Fri08AM 0:00.65 /usr/sbin/cron -s
root 8953 0.0 0.6 17356 11884 ?? SsJ 11:42AM 0:00.48 /usr/local/sbin/httpd -DNOHTTPACCEPT
www 11665 0.0 0.6 17356 11896 ?? IJ 5:03PM 0:00.00 /usr/local/sbin/httpd -DNOHTTPACCEPT
www 11687 0.0 0.6 17356 11896 ?? IJ 5:08PM 0:00.00 /usr/local/sbin/httpd -DNOHTTPACCEPT
www 11693 0.0 0.6 17356 11896 ?? IJ 5:09PM 0:00.00 /usr/local/sbin/httpd -DNOHTTPACCEPT
www 11780 0.0 0.6 17356 11896 ?? IJ 5:13PM 0:00.00 /usr/local/sbin/httpd -DNOHTTPACCEPT
www 11797 0.0 0.6 17356 11896 ?? IJ 5:16PM 0:00.00 /usr/local/sbin/httpd -DNOHTTPACCEPT
root 54636 0.0 0.1 5752 2360 ?? IsJ Fri05PM 0:00.00 /usr/sbin/sshd
On a la liste des processus (ps -auxwww) s'exécutant dans le jail ayant pour JID 3. On voit que là-dedans tournent un serveur SSH et un serveur Apache. Nous allons voir comment arriver à ce résultat.
2.1 Création du jail
Nous allons ici traiter uniquement des fat jails. Ils sont faciles à mettre en œuvre et à mettre à jour (tout ce que l'on attend d'un environnement confiné). L'utilisation des thin jails, bien qu'optimale, nous ferait retomber dans les mêmes difficultés que le chroot. À ce stade, un petit rappel s'impose. FreeBSD est composé de trois éléments : le noyau, le système de base et les ports. Le noyau gère (entre autres) les interactions avec le matériel. Le système de base comprend tous les outils de base du système (shell, compilateurs, sendmail, commandes système, bibliothèques de base, etc.). Enfin, les ports sont des portages de logiciels (souvent issus du monde UNIX) vers FreeBSD. Nous allons initialiser le jail avec le système de base. Les sources du système de base sont dans /usr/src. Si vous ne les avez pas dans ce répertoire, il faut faire un coup de cvsup [4].
2.1.1 Installation du jail (système confiné)
Nous allons commencer par rechercher les services à l'écoute sur toutes les interfaces. Pour que les services du système hôte n'interfèrent pas avec ceux du système confiné, il faut explicitement lier les services avec la ou les adresses du système de base. La commande suivante :
donne tous les services liés à *, c'est-à-dire sur toutes les interfaces. Nous avions juste SSHD dans ce cas. Une documentation en ligne assez complète liste les différents cas et les actions à effectuer [5]. Une fois les services attachés à l'IP de l'hôte et les sources récupérées, nous pouvons passer à la compilation et installation du système confiné :
$ make world DESTDIR=/jail/jailtest
La racine du jail est créée dans /jail/jailtest. La commande suivante compile le système de base. La compilation se base sur le fichier /etc/make.conf qui va donner des éléments sur ce qu'il faut compiler. On peut faire un compromis entre le thin jail et le fat jail à ce niveau. Cependant, un allègement du système de base peut avoir comme conséquence un mauvais fonctionnement des applications emprisonnées. Si vous souhaitez vous lancer dans l'optimisation du make.conf, allez voir du côté de [5].
Cette commande installe le système de base fraîchement compilé dans /jail/jailtest.
On notera qu'une installation binaire du système confiné est possible. Pour cela, il faut se procurer un CD contenant le système FreeBSD. Supposons que le CD soit monté dans /cdrom. Les commandes suivantes installent un système dans /jail/jailtest :
On monte le /dev à l'intérieur du jail.
$ cd /jail/jailtest
$ ln -s dev/null kernel
$ cp /etc/resolv.conf /jail/jailtest /etc/
Ces dernières commandes créent un fstab vide, un faux kernel et importent le resolv.conf de l'hôte. Il ne reste plus qu'à commenter la ligne adjkerntz dans /etc/crontab (le temps est synchronisé sur le système hôte) et à créer un rc.conf minimal (à l'intérieur du jail) :
hostname="jailtest.garnett.fr"
keymap="fr.iso.acc"
sshd_enable="YES"
syslogd_flags="-s -s"
sendmail_enable="NONE"
Le rc.conf initialise les variables par défaut du système de base, ainsi que les services à activer au démarrage. Ici, on initialise le hostname du jail et sa disposition clavier. On active aussi le serveur SSH et Syslog (uniquement en local avec -s -s). On désactive Sendmail. La configuration locale au jail s'arrête ici. Il faut maintenant configurer le système hôte.
2.1.2 Configuration du système hôte
La première étape est d'initialiser ce qu'il faut côté système hôte pour accueillir le jail. Nous allons créer un fat jail sur la machine hôte ayant l'IP 192.168.89.1. Nous rappelons que le répertoire accueillant le fat jail est /jail/jailtest. Ce fat jail aura l'IP 192.168.89.2. Pour la partie réseau, la création d'un alias sur l'interface réseau est nécessaire. Cet alias sera utilisé par notre jail pour communiquer sur le réseau. Dans le /etc/rc.conf de l'hôte, ajoutez :
ifconfig_bge0="inet 192.168.89.1 netmask 255.255.255.0"
ifconfig_bge0_alias0="inet 192.168.89.2 netmask 255.255.255.0"
Ensuite, il faut indiquer au système hôte les emplacements des différents jails, ainsi que la manière de les démarrer. On a deux sections pour initialiser les jails : une section globale pour l'ensemble des systèmes confinés et une section locale spécifique à chacun. Ces deux sections sont renseignées dans le rc.conf. Pour la section globale, on a les paramètres minimaux suivants :
Cette directive active Jail sur le système hôte.
liste des jails à démarrer. Ce nom sert de préfixe aux options associées à ce jail. Par exemple, pour déclarer la variable option1 à la valeur valeur1 sur jailtest, on ajoute la ligne jailtest_option1='valeur1' au rc.conf.
Cette instruction clôt la section globale en précisant la commande à exécuter au démarrage du jail. Ici, on fait exécuter le script /etc/rc au shell /bin/sh. Cette commande démarre les services installés dans le système confiné conformément aux directives du fichier /jail/jailtest/etc/rc.conf. Sous FreeBSD, le démarrage des composants de base est réalisé par le script /etc/rc. Pour le démarrage des services installés par les ports, cela se passe dans le répertoire /usr/local/etc/rc.d (si on a préalablement activé le service dans /etc/rc.conf). Nous allons maintenant voir les options minimales de la section spécifique à chaque système confiné.
Ces trois directives fixent les paramètres locaux de chaque système confiné. Nous avons respectivement le répertoire racine, le FQDN et l'adresse IP. À ce stade, le système confiné devrait démarrer au prochain reboot du système hôte. Avant le reboot, il faut tester que notre jail fonctionne en se mettant à l'intérieur. On en profitera pour faire quelques ajustements. Nous allons donc utiliser la commande jail :
Cette commande lance un shell /bin/sh à l'intérieur du jail situé dans /jail/jailtest. On doit aussi fixer les paramètres réseau (IP et FQDN). Une fois dans le jail, on affecte un mot de passe à root et on crée un utilisateur habilité à passer root (pour l'accès SSH).
La première commande donne un mot de passe à root. La seconde crée un utilisateur appartenant au groupe wheel (sous FreeBSD, seuls les membres de ce groupe peuvent faire un su pour passer root) avec un compte bloqué (-w no). La dernière commande affecte un mot de passe à l'utilisateur créé pour le débloquer. Nous avons bien rempli le rc.conf du jail. Donc, on peut rebooter la machine pour tester qu'il se lance bien au démarrage (on peut aussi utiliser le script /etc/rc.d/jail qui est fait pour manipuler les jails). Si le jail est bien lancé (un coup de jls), alors on peut passer à la post-configuration par SSH (la classe quoi !).
2.1.3 Post-configuration et exploitation
Pour exploiter notre système confiné, il faut commencer par installer les ports dessus. Plusieurs écoles pour réaliser cette opération. Nous avons retenu deux méthodes : montage NFS en read only et installation des ports dans chaque jail. Nous avons choisi la seconde version, car le stockage de quelques dizaines de Mo par jail n'est pas un problème. De plus, avoir un arbre des ports spécifique à chaque machine permet de travailler avec des versions différentes. Connectons-nous donc en SSH et installons l'arbre des ports :
Cette commande installe l'arbre des ports dans /usr/ports. Nous allons enchaîner par l'installation des outils portinstall et portupgrade :
$ cd /usr/ports/ports-mgmt/portupgrade
$ make clean install clean
Cette commande télécharge les sources de portupgrade, les compile, installe le logiciel sur le système et complète la base de données des ports installés. BSD ou comment combiner le meilleur du monde des gestionnaires de paquets avec la réactivité (et la finesse de configuration) des sources. Sous Gentoo, le système de ports est très similaire. Pour finir, installons un « vrai » service sur la machine. Apache fera l'affaire :
$ portinstall www/apache22
Activons ensuite Apache dans le rc.conf du système confiné :
$ vi /etc/rc.conf
hostname="jailtest.garnett.fr"
keymap="fr.iso.acc"
sshd_enable="YES"
syslogd_flags="-s -s"
sendmail_enable="NONE"
apache22_enable="YES"
$ /usr/local/etc/rc.d/apache22 start
Et voilà, vous avez un Apache dans un jail FreeBSD. Vous pouvez ajouter d'autres services à volonté. Il nous reste maintenant le côté exploitation. À savoir :
- mises à jour du système de base (confiné) ;
- mises à jour des ports installés ;
- audit des vulnérabilités de sécurité sur les services installés.
De plus, nous souhaitons planifier ces tâches depuis le système de base. Commençons par les mises à jour du système de base. Nous allons utiliser la commande freebsd-update qui récupère (et installe) les mises à jour du système de base. Connectons-nous au système hôte (192.168.89.1). Nous pouvons l'exécuter depuis le système de base et le faire opérer sur le système confiné par la commande suivante :
Les commandes fetch et install vont respectivement chercher les mises à jour et les installer. Le paramètre -b donne à freebsd-update la racine à partir de laquelle travailler. On peut automatiser le tout dans cron avec envoi automatique aux administrateurs (paramètres -t) si des mises à jour sont disponibles. Dans ce cas, on utilisera la commande cron à la place de fetch qui télécharge les mises à jour au bout d'un temps aléatoire par rapport au lancement de la commande pour que tous les serveurs ne demandent pas la mise à jour au même moment. Ajoutons dans la crontab :
Round 2, la mise à jour des ports. Pour cela, il faut déjà mettre à jour l'arbre des ports du système confiné. On va utiliser portsnap. Un update classique des ports se fait comme ça :
À l'instar de freebsd-update, portsnap dispose d'un argument cron que nous allons utiliser dans le crontab des systèmes confinés.
Pour vérifier que les ports de notre système sont à jour, on va utiliser portversion (issu du port ports-mgmt/portupgrade).
La première commande donne l'emplacement de la base de données des paquets à examiner par l'intermédiaire de la variable d'environnement PKG_DBDIR. La commande portversion est ensuite utilisée pour relever les paquets nécessitant une mise à jour. S’il faut en mettre à jour, on utilisera portupgrade en SSH sur le système confiné :
Cette commande réalise la mise à jour de tous les paquets (-a). Les logiciels dépendant de ces paquets sont également recompilés. Enfin, auditons les potentielles vulnérabilités du système. FreeBSD propose un outil, portaudit, qui analyse les ports installés sur le système et les confronte à une base de données propre à FreeBSD de vulnérabilités connues. Cette base est maintenue par la FreeBSD Security Team. Pour réaliser ces audits depuis le système de base, il faut d'abord installer portaudit :
On doit aussi récupérer la base connue des vulnérabilités (et scanner la machine hôte en passant) :
Ensuite, pour le lancer sur un système, il faut d'abord créer (un peu artificiellement) une liste des paquets du système à auditer. Une simple liste du contenu de /var/db/pkg du système confiné suffit :
Un coup de portaudit sur le fichier (-f) réalise l'audit :
Pour finir sur le volet exploitation, vous pensez sans doute qu'il serait judicieux d'automatiser tout cela dans un script ? C'est parfaitement jouable en utilisant le rc.conf dans votre script. Ajoutez la ligne suivante :
Et vous aurez accès à un grand nombre de variables dans votre script comme $jail_list qui contient la liste des jails. Quelques exemples à mettre dans vos scripts :
Pour faire les portaudit sur les machines. On a le même pour les portversion :
Le dernier point concerne l'aspect sécurité. D'autres paramètres existent pour affiner la configuration des jails rendant leur exécution encore plus sécurisée. Nous allons les lister dans un tableau avec une explication.
Paramètre |
Explication |
jail_set_hostname_allow = « NO » |
Empêche le root du jail de modifier le nom du système confiné |
jail_socket_unixiproute_only = « YES » |
On ne fait que du TCP/IP dans le jail. Pas d'autres protocoles. |
jail_sysvipc_allow = « NO » |
Pas d'IPC système V dans le jail. |
jail_devfs_enable = « YES » jail_devfs_ruleset = « devfsrules_jail » |
On définit un /dev minimal adapté. On a juste les terminaux, /dev/null, /dev/zero, /dev/random, /dev/urandom, /dev/crypto. |
jail_procfs_enable = « NO » |
On désactive /proc |
jail_mount_enable = « NO » |
Une autre raison de ne pas utiliser NFS pour les ports : on peut désactiver la commande mount. |
3. OpenVZ
OpenVZ prend la forme d'un patch pour le noyau Linux. Il est livré sous forme de paquets pour diverses distributions. Nous allons utiliser la version fournie pour Debian lenny. À l'instar de Jail, OpenVZ propose d'associer les processus à des environnements confinés nommés VE (Virtual Environments) ou VPS (Virtual Private Servers). De même qu'un jail FreeBSD ne peut accueillir que des systèmes confinés FreeBSD, un Linux en OpenVZ ne peut avoir que des VE Linux (éventuellement sous d'autres distributions). La grosse différence par rapport à Jail (à part l'OS sous-jacent) est que OpenVZ est très orienté « fat jail ». Dans le même esprit, on trouve aussi Linux Vserver qui a déjà fait l'objet d'articles dans GLMF [6]. Chaque VE est composé des éléments suivants :
- Fichiers : initialisation de la racine au répertoire accueillant le VE.
- Utilisateurs et groupes : chaque VE possède ses propres utilisateurs et groupes. Le root du VE est confiné au VE.
- Processus : chaque processus s'exécute dans l'espace de son VE. Il n'est pas possible pour des processus de VE différents d'interagir entre eux. Tout ce qui est IPC est également confiné au VE.
- Réseau : les VE disposent chacun d'une interface virtuelle. Dès règles iptables ou un routage spécifique au VE pourront être associés sur cette interface.
- Périphériques : chaque VE dispose de son propre /dev. Les fichiers spéciaux pourront être créés au besoin (on n’a pas de configuration minimale comme le devfsrules_jail fourni sous FreeBSD).
Au vu de cette introduction, OpenVZ semble être un jail like pour Linux. Il offre cependant des fonctionnalités supplémentaires telles que :
- Quota d'espace disque pour un VE : le seul moyen de limiter l'espace occupé par un jail est de travailler au niveau des partitions. OpenVZ propose de limiter l'espace occupé par un VE en termes d'inodes (fichiers) ou de taille selon le principe bien connu des « soft & hard limits » [7]. À l'intérieur du VE, les outils classiques de gestion de quotas sont utilisables.
- Gestion du CPU : une valeur cpuunits peut être associée à chaque VE. Cette valeur détermine le pourcentage du (ou des) processeurs utilisables par le VE. À l'intérieur du VE, les algorithmes classiques du noyau Linux sont utilisés.
- Répartition des E/S : la priorité des VE par rapport aux entrées sorties type accès au(x) disque(s) dur(s) sont configurables.
- Quotas divers : OpenVZ permet de quantifier les limites pour différentes entités du système (RAM, fichiers ouverts, taille de la mémoire partagée, etc.). Il y a plus d'une vingtaine de paramètres.
- Last but not least, il est possible de migrer un VE d'une machine physique à une autre à chaud [8] moyennant que les systèmes source et cible soient équivalents au niveau données.
On notera quand même que l'utilisation d'OpenVZ oblige à se passer de certains patchs de sécurité du noyau type GRSecurity [9]. Nous allons dérouler l'installation d'une instance OpenVZ dans une Debian Lenny [10] (maintenant stable).
3.1 Configuration du système de base
Nous allons commencer par récupérer le noyau fournissant le support de OpenVZ :
A pour effet le rajout dans /boot/grub/menu.lst :
Configurons /etc/sysctl.conf pour paramétrer le noyau (IPV4 - IPV6 – forwarding – sécurité). Par défaut, tout est commenté. Nous activerons les lignes suivantes :
Redémarrons la machine et vérifions que OpenVZ est bien actif :
OpenVZ se présente sous la forme d'un module noyau :
ifconfig fait apparaître une nouvelle interface :
3.2 Création d'un VE
Il reste à créer nos VE (environnements virtuels équivalents des machines virtuelles ou serveurs privés virtuels VPS). Pour cela, il existe des gabarits (templates) pour chaque système d'exploitation notamment pour les Debian que l'on installe :
Réalise l'installation du template de la Lenny dans /var/lib/vz/template/cache/ :
Ce template est l'arborescence de la Lenny qui sera utilisée comme VE. Il apparaît que toutes les VE et la configuration de VZ vont se trouver dans /var/lib/vz. Il convient de dédier une partition pour vos VE. Nous monterons cette partition dans /vz (/vz est un lien de /var/lib/vz).
Création d'un VE :
L'id doit être un nombre et non un nom. Cela pose quelques soucis de lisibilité. Cette commande a pour effet de créer un répertoire contenant l'arborescence de la distribution :
3.3 Configuration du VE
Il n'y a pas d'interface réseau de défini au VE 101 :
Commençons par assigner une adresse IP à notre VE 101 :
Associons-lui aussi un serveur de nom :
Lancement du VE :
Voyons la configuration réseau de notre VE 101 :
3.4 Utilisation du VE
À l'instar de Jail, les commandes sont directement exécutables dans le VE par vzctl :
Nous pouvons accéder au VE directement depuis le système hôte :
Une fois connecté au VE, on peut initialiser le mot de passe root, faire sa configuration IPTABLES ou encore installer ses packages. OpenSSH serait utile pour se connecter directement à la machine. Pour arrêter le VE :
$ vzctl stop 101
3.5 Adapter le VE à ses besoins (quotas disque, mémoire et CPU)
Comme dit dans l'introduction, OpenVZ propose de fixer un certain nombre de quotas. Commençons par les quotas sur l'espace disque. La syntaxe de vzctl est la suivante :
Cette commande fixe une limite souple et dure des quotas. Voyons comment cela fonctionne.
Avant la mise en place du quota :
Fixons une limite souple à 6G et dure à 7G :
Le / est alors limité à 6 Go. Voyons maintenant la manipulation de la mémoire allouée. Par défaut, le VE a 256 Mo :
Les changements à faire concernent plusieurs paramètres et le choix dépend d'un calcul savant en fonction des possibilités de la machine physique :
Augmentons la limite mémoire :
Nous avons alors doublé la mémoire allouée :
Pour finir, limitons le CPU (La mesure est 100% par CPU, donc, si vous avez deux CPU, vous avez 200%...) :
Conclusion sur le confinement
Cet article a dressé un panorama des différentes solutions de confinement disponibles. Nous avons commencé par le plus ancien : chroot. Retenons que l'utilisation de chroot dans un contexte sécurité est à proscrire (malgré le fait qu'un patch de sécurité nommé GRSecurity en augmente considérablement la sécurité). Sous FreeBSD, on reposera plutôt sur Jail qui propose de réaliser des « thin jails » pour confiner un programme particulier ou des « fat jails » pour confiner un système complet. Sous Linux, on pourra utiliser OpenVZ très orienté confinement de système complet. L'avantage de Jail par rapport à OpenVZ est qu'il est intégré dans le système de base de FreeBSD. En revanche, OpenVZ propose des possibilités de quotas et de migration en direct absentes de Jail. Les développeurs d'OpenVZ font beaucoup d'efforts pour rendre leur système compatible avec la libvirt [11]. Ces fonctionnalités rapprochent OpenVZ d'un système comme XEN. En tout cas, le confinement peut s'avérer une alternative intéressante à la virtualisation pour la mise à disposition de machines homogènes.
Références
[1] « La virtualisation : vecteur de vulnérabilités ou de sécurité ? », MISC 32.
[2] http://cr.yp.to/djbdns/other.html (BIND = Buggy Internet Name Daemon)
[3] http://www.cgsecurity.org/Articles/SecProg/Art5/index-fr.html
[4] http://www.freebsd.org/doc/en/books/handbook/cvsup.html
[5] http://www.section6.net/wiki/index.php/Creating_a_FreeBSD_Jail
[6] GLMF, Numéros 90 et 92.
[7] http://wiki.openvz.org/Checking_disk_quota
[8] http://wiki.openvz.org/Checkpointing_and_live_migration
[9] http://wiki.openvz.org/Grsecurity
[10] http://wiki.openvz.org/Installation_on_Debian
[11] http://libvirt.org/
À propos des auteurs
Nicolas Grenèche (aka Garnett ou Poussin), 29 ans, RSSI au Centre de Ressources Informatiques de l'Université d'Orléans et Doctorant au sein du projet SDS (Sécurité et Distribution des Systèmes). Adepte de [Open|Free]BSD.
Pascal Pautrat, 39 ans, administrateur systèmes et réseaux au Centre de Ressources Informatiques de l'Université d'Orléans. Adepte de Linux Debian et de Xen.