Copyright © 2005 Kapil Hari Paranjape
Copyright © 2005 Jean-Baka Domelevo-Entfellner
Copyright © 2005 Joëlle Cornavin
Article paru dans le n°114 de la Gazette Linux de mai 2005.
Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Table des matières
Linux 2.4.x
avait le LVM (Logical Volume Manager, gestionnaire de volumes logiques) et d'autres constructs de périphériques blocs multidisques ou multipartitions. Ceux-ci ont été améliorés par le mappeur de périphériques (device mapper) dans Linux 2.6.x
. En voici le résumé en une ligne :
Vous pouvez choisir n'importe quelle séquence de blocs sur une séquence de périphériques blocs et créer un nouveau périphérique bloc dont certains des blocs sont identifiés avec les blocs que vous avez choisis auparavant.
Cette opération prendra un certain temps. En attendant, voici quelques façons d'utiliser le mappeur de périphériques :
Quels fichiers ce programme créera-t-il ? Écrivez-les sur un disque en mode copie-sur-écriture (COW, Copy-On-Write) !
Ne laissez pas le système de fichiers partir en vacances lorsque vous prenez des instantanés.
Coupez-le en dés, en tranches, redimensionnez-le — mais ne réamorcez pas.
Si vos données valent plus que votre disque dur, chiffrez-les.
Il y a une contrepartie, naturellement. Pour faire tout cela avec le périphérique racine (root), certains changements sont nécessaires au moment de l'amorçage. Plutôt que de nous éparpiller, nous nous concentrerons sur l'apprentissage du mappeur de périphériques — à cette fin, nous utiliserons des périphériques boucles « factices » (loop devices) au lieu de disques « réels ». Quand vous aurez pris de l'assurance, vous pourrez passer aux disques réels, voire à la partition racine.
Sous Unix
, tout est fichier. Même un périphérique bloc comme /dev/hda2
, qui est censé être lu par « morceaux » appelés blocs, peut être lu octet par octet, comme un fichier. Le périphérique boucle (loop device) nous permet d'inverser cette asymétrie et de considérer n'importe quel fichier comme un périphérique bloc. Activez les périphériques boucle pour votre noyau Linux
avec modprobe boucle
(en tant que root) si nécessaire.
Pour le démontrer sans risquer d'endommager gravement des fichiers utiles, nous n'utiliserons que des fichiers vides. Tout d'abord, créez un fichier vide comme suit :
dd if=/dev/zero
of=/tmp/enregistrement1
bs=1024
seek=2047
count=1
Cette commande crée un fichier plein de vide d'une taille de 2 Mo. Transformons-le maintenant en périphérique bloc :
losetup /dev/boucle1
/tmp/enregistrement1
Nous employons ensuite ce périphérique bloc comme nous le ferions avec tout autre périphérique bloc :
Vérifiez sa taille exprimée en blocs de 512 octets :
blockdev --getsize /dev/boucle1
Créez un système de fichiers sur celui-ci :
mke2fs /dev/boucle1
Montez ce système de fichiers dans un endroit quelconque :
mount /dev/boucle1
/mnt
Vous pouvez ensuite utiliser /mnt
exactement de la même manière que tout autre système de fichiers. Les changements seront écrits dans /tmp/
. Lorsque vous en avez assez de jouer avec les blocs en boucle, débarrassez-vous en avec une commande comme losetup enregistrement1
-d
/dev/
.
boucle1
Dans ce qui suit, nous utiliserons des périphériques boucles comme /dev/
, boucle1
/dev/
, etc. à titre de briques de base de nos périphériques blocs.
boucle2/
... dit le mappeur de périphériques au périphérique bloc. S'il n'est pas déjà activé, chargez le mappeur de périphériques correspondant à votre noyau Linux avec la commande modprobe dm-mod (en tant que root). Le mappeur de périphériques peut prendre sous son aile n'importe quel périphérique bloc avec une commande comme :
echo 0 $(blockdev --getsize /dev/boucle1
) linear /dev/boucle1
0 | \
dmsetup create nouveau
Cette commande crée un « nouveau » périphérique bloc /dev/mapper/
, mais ce ne sont pas vraiment de nouvelles données. Lire à partir de cette unité de blocs donne exactement le même résulat que lire directement depuis nouveau
/dev/
. Il en va de même en ce qui concerne l'écriture sur ce périphérique bloc. Ainsi, vous pouvez vous débarrasser de ce périphérique bloc à l'aide de la commande dmsetup remove boucle1
nouveau
.
Bien sûr, vous pouvez procéder différemment. Par exemple, vous pouvez ne prendre que la moitié de /dev/
en tant que périphérique bloc :
boucle1
SIZE1=$(blockdev --getsize /dev/boucle1
)
echo 0 $(($SIZE1 / 2)) linear /devboucle1
0 | \
dmsetup create un-demi
La moitié restante (qui pourrait être la plus grosse si /dev/
contient un nombre impair d'octets !) est alors toujours disponible pour un autre usage. Vous pourriez par exemple l'employer en combinaison avec boucle1
/dev/
pour créer un autre périphérique bloc :
boucle2
REST1=$((SIZE1 - $SIZE1 / 2))
echo 0 $REST1 linear /dev/boucle1
$((SIZE1 / 2)) > /tmp/table1
echo $REST1 $(blockdev --getsize /dev/boucle2
) \
linear /dev/boucle2
0 >> /tmp/table1
dmsetup create un-et-demi
/tmp/table1
Essayons de comprendre cet exemple et la signification de chacun des trois nombres qui apparaissent sur chaque ligne du fichier /tmp/table
. Le premier nombre est le secteur de démarrage de la correspondance décrite, le deuxième est le nombre de secteurs dans la correspondance. Le mot linear
est suivi du nom du périphérique d'origine auquel se réfère la correspondance. Vient ensuite le numéro de secteur du premier secteur (de ce périphérique d'origine) qui est affecté par cette correspondance. Relisez cela encore une fois !
Ainsi, vous pouvez découper et recomposer vos disques à l'envi, mais il y a un léger coût, évidemment. Toutes les opérations effectuées sur ces nouveaux périphériques bloc passent au travers du mappeur de périphériques plutôt que de se rendre directement dans le matériel sous-jacent. Avec une gestion efficace des tables dans le noyau, ce surcoût ne devrait pas ralentir les choses de façon perceptible.
Notez comment j'ai (habilement, n'est-ce pas ?) introduit le sujet sur l'utilisation des « tables » qui contiennent la description des périphériques mis en correspondance. Si vous envisagez de beaucoup utiliser les périphériques mis en correspondance et ne voulez pas oublier vos paramètres, vous ne ferez pas l'économie de telles tables. Ne vous inquiétez pas — vous pouvez toujours obtenir la table d'un périphérique quelconque comme /dev/mapper/nouveau
par la commande suivante :
dmsetup table nouveau
Dans la sortie, comme le périphérique bloc d'origine apparaîtra sous la forme majeur
:mineur
, vous devrez découvrir quel est le périphérique effectivement appelé si vous avez besoin de cette table dans un format humainement lisible.
Essayez la commande suivante (ou quelque chose d'approchant) :
ls -l /dev | grep "$majeur
, *$mineur
"
Quand vous avez fini, n'oubliez pas d'exécuter la commande suivante :
dmsetup remove un-demi
dmsetup remove un-et-demi
Peut-être êtes-vous l'une de ces personnes qui ont plusieurs disques configurés, de sorte qu'il est plus lent de lire n
octets à partir de l'un d'entre eux que de lire n/2
octets à partir de deux d'entre eux. Cela peut arriver parce que votre contrôleur de disque est capable de réaliser des opérations sur plusieurs disques en parallèle ou bien parce que vous avez plusieurs contrôleurs de disque. Le mappeur de périphériques peut vous aider à accélérer vos opérations :
SIZE=$(( $(blockdev --getsize /dev/boucle1
) + \
$(blockdev --getsize /dev/boucle2
) ))
echo 0 $SIZE striped 2 16 /dev/boucle1
0 /dev/boucle2
0 | \
dmsetup create tigre
Dorénavant, les lectures/écritures depuis /dev/mapper/
se succéderont (par blocs de 16 secteurs) entre les deux périphériques. Vous aurez de plus combiné les deux disques en un seul comme dans le cas avec tigre
linear
.
Un certain nombre de raisons peuvent vous amener à interrompre toutes les opérations d'écriture sur votre périphérique bloc, sans que votre système ne s'arrête définitivement.
Il fut un temps où les machines étaient placées en mode mono-utilisateur le temps d'effectuer des sauvegardes. Toutefois, sauvegarder un système « vivant » présente le risque d'avoir des fichiers incomplets ou corrompus, voire des pointeurs temporels erronés.
Vous voulez installer cet économiseur d'écran aux couleurs vives. Quels changements va-t-il faire subir à votre système de fichiers ? Vous voulez savoir.
... et autres périphériques physiques en lecture seule. Supposons que vous vouliez installer votre système sur un cédérom, tout en autorisant néanmoins des changements locaux « éphémères » qui sont abandonnés lorsque le système redémarre.
La solution réside dans la redirection. En pratique, vous indiquez aux processus « Regardez derrière vous ! » et, en un clin d'œil, vous rajoutez une couche entre le processus et le périphérique. Activez la fonctionnalité d'instantané du mappeur de périphériques avec modprobe dm-snapshot si nécessaire.
Commençons alors avec un périphérique géré par le mappeur de périphériques. Il pourrait par exemple être créé par les commandes suivantes :
SIZE1=$(blockdev --getsize /dev/boucle1
)
SIZE2=$(blockdev --getsize /dev/boucle1
)
cat > /tmp/table2
<<EOF
0 $SIZE1 linear /dev/boucle1
0
$SIZE1 $SIZE2 linear /dev/boucle2
0
EOF
dmsetup create base
/tmp/table21
Supposons maintenant que vous ayez placé un système de fichiers sur ce périphérique avec une commande comme mke2fs /dev/mapper/
et que vous ayez commencé à utiliser ce système de fichiers au point de montage base
/mnt
avec la commande mount /dev/mapper/
base
/mnt
.
Prenons à présent un « instantané » de ce système de fichiers — au ralenti ! Les étapes suivantes doivent être effectuées assez rapidement (par exemple avec un script) sur un système qui tourne et où les changements sur le système de fichiers sont fréquents.
Avant tout, créez une copie de ce disque. Ce n'est pas seulement pour des raisons de sécurité, puisque nous allons changer la signification de /dev/mapper/
sans en avertir le système de fichiers :
base
dmsetup table base
| dmsetup create base
Préparons ensuite notre périphérique bloc en copie-sur-écriture en nous assurant que les huit premiers secteurs (ou toute autre valeur que vous choisirez comme étant la taille de vos blocs) sont remplis de zéros :
CHUNK=8
dd if=/dev/zero
of=/dev/boucle3
bs=512
count=$CHUNK
Maintenant, nous suspendons toutes les opérations d'E/S (lecture/écriture) sur le périphérique base
. C'est l'étape critique pour tout système actif. Le noyau devra mettre en sommeil[1] tous les processus qui tentent de lire depuis, ou écrire vers, ce périphérique. Nous voulons donc être sûrs de pouvoir reprendre ces opérations rapidement :
dmsetup suspend base
&& DATE=$(date)
L'étape suivante consiste à utiliser la copie-sur-écriture pour cloner le périphérique :
echo 0 $(blockdev --getsize /dev/mapper/basedup
) \
snapshot /dev/mapper/base
/dev/boucle3
p 8 | \
dmsetup create top
Ces lignes signifient qu'à partir de maintenant, un accès en lecture depuis /dev/mapper/top
renverra les données provenant de /dev/mapper/
, à moins que vous n'écriviez « par-dessus » les données d'origine. Les accès en écriture sur basedup
top
seront en fait réalisés sur /dev/
par blocs d'une taille de 8 secteurs. L'effet est le même que si vous utilisiez plusieurs feuilles de plastique transparent superposées (appelées « calques » dans Gimp) ‐ ce qui est écrit par-dessus occulte ce qui est au-dessous, mais là où rien n'est écrit par-dessus, vous voyez clairement ce qu'il y a sur la feuille du dessous.
boucle3
En particulier, nous pouvons maintenant nous assurer que tous les changements apportés aux périphériques blocs sous-jacents sont « volatils ». Si nous exécutons les commandes suivantes (par la suite, nous ferons référence à ce point en tant que 'point A') :
dmsetup table top | dmsetup load base
dmsetup resume base
nous aurons remplacé le système de fichiers situé sous /mnt
par un autre, où tous les changements s'insèrent en fait dans /dev/
. Lorsque nous démontons cette configuration, boucle3
/dev/
et boucle1
/dev/
seront exactement dans l'état où ils étaient à la date boucle2
$DATE
.
Si /dev/
et boucle1
/dev/
sont sur des supports physiques non inscriptibles (par exemple un cédérom), alors que boucle2
/dev/
est sur un support inscriptible (par exemple une mémoire vive ou un disque dur), alors nous avons créé un système de fichiers inscriptible à partir d'un autre en lecture seule !
boucle3
Ceci répond au dernier problème de notre liste ci-dessus. Mais quid des deux premiers ? Pour traiter le deuxième problème, nous devons disposer d'un moyen quelconque pour comparer le nouveau système de fichiers avec l'ancien. Si pour ce faire, vous essayez de monter /dev/mapper
quelque part, vous constaterez que basedup
Linux
(le noyau !) vous l'interdit. À la place, nous pouvons créer encore un autre périphérique :
echo 0 $(blockdev --getsize /dev/mapper/basedup
) \
snapshot-origin /dev/mapper/basedup
| \
dmsetup create originel
Vous pouvez maintenant monter /dev/mapper/
quelque part (par exemple originel
/tmp/
) et comparer le système de fichiers originel avec l'actuel à l'aide d'une commande comme :
orig
diff -qur /tmp/orig
/mnt
Que se passe-t-il si vous écrivez sur /tmp/
? Vérifiez, et vous n'en reviendrez pas.
orig
L'analogie avec les transparents s'arrête ici ! Tous les accès en écriture sur /tmp/
vont directement vers le périphérique sous-jacent orig
basedup
mais sont niés sur /dev/
, de telle sorte qu'ils deviennent invisibles aux lectures depuis boucle3
/mnt
. De la même manière, les opérations de lecture depuis /tmp/
ignorent tout changement apporté par une écriture sur orig
/mnt
. En d'autres termes, le système de fichiers originel a été « copié » (forked), orthogonalement, qui plus est ! et /dev/
qui en fait stocke à la fois les données en positif et en négatif. Impossible de créer un transparent qui fonctionne ainsi !
boucle3
Pour vérifier l'utilité de ce comportement, regardons comment il résoud le problème des sauvegardes. Ce que nous voulons, c'est obtenir un « instantané » du système de fichiers, tout en continuant à utiliser le système de fichiers originel. Dans ce cas, nous ne devrons donc pas exécuter les commandes au point A ci-dessus. À la place, exécutons les commandes suivantes ici, au point B :
dmsetup table originel
| dmsetup load base
dmsetup resume base
Désormais, tous les accès en écriture sur /mnt
vont vers le périphérique originel, mais ces changements sont niés sur /dev/mapper/top
. Donc, si nous montons ce dernier sur, par exemple /tmp/snap
, alors nous pouvons lire un instantané des fichiers à l'heure $HEURE
depuis ce répertoire. Une commande comme :
cd /tmp/snap
find . -xdev | cpio -o -H nouveau
> "sauvegarde-$HEURE
"
fournira une sauvegarde de l'instantané du système de fichiers à l'heure $HEURE
.
Nous aurions aussi pu prendre un instantané au point A avec les commandes :
cd /tmp/orig
find . -xdev | cpio -o -H nouveau
> "sauvegarde-$HEURE
"
La principale différence est que les changements apportés à /dev/mapper/top
sont volatils ! Il n'y a aucun moyen de démonter facilement la structure créée au (A) sans perdre toutes les changements effectués. Dans le contexte d'une sauvegarde, vous voulez retenir les changements. Au point B, exécutez :
dmsetup suspend base
dmsetup remove top
dmsetup remove originel
dmsetup table basedup | dmsetup load base
dmsetup resume base
et vous pouvez reprendre votre travail comme d'habitude. Si vous le faisiez au point A, les résultats seraient complètement imprévisibles ! Quel serait l'état de tous ces fichiers ouverts sur /dev/mapper/top
? Un certain nombre de processus seraient très probablement suspendus — il se pourrait même que certains threads du noyau se bloquent — et même « plantent » !
Supposons que vous ayez un ordinateur portable ou un cédrom qui contient des données importantes, non seulement à vos yeux mais pour quiconque les détient. Dans ce cas, des sauvegardes ne sont pas souhaitables. Ce que vous voulez, c'est protéger ces données contre le vol. En partant du principe que vous croyez en la robustesse des techniques de chiffrement actuelles, vous pourriez les protéger en chiffrant le fichier en question. Cette approche présente de graves défauts :
Le fichier doit être déchiffré/rechiffré chaque fois que vous voulez vous en servir.
Le fichier chiffré se singularise trop, facilitant ainsi les recherches de quiconque voudrait voler vos donnés.
Il se peut que vous ne sachiez pas précisément quels fichiers contiennent des données secrètes. Par exemple, vous préparez un rapport confidentiel et vous l'envoyez vers l'imprimante. Pour ce faire, un fichier PDF temporaire a été créé à votre insu et vous ne l'avez pas chiffré.
Pour ces raisons et pour tant d'autres, vous pourriez être amené à chiffrer la totalité du périphérique bloc. Le mappeur de périphériques offre un moyen de le faire. Activez le service de chiffrement du mappeur de périphériques avec modprobe dm-crypt si nécessaire. Si besoin est, activez également certains mécanismes de chiffrement et de hachage avec des commandes comme modprobe md5 ou encore modprobe aes.
Tout d'abord, vous devez générer et enregistrer votre clef secrète. Si vous faites appel à AES (Advanced Encryption Standard) comme indiqué ci-dessus, vous pouvez utiliser une clef d'une longueur allant jusqu'à 32 bits. Pour pouvoir la générer, saisissez par exemple :
dd if=/dev/random bs=16 count=1 | \
od --width=16 -t x2 | head -1 | \
cut -f2- -d' ' | tr -d ' ' > /tmp/ma_clef_secrète
Naturellement, vous ne devrez certainement pas écrire votre clef secrète dans un tel fichier — il y a des moyens plus sûrs pour l'enregistrer :
Affichez-la directement à l'écran et efforcez-vous de la retenir ! Après tout, ce ne sont que de trente-deux caractères !).
Écrivez-la sur une clé USB
ou tout autre périphérique de stockage qui ne quittera jamais votre poche.
Chiffrez-la à l'aide de gpg (GnuPG) ou d'openssl (Secure Sockets Layer) pour l'enregistrer ensuite sur une clé USB ou tout autre mémoire que vous gardez toujours sur vous.
Si vous choisissez la troisième option, vous devrez utiliser une phrase de passe — vous devrez la retenir également. Un bon moyen de s'en rappeler est de l'utiliser souvent : utilisez-la... ou prenez le risque de l'oublier !
Configurez à présent le périphérique chiffré :
echo 0 $(blockdev --getsize /dev/boucle1
) \
crypt aes-plain $(cat /tmp/ma_clef_secrète
) 0 /dev/lboucle1
0 | \
dmsetup create mes-données
Créez ensuite un système de fichiers mke2fs /dev/mapper/
sur ce périphérique bloc et stockez-y des données après l'avoir monté quelque part avec mount mes-donnees
/dev/mapper/
mes-donnees
/mnt
. Toutes les données écrites sur /mnt
seront chiffrées de façon transparente avant stockage sur /dev/
. Quand vous avez fini, démontez le périphérique et désinstallez-le comme précédemment :
boucle1
umount /mnt
dmsetup remove mes-donnees
La prochaine fois que vous voudrez utiliser ce périphérique, vous pourrez l'installer avec les mêmes commandes que ci-dessus, sous réserve de fournir la clef secrète dans /tmp/
. Bien sûr, vous ne devrez pas exécuter mke2fs une seconde fois, à moins de vouloir effacer toutes ces précieuses données !
ma-clef-secrete
Vous pouvez réaliser toutes les étapes décrites ci-dessus sur n'importe quel(s) périphérique(s) bloc(s), à la place des périphériques boucle (loop devices) que nous avons fait intervenir. Cependant, quand le périphérique en question est la partition racine, les choses se compliquent un peu (les racines sont généralement complexes).
Avant tout, nous devons placer le périphérique racine sous le contrôle du mappeur de périphériques. La meilleure façon de procéder est de faire appel à un disque virtuel (RAM disk) initial (ou initrd). Même après cela, la prudence s'impose si nous essayons d'exécuter une des commandes ci-dessus sur la partition racine d'un système « actif ». En particulier, il n'est pas recommandé de suspendre les entrées/sorties sur le système de fichiers sur le fichier racine sans une profonde introspection préalable ! Quoi qu'il en soit, cela signifie que tous les processus qui créent un appel en lecture/écriture sur le système de fichiers racine seront mis en sommeil.
Voici un moyen de contourner cette difficulté. Créez un système de fichiers temporaire :
mount -t tmpfs tmpfs /mnt
Sur ce système de fichiers, copiez ensuite tous les fichiers nécessaires afin d'effectuer les changements. En particulier, vous aurez besoin de /sbin/dmsetup
, de /bin/sh
, des fichiers /dev
ainsi que de toutes les bibliothèques partagées dont dépendent ces programmes. Exécutez ensuite chroot /mnt. Après quoi vous pourrez lancer un script ou bien (à condition que vous tapiez rapidement et sans fautes !) une suite de commandes qui suspendront la liaison avec la racine et exécuteront les opérations souhaitées, par exemple en prenant un instantané du disque. N'oubliez pas de remettre en route la partition racine avant de sortir du chroot.
Compte tenu de la complexité des différentes opérations, la meilleure solution est probablement d'écrire un script shell ou même un programme C qui réaliserait lesdites opérations. Fort heureusement, la seconde solution a déjà été implémentée, puisque le Gestionnaire de Volumes Logiques version 2 (LVM2) réalise effectivement la plupart des tâches décrites ci-dessus de manière assez « automagique ». La mise en place et l'utilisation du chiffrement se trouve grandement simplifiée par l'utilisation du programme cryptsetup. Mais alors pourquoi ai-je donc écrit cet article ?
Au début, je suis tombé sur dmsetup alors que j'essayais de créer un système de fichier racine en lecture seule, pour un « live CD ». Malheureusement, les outils LVM2 ne sont alors d'aucune utilité puisqu'ils ne sont là que pour l'utilisation d'instantanés à des fins de sauvegarde (clairement, il n'ont que faire des systèmes de copie-sur-écriture) ! La seule ressource que j'ai trouvé à ce sujet se trouve dans les archives de la liste de diffusion RedHat. On trouve aujourd'hui des outils fournis avec des « live CD » qui font usage de dmsetup ; par exemple je suis tombé sur ce lien, qui explique comment cela marche chez Ubuntu.
Et bien sûr, l'utilisation de dmsetup m'a permis d'aller « aussi près que possible » du matériel sans toutefois écrire de vrais programmes...
Kapil Hari Paranjape a l'âme d'un « hacker » depuis l'époque des cartes perforées. Plus précisément, cela veut dire qu'il n'a jamais écrit de « véritable » programme. Il s'est contenté d'expérimenter des programmes écrits par d'autres. Après avoir joué avec
Minix
en 1990-91, il a pensé écrire son premier logiciel — un « authentique » noyau*nix
pour la classe d'ordinateurs x86. Heureusement pour lui, un certain Linus Torvalds l'a devancé — lui évitant ainsi la peine (une fois de plus) d'écrire effectivement du code. En signe de gratitude éternelle, il a passé beaucoup de temps à expérimenter et promouvoir le systèmeLinux
et le projet GNU depuis lors, au grand dam de la plupart de son entourage, qui pense qu'il devrait se concentrer sur la recherche en mathématiques — qui est son gagne-pain. L'interaction entre les programmes s'exécutant réellement, ce qui peut être calculé en théorie et ce dont on peut prouver l'existence, continuent de le fasciner.
L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.
Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.
Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.