Déçu par mon précédent hébergeur, je décidais d'opter pour un hébergeur allemand proposant une configuration de serveur dédié alléchante :
- CPU : i7-920 Quadcore ;
- RAM : 8 GB DDR3 ;
- HDD : 2 x 750 GB SATA II ;
- Carte Gigabit Ethernet.
(N.B : A l'époque où j'ai souscrit à cette offre, il n'y avait pas « moins » ;))
1. Avant-propos
Cet article a pour but de montrer un exemple d'installation de FreeBSD non proposé par l'installeur (sysinstall). Il ne couvre pas la description du système de fichiers ZFS déjà discuté dans le numéro 114 de GLMF.
2. Prérequis
L'hébergeur ne propose pas FreeBSD comme OS à l'installation. Toutefois, il propose des services additionnels comme le gravage sur CD/DVD de la distribution de votre choix, pour pouvoir booter dessus et procéder à l'installation via KVM/IP (autre service proposé à la location), ou encore l'ajout d'une clé USB de 16 ou 32 Go sur le serveur. Tous ces services étant payants, je me suis limité au strict nécessaire, à savoir une clé USB de 16 Go qui pourra me rendre d'autres services par la suite [1]. Comme l'hébergeur propose un mode « rescue » FreeBSD 7.2 que l'on peut activer par l'interface web d'administration, et que cette fonctionnalité est gratuite, je suis parti de ce mode pour procéder à l'installation.
2.1 Pourquoi une clé USB ?
Il s'agit d'une installation ZFSonRoot mirrorée, ce qui veut dire que même le basesystem est en ZFS et pourra faire l'objet de snapshots (très pratique pour les « rollbacks » en cas de régressions suite à une mise à jour, pour les fichiers de configuration sous /etc, /usr/local/etc, etc.) et l'ensemble de ce système sera en RAID1.
Comme les disques doivent directement être configurés en miroir ZFS avec support du zfsboot dans le MBR de chacun de ces disques et que le mode rescue 7.2 offert par l'hébergeur ne dispose pas des modules opensolaris.ko, zfs.ko et encore moins de zfsboot, il faut les récupérer… Or on ne peut pas les stocker sur les disques. La clé USB était donc une solution de contournement.
3. La pose du miroir
3.1 Préparer ses outils...
Le wiki Freebsd [2] propose une procédure détaillée pour faire du ZFSonRoot mirroré, seulement voilà, elle nécessite de booter sur une version 8 de FreeBSD, or l'hébergeur en question ne propose qu'une version 7.2 « Rescue » en amorce. Mon pote Bapt [3], lui, propose une installation full ZFS mono-disque et surtout une préparation du MBR [4] possible depuis une version 7.2 (ce qui n'est pas le cas de la méthode du wiki FreeBSD). Nous allons donc faire un mix des deux.
Tout d'abord, on démarre le serveur en mode rescue 7.2 amd64 via l'interface de gestion de l'hébergeur, la clé USB de 16 Go a déjà été commandée et est branchée sur le serveur. Ce mode « Rescue » est accessible en SSH, on s'y connecte donc.
On formate la clé USB en UFS2 et on monte cette dernière :
# dd if=/dev/zero of=/dev/da0 count=2
# disklabel /dev/da0 | disklabel -B -R -r da0 /dev/stdin
Une seule partition suffit, on peut alors utiliser la slice c :
# newfs /dev/da0c
On crée un point de montage pour la clé USB et on la monte :
# mkdir /cle_usb
# mount /dev/da0c /cle_usb
# cd /cle_usb
Optionnel : si comme moi, vous n'avez pas de répertoire /boot/zfs, alors créez-le :
# mkdir /boot/zfs
On récupère les ISO 7.2-RELEASE-amd64-livefs.iso et 8.0-RELEASE-amd64-dvd1.iso.gz. On prend le DVD de la version 8.0 car cet ISO contient les « sets » que l'on va installer en ligne de commandes (et non via sysinstall).
Comme le CD rescue basé sur FreeBSD 7.2 ne dispose pas des modules ZFS (zfs.ko et opensolaris.ko), on récupère le livefs de la version 7.2 pour pouvoir charger ces modules (j'aurai très bien pu ne récupérer que ces modules sur le dépôt subversion de FreeBSD, mais ne sachant pas s' il fallait des dépendances et ayant 16 Go d'espace sur la clé USB, j'ai écouté la loutre qui sommeille en moi).
# fetch ftp://ftp.freebsd.org/pub/FreeBSD/ISO-IMAGES-amd64/7.2/7.2-RELEASE-amd64-livefs.iso
# fetch ftp://ftp.freebsd.org/pub/FreeBSD/ISO-IMAGES-amd64/8.0/8.0-RELEASE-amd64-dvd1.iso.gz
# gunzip 8.0-RELEASE-amd64-dvd1.iso.gz
On crée 2 points de montage et on monte les ISO via mdconfig :
# mkdir /livefs7
# mkdir /dvd8
# mdconfig -a -t vnode -f /cle_usb/7.2-RELEASE-amd64-livefs.iso -u 4
# /mnt2/sbin/mount_cd9660 /dev/md4 /livefs7/
# mdconfig -a -t vnode -f /cle_usb/8.0-RELEASE-amd64-dvd1.iso -u 5
# /mnt2/sbin/mount_cd9660 /dev/md5 /dvd8/
Sur le CD rescue comme en mode livefs, il faut utiliser les commandes mount du répertoire /mnt2/sbin/.
On charge les modules nécessaires à ZFS tant attendus :
# kldload /livefs7/boot/kernel/opensolaris.ko
# kldload /livefs7/boot/kernel/zfs.ko
et on vérifie que les modules sont bien chargés :
# kldstat
Id Refs Address Size Name
1 17 0xffffffff80100000 d17dc0 kernel
2 1 0xffffffff80e18000 18fb40 zfs.ko
3 2 0xffffffff80fa8000 3868 opensolaris.ko
[...]
A ce stade, nous avons tous les outils pour installer notre système, un dernier repérage des disques disponibles… :
# ls /dev/ad*
/dev/ad4 /dev/ad6
3.2 Et on se lance ...
3.2.1 Création du pool ZFS
On crée notre pool ZFS (ou zpool) que l'on appelle ici zroot mirroré :
# zpool create zroot mirror /dev/ad4 /dev/ad6
On met le pool hors service afin de lui tailler un MBR sur mesure... :
# zpool export zroot
Nombre de documentations évoquent « tank » comme nom de zpool par défaut. Seulement ayant déjà dû importer un zpool « tank » FreeBSD sur un OpenSolaris disposant déjà un zpool « tank » (nom par défaut), il faut renommer le pool avant import dans OpenSolaris. Seulement voilà, quand « tank » est la racine système du FreeBSD, il faut passer par un live CD supportant ZFS pour renommer le pool à son nom d'origine pour pouvoir le réintégrer dans FreeBSD et booter dessus. J'ai donc décidé de bannir ce nom de pool par défaut dans toutes mes installations ZFS.
3.2.2 ZFS-isation du MBR
Nous ajoutons le zfsboot dans le Master Boot Record, afin que le système puisse booter sur une partition ZFS. Comme il s'agit d'un miroir, il faut faire cette manipulation sur les 2 disques :
# dd if=/dvd8/boot/zfsboot of=/dev/ad4 bs=512 count=1
# dd if=/dvd8/boot/zfsboot of=/dev/ad4 bs=512 skip=1 seek=1024
# dd if=/dvd8/boot/zfsboot of=/dev/ad6 bs=512 count=1
# dd if=/dvd8/boot/zfsboot of=/dev/ad6 bs=512 skip=1 seek=1024
Et on réactive le pool :
# zpool import zroot
3.2.3 Création des volumes
On crée un FS de swap de 4 Go et on indique qu'il faut l'activer au boot :
# zfs create -V 4g zroot/swap
# zfs set org.freebsd:swap=on zroot/swap
Le « checksumming » est une des grandes fonctionnalités proposées par ZFS, il permet de détecter et de corriger plusieurs types d'erreurs que bien d'autres produits ne savent pas détecter. C'est pourquoi nous utilisons l'algorithme de somme de contrôle « fletcher » version 4, qui est plus robuste que la version 2 utilisée par défaut par ZFS.
# zfs set checksum=fletcher4 zroot
Par contre, nous désactivons la somme de contrôle pour la partition de swap :
# zfs set checksum=off zroot/swap
Ensuite, nous créons notre hiérarchie de système de fichiers :
# zfs create zroot/usr
# zfs create zroot/usr/home
# zfs create zroot/var
Dans les répertoires « systèmes » qui suivent, nous activons ou non la compression par défaut, à savoir l'algorithme de compression « lzjb » fournissant un bon compromis entre rapidité et taux de compression, nous activons ou non le bit SUID.
# zfs create -o compression=on -o setuid=off zroot/tmp
Pour /tmp, on empêche quiconque de supprimer ce répertoire ou de modifier ses droits en y apposant le « sticky bit ».
# chmod 1777 /zroot/tmp
Egalement, on active ou non l'exécution de processus depuis le système de fichiers voulu :
# zfs create -o exec=off -o setuid=off zroot/var/db
# zfs create -o exec=off -o setuid=off zroot/var/empty
# zfs create -o compression=on -o exec=on -o setuid=off zroot/var/db/pkg
# zfs create -o compression=on -o exec=off -o setuid=off zroot/var/log
# zfs create -o compression=on -o exec=off -o setuid=off zroot/usr/src
3.2.4 Installation de FreeBSD 8.0-RELEASE depuis les sets
On se place dans le répertoire contenant les sets du DVD 8.0-RELEASE :
# cd /cle_usb/8.0-RELEASE
On définit la variable d'environnement DESTDIR, définissant l'emplacement dans lequel les sets seront installés (dans notre cas zroot, la racine de notre système) :
# export DESTDIR=/zroot
et on installe les sets « base », « catpages », « dict », « doc », « info », « lib32 », « manpages », « games » (oui j'ai bien dit « games », car si vous n'avez pas fortunes, alors vous ratez quelque chose).
# for dir in base catpages dict doc games info lib32 manpages;
do (cd $dir ; ./install.sh); done
# cd src; ./install.sh all
# cd ../kernels; ./install.sh generic
# cd /zroot/boot; cp -Rlp GENERIC/* /zroot/boot/kernel/
Certains démons ont besoin d'un environnement « chrooté » non accessible en écriture lorsqu'ils procèdent à une séparation des privilèges au cours de leur processus d'exécution. C'est le cas de sshd avec /var/empty :
# zfs set readonly=on zroot/var/empty
Jusqu'ici, nous avons « préparé » notre OS depuis le CD de rescue 7.2. Passons maintenant à l'étape de post installation.
3.3 Post Installation
On chroot dans notre racine zroot :
# chroot /zroot
Puis on procède aux configurations minimales d'usage...
3.3.1 Au niveau rc.conf
On active la gestion du ZFS :
# echo 'zfs_enable="YES"' > /etc/rc.conf
On indique un nom d'hôte :
# echo 'hostname="vasc0.ixsys.fr"' >> /etc/rc.conf
On configure l'unique interface avec son adresse IP publique afin que le serveur dédié soit accessible sur le grand Nain Ternet :
# echo 'ifconfig_re0="inet mon.adresse.ipv4.publique netmask 255.255.255.192"' >> /etc/rc.conf
On active le démon ssh :
# echo 'sshd_enable="YES"' >> /etc/rc.conf
On configure le clavier pour la console en français :
# echo 'keymap=fr' >> /etc/rc.conf
On positionne la vitesse de rémanence des touches à rapide :
# echo 'keyrate=fast' >> /etc/rc.conf
3.3.2 Au niveau loader.conf
On active le module ZFS au boot :
# echo 'zfs_load="YES"' > /boot/loader.conf
On indique quelle sera la racine ZFS à monter au boot :
# echo 'vfs.root.mountfrom="zfs:zroot"' >> /boot/loader.conf
3.3.3 Au niveau src.conf
On précise au chargeur de démarrage (bootloader) qu'il doit gérer le boot sur ZFS (Ceci est nécessaire uniquement pour les versions 8.0-RELEASE et 7.{0-2}-RELEASE )
# echo 'LOADER_ZFS_SUPPORT=YES' > /etc/src.conf
3.4 Finalisation
Facultatif : on crée un lien symbolique pour /home :
# ln -s /usr/home /home
On monte le système de fichiers pour les périphériques (devfs) :
# mount -t devfs devfs /dev
Comme nous avons « chrooté », nous redéfinissons la variable d'environnement DESTDIR précédemment paramétrée :
# export DESTDIR=""
Puis on recompile le chargeur de démarrage (Stage 3 du processus de boot [5]) :
# cd /usr/src/sys/boot/
# make obj
# make depend
# make
# cd i386/loader
# make install
Avec FreeBSD 8.0-RELEASE, la recompilation du loader est nécessaire, elle ne l'est plus pour les versions 7.2-STABLE, 8.0-STABLE à partir du 07/12/2009.
On indique un mot de passe à root :
# passwd
On crée un groupe sidh et un utilisateur sidh membre du groupe wheel et on indique un mot de passe :
# pw groupadd sidh
# pw useradd sidh -g sidh -h -G wheel -m -s /bin/sh
password for user sidh:
Facultatif mais conseillé : on crée un répertoire .ssh et un fichier authorized_keys dans lequel on copie/colle notre clé publique SSH :
# su - sidh
$ mkdir .ssh
$ vi .ssh/authorized_keys
(copier/coller de la clé publique ssh)
On repasse en root :
$ exit
On interdit les connexions SSH en tant qu'utilisateur root, on active l'authentification par clé publique dans le fichier de configuration /etc/ssh/sshd_config, en activant/modifiant les options suivantes :
PermitRootLogin no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
On peut aussi interdire l'authentification par mot de passe (et ainsi obliger l'utilisateur à utiliser une clé) en indiquant l'option suivante dans le sshd_config :
PasswordAuthentication no
Pour ma part, je ne l'ai pas activé car certains amiswWwclients se connectent à leur jails [6] en SSH et n'utilisent pas de clés.
On crée un fichier fstab vide. En effet, son absence peut perturber et certains outils peuvent couiner au démarrage :
# touch /etc/fstab
On configure le fuseau horaire voulu :
# tzsetup
On démonte /dev et on sort du chroot :
# umount /dev
# exit
Nous voilà donc revenus en mode « rescue » (plus ou moins équivalent à un mode fixit# d'un live CD FreeBSD), où il nous reste les ultimes étapes de configuration avant le reboot final.
Important : il ne faut pas oublier de copier le zpool.cache (qui est généré lors de l'import/export du pool ZFS) dans son emplacement ZFS équivalent :
# cp /boot/zfs/zpool.cache /zroot/boot/zfs/zpool.cache
On définit la variable d'environnement contenant le chemin des bibliothèques partagées LD_LIBRARY_PATH, en mode rescue ou livefs, il s'agit de /mnt2/lib :
# export LD_LIBRARY_PATH=/mnt2/lib
On défait tous les points de montage ZFS :
# zfs unmount -a
Il faut préciser que notre pool « zroot » sera géré directement par le gestionnaire de démarrage et que du coup, ZFS n'a pas à s'occuper de son montage et démontage. Ceci se fait en précisant la propriété legacy au point de montage :
# zfs set mountpoint=legacy zroot
On affecte les points de montage dans le pool « zroot » :
# zfs set mountpoint=/tmp zroot/tmp
# zfs set mountpoint=/usr zroot/usr
# zfs set mountpoint=/var zroot/var
and That's all Folks !!!
Il ne vous reste plus qu'à réciter un mantra en pulvérisant du rhum agricole sur votre clavier entre chaque couplet, et taper :
# reboot
ou déclencher le reboot à distance du serveur via l'interface de gestion web de l'hébergeur…
Connectez-vous en SSH sur le serveur et savourez le restant de votre rhum agricole devant la récompense [7] :
-=[sidh@vasc0]=(~)# zpool status
pool: zroot
state: ONLINE
scrub: none requested
config:
NAME STATE READ WRITE CKSUM
zroot ONLINE 0 0 0
mirror ONLINE 0 0 0
gpt/disk0 ONLINE 0 0 0
gpt/disk1 ONLINE 0 0 0
errors: No known data errors
Je tiens à rappeler que cette configuration ne tient en aucun cas de système de sauvegarde, il s'agit d'un système de tolérance aux pannes (minime certes, mais bien utile). Les sauvegardes devront se faire sur un support physique distant du serveur (rsync ou autres).
3.5 Sécurisation
Actuellement, le serveur n'a qu'un service opérationnel : SSH. Activer Packet Filter à ce stade n'est franchement pas obligatoire, mais nous allons pourtant le mettre en place avec une configuration minimale « plus plus ».
3.5.1 Activation et configuration de PF (Packet Filter)
On commence par activer PF au démarrage du système :
# echo 'pf_enable="YES"' >> /etc/rc.conf
On indique quel sera notre fichier de règles par défaut :
# echo 'pf_rules="/etc/pf.conf"' >> /etc/rc.conf
On précise que l'on veut que PF logue le trafic avec le démon pflogd :
# echo 'pflog_enable="YES"' >> /etc/rc.conf
Et on indique dans quel répertoire il doit déposer les logs :
# echo 'pflog_logfile="/var/log/pflog"' >> /etc/rc.conf
3.5.2 Le fichier pf.conf en question
On édite le fichier /etc/pf.conf (s'il n'existe pas, créez-le), et on commence par définir nos macros :
#MACROS
ext_if="re0" #nom de l'interface réseau
ext_adr="mon.adresse.IP.publique"
Puis on place les règles dans l'ordre :
#REGLES
#Par défaut on bloque tout :
block all
#Normalisation des paquets entrants.
scrub in all
#On laisse passer tout le trafic sortant
pass out inet proto {tcp udp} from $ext_adr to any
A ce stade, notre serveur ne laisse rien entrer, nous allons donc autoriser SSH.
3.5.3 « Papa, y'a des brutes qu'arrêtent pas de toquer à la porte, fais le 22 ! »
Pour autoriser les connexions SSH entrantes sur le serveur, un simple :
pass in inet proto tcp from any to $ext_adr port 22
suffirait.
Seulement voilà, si vous avez déjà regardé le fichier de log des authentifications /var/log/auth.log, vous pouvez rencontrer des centaines, voire des milliers de lignes de ce genre (entre autres) :
Jan 22 13:30:05 vasc0 sshd[44345]: Invalid user ignatz from 213.200.71.251
Jan 22 13:30:06 vasc0 sshd[44347]: Invalid user ilma from 213.200.71.251
Il s'agit d'attaque basique de type brute-force consistant à tester au hasard un login puis un mot de passe sur une connexion TCP (SSH en l'occurrence). Mis à part le risque d'intrusion si le « méchant » trouve le bon couple login/mot de passe, ces tentatives d'intrusion polluent grandement les fichiers de log.
C'est pourquoi nous allons utiliser une table de blacklistage avec notre pare-feu préféré.
On crée d'abord une table ssh-bruteforce :
table <ssh-bruteforce> persist
On indique ensuite que PF doit rejeter toute tentative de connexion SSH provenant des IP contenues dans la table de black-list ssh-bruteforce. Le mot-clé quick indique que PF doit ignorer toute autre règle de filtrage pour ce paquet :
block in quick on $ext_if proto tcp from <ssh-bruteforce> port ssh
Puis on définit la règle de blacklistage basée sur une fréquence de connexion (max-src-conn-rate), dans notre cas, on laisse passer les connexions entrantes SSH, mais s'il y a plus de 2 connexions SSH en 10 secondes provenant de la même IP, alors on blackliste l'IP en question en la mettant dans la table ssh-bruteforce :
pass in quick on $ext_if inet proto tcp from any to $ext_adr port ssh flags S/SA keep state \
( max-src-conn-rate 2/10, overload <ssh-bruteforce> flush global)
Au final, notre fichier /etc/pf.conf minimal en entier :
#MACROS
ext_if="re0" #nom de l'interface réseau
ext_adr="mon.adresse.IP.publique"
#REGLES
#Par défaut on bloque tout :
block all
#Normalisation des paquets entrants.
scrub in all
#On laisse passer tout le trafic sortant
pass out inet proto {tcp udp} from $ext_adr to any
#SSH bruteforce blacklistage
table <ssh-bruteforce> persist
block in quick on $ext_if proto tcp from <ssh-bruteforce> port ssh
pass in quick on $ext_if inet proto tcp from any to $ext_adr port ssh flags S/SA keep state \
( max-src-conn-rate 2/10, overload <ssh-bruteforce> flush global)
On vérifie qu'il n'y a pas de fautes de syntaxe :
# pfctl -n -f /etc/pf.conf
Si c'est OK, alors on démarre Packet Filter :
# /etc/rc.d/pf start
3.5.4 Ne pas couper la branche sur laquelle on est assis…
Lorsqu'on met en place des règles de pare-feu à distance, il vaut mieux être sûr de son coup ou de prévoir une solution de secours (surtout si comme moi, votre serveur se trouve à plus de 1000 km de distance). J'ai préféré opter pour la solution de secours : travailler avec 2 fichiers de règles pf.conf. On commence par un fichier de règles validées et fonctionnelles pf.conf (ci-dessus) qui est lu par défaut par PF au démarrage du service, et lorsqu'on doit faire évoluer les règles de filtrage, on utilise un fichier de règles test pf-test.conf que l'on charge manuellement avec la commande :
# pfctl -vl pf-test.conf
Si après cette commande, on est toujours connecté au serveur dédié et que l'on obtient bien le comportement attendu, il suffit alors de reporter les modifications dans le fichier pf.conf. Si, au contraire, on a plus d'accès, alors on lance un reboot à distance via l'interface de gestion web de l'hébergeur, qui relancera PF avec le fichier pf.conf validé et nous permettra de nous reconnecter à nouveau. C'est tout bête, mais des fois...
Voilà, j'espère que cet article vous aura donné envie de ne plus limiter ZFS qu'à vos seules données, mais à l'ensemble de votre OS et d'en savoir un peu plus sur vasc0, qui est en fait un maton d'une prison ou règne l'ordre et la sécurité (à suivre...).
Remerciements
Je remercie tout d'abord mon épouse, Mylène, pour sa patience et ses encouragements. Je remercie ensuite Patrick Lamaizière, Baptiste Daroussin et Freddy Dissaux pour leur relecture et conseils avisés.
Références
[1] En effet, cette clé vous appartient et peut vous être envoyée par voie postale. Si vous avez une faible bande passante chez vous, migrer plusieurs Go de données par ce biais peut être une alternative non négligeable.
[2] http://wiki.freebsd.org/RootOnZFS/GPTZFSBoot/Mirror
[3] http://blog.etoilebsd.net/post/Migrer_sur_du_full_ZFS
[4] Des documentations évoquent ou recommandent l'utilisation de GPT à la place du classique MBR. J'ai moi-même essayé via GPT à partir du mode « rescue » 7.2 pour installer la 8-RELEASE, sans succès, j'en suis donc resté à ce bon vieux MBR.
[5] http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/boot-blocks.html
[6] Oui mais chuuuut, c'est dans le prochain épisode ezjail !
[7] Non dans cet article, la récompense n'est pas le rhum agricole !!