Chiffrer un répertoire sur FreeBSD avec PEFS

La sécurité des données numériques est au cœur des enjeux actuels, et pas seulement pour les professionnels. Pouvoir chiffrer ses propres données pour les rendre inutilisables pour un voleur éventuel, est donc une nécessité. Sous FreeBSD il est possible de faire du chiffrement sur l'ensemble du disque avec geli(8) ou gbde(4), ou sur un répertoire uniquement avec PEFS. Les deux premières solutions opèrent un chiffrement au niveau bloc. Elles travaillent sur des devices de taille fixe, et ne protègent pas vos données si un pirate accède à la machine quand elle est allumée et que les disques chiffrés sont montés.
PEFS permet de travailler sur un répertoire donc sans avoir à fixer un volume arbitraire au moment de l'initialisation. Le chiffrement se fait au niveau fichier avec une ou plusieurs clés autorisant la hiérarchisation des accès.

fichiers-chiffres-avec-PEFS

Installer PEFS

PEFS est disponible dans les ports et dans les packages. Il est recommandé de faire l'installation par les ports. En effet, l'outil contient un module pour le kernel, et un trop grand écart entre la version de votre noyau et celle qui a servi à la compilation du package peut générer un panic et geler votre système. Aussi, avec un processeur récent vous pourrez activer AES-NI au moment de la compilation du port (accélération matérielle pour le chiffrement AES).

$ cd /usr/ports/sysutils/pefs-kmod
$ sudo make install clean

Pour charger le module automatiquement au redémarrage, ajouter cette ligne dans votre /boot/loader.conf :

pefs_load="YES"

Utiliser PEFS

PEFS s'utilise assez simplement. Il faut commencer par créer un répertoire vide pour stocker les fichiers chiffrés. On peut créer un second répertoire qui servira de point de montage pour la version déchiffrée, mais le montage peut aussi se faire sur le premier répertoire.

$ mkdir ~/coffre.pefs ~/coffre

Ensuite on monte le répertoire (soit vers lui-même, soit comme ici vers un autre pour des raisons de clarté) :

$ sudo pefs mount ~/coffre.pefs ~/coffre

À ce stade, le répertoire ~/coffre est en lecture seule, car nous n'avons pas encore fourni de clé de chiffrement.

$ touch ~/coffre/toto
touch: coffre/toto: Read-only file system

L'ajout d'une clé est une étape simple, mais la documentation officielle manque légèrement d'explications. Il ne s'agit pas ici de fournir une clé une fois pour toute, mais de dire à PEFS de travailler pour la session courante avec cette clé. Si le répertoire est démonté, ou si les clés sont vidangées (flushkeys) il faudra fournir la même clé à nouveau pour accéder aux fichiers.

$ pefs addkey ~/coffre
Enter passphrase: (mot de passe pour (dé)chiffrer vos fichiers)
$ touch ~/coffre/toto
$ ls -A1 ~/coffre*
/home/me/coffre:
toto                               <- version en clair

/home/me/coffre.pefs:
.SfoQ93cCWWT+NnBB7EYE2ZK402YDSwsT  <- version chiffrée

Pour interdire l'accès à vos données vous pouvez simplement utiliser la commande flushkeys, ou aller jusqu'au démontage du répertoire:

$ pefs flushkeys ~/coffre
$ ls -A1 ~/coffre*
/home/me/coffre:
.SfoQ93cCWWT+NnBB7EYE2ZK402YDSwsT  <- version chiffrée

/home/me/coffre.pefs:
.SfoQ93cCWWT+NnBB7EYE2ZK402YDSwsT  <- version chiffrée
$ sudo pefs unmount ~/coffre
$ ls -A1 ~/coffre*
/home/me/coffre:
(vide)

/home/me/coffre.pefs:
.SfoQ93cCWWT+NnBB7EYE2ZK402YDSwsT  <- version chiffrée

Le démontage est facultatif, mais si vous utilisez PEFS dans des scripts, c'est sans doute plus propre. Aussi, faites bien attention, il est possible de monter plusieurs fois le même répertoire au même endroit ce qui peut déboucher sur des situations tout à fait inconfortables. En cas d'utilisation automatisée mettez les bouchées doubles sur les contrôles avant et après chaque commande.

Après le flushkeys, les données sont présentées uniquement dans leur forme chiffrée, mais vous y avez toujours accès ce qui vous permet par exemple de les exporter vers un cloud de sauvegarde (Amazon Glacier, par exemple) en toute sérénité.

Pour déchiffrer vos données, et en admettant que le répertoire soit déjà monté, il vous suffit de reproduire l'étape addkey avec la même clé :

$ pefs addkey ~/coffre
Enter passphrase:

Si à cette étape vous changez la valeur de la clé (volontairement ou par faute de frappe), la nouvelle clé sera acceptée par PEFS. Cette nouvelle clé ne vous donnera pas accès aux anciennes données bien évidemment. Par contre elle vous permettra de stocker de nouveaux fichiers, qui seront chiffrés grâce à cette nouvelle clé. C'est à la fois dangereux et pratique. Si la faute de frappe est votre ennemi vous pouvez jouer la prudence et utiliser un trousseau de clé. Le trousseau se trouve dans le fichier ~/coffre.pefs/.pefs.db et il stocke la ou les clés "officielles" de votre choix. L'initialisation du trousseau se fait avec la commande addchain :

$ pefs addchain -f -Z ~/coffre.pefs

Ensuite, il vous suffira lorsque vous fournirez votre clé de le faire avec l'option -c pour que votre saisie soit comparée au contenu du trousseau :

$ pefs addkey -c ~/coffre

Cela offre une sécurité intéressante mais impose aussi la contrainte d'avoir un fichier dont il faudra prendre soin (attention aux rsync qui pourraient le supprimer si vous utilisez PEFS pour stocker des sauvegardes dans un répertoire chiffrés).

Il existe d'autres possibilités au logiciel PEFS que je ne détaille pas ici, notamment celle de stocker dans le trousseau .pefs.db des chaînes de clés avec héritage parent-enfant, ou encore le module PAM pam_pefs.so qui permet de fournir le montage et le déchiffrement automatique du ~/ d'un utilisateur au moment où il s'authentifie.

Un peu de lecture

Wiki FreeBSD au sujet de PEFS
Tutoriel PEFS
Audit de sécurité rapide sur PEFS

S’envoyer des SMS multilignes via l’API de free-mobile

Free vient de mettre à disposition de ses abonnés Free-mobile un moyen simple de générer via une API accessible en ligne des SMS à soi-même. L'utilisation est globalement très simple et tient presque toutes ses promesses (actuellement il ne semble pas possible de faire du POST, seul le GET fonctionne).
Il suffit aux intéressés de se rendre sur leur interface de gestion de compte Free-mobile, dans la section "Mes options", et d'y activer (vers le bas de la page) l'option ad-hoc. Une fois l'option activée, Free vous gratifie d'un mot de passe dédié à cet usage, et vous renseigne un peu sur le fonctionnement du service :

free-mobile-notification

Une fois l'option activée, l'utilisation se résume à une requête HTTP de cette forme :

curl 'https://smsapi.free-mobile.fr/sendmsg?user=***&pass=***&msg=coucou'

Vous recevez alors sur votre téléphone Free-mobile le SMS contenant le message "coucou".

La création d'un message multiligne est un peu plus délicate. La plupart des plateforme Unix/Linux envoie en guise de séparateur de ligne un \n qui se traduit par %0a une fois "url-encodé". Malheureusement, l'API n'accepte pas ce %0a et le message sera tronqué à la première occurrence.
Par contre, le séparateur de ligne \r, encodé en %0d est bien accepté par l'API Free-mobile. Il faut donc, avant d'envoyer un message sur plusieurs lignes, traduire les \n en \r.

J'ai conçu un script shell pour mes besoins personnels. Il prend ses données en entrée standard, par exemple via un pipe :

echo "mon message" | sms.sh

Ce qui est plus souple pour moi dans bien des situations, que de devoir invoquer le script et de lui servir le message comme argument, par exemple :

autresms.sh mon message

Voici donc le code de mon script qui prend les messages via stdin, et qui converti les \n en \r. Notez que le SMS à l'arrivée ne contient pas de saut de ligne, donc prévoyez des espaces en fin de ligne si vous ne voulez pas que la fin de ligne soit collée au début de la ligne suivante.

#!/usr/local/bin/bash  
#
# push notification via smsapi.free-mobile.fr
#
LOGGEROPT="-p security.notice -t SMS"
REPLOGG=1
REPMAIL=1
MAILTO=root
USR="****"
PSW="****"
URL="https://smsapi.free-mobile.fr/sendmsg"
CURL=/usr/local/bin/curl

report() {
        [ ${REPLOGG} -eq 1 ] && echo $@ | logger ${LOGGEROPT}
        [ ${REPMAIL} -eq 1 ] && echo $@ | mail -s "SMS $@" ${MAILTO}
}

eval $(${CURL} --insecure -G --write-out "c=%{http_code} u=%{url_effective}" \
        -o /dev/null -L -d user=${USR} -d pass=${PSW} --data-urlencode msg="$(cat|tr '\n' '\r')" ${URL} 2>/dev/null | tr '&' ';')

echo ${c} ${u}\&pass=***\&msg=${msg} | logger ${LOGGEROPT}

case ${c} in
        "200")
                exit 0
                ;;
        "400")
                report "parameter missing" ; exit 400
                ;;
        "402")
                report "too many SMS..." ; exit 402
                ;;
        "403")
                report "service unavailable for user, or wrong credentials" ; exit 403
                ;;
        "500")
                report "server error, try later" ; exit 500
                ;;
        *)
                report "unexpected result" ; exit 600
                ;;
esac

Utilisation :

echo "mon message 
sur plusieurs 
lignes" | sms.sh

Il reste à faire :
- ajouter l'enregistrement dans les log système de chaque utilisation du script sms.sh
- ajouter la gestion des codes de retour HTTP autres que 200.

Attention
Pour bénéficier d'une gestion d'erreur et d'une trace dans les log de la machine j'ai utilisé une grosse ruse très sale avec eval. Cela permet de créer des variables à la volée à partir de données fournie par l'utilisateur. C'est éminemment risqué car tout ce est qui généré par c=%{http_code} u=%{url_effective} sera évalué sans distinction (avec potentiellement des exécutions de code).
Pour un système de monitoring c'est un risque négligeable : les messages sont connus et fixés par le logiciel qui les génère, sans interaction avec l'utilisateur. Pour une utilisation dans tout autre mécanisme la prudence est requise.

ZFS primarycache: all versus metadata

In my previous post, I wrote about tuning a ZFS storage for MySQL. For InnoDB storage engine, I've tuned the primarycache property so that only metadata would get cached by ZFS. It makes sense for this particular use, but in most cases you'll want to keep the default primarycache setting (all).
Here is a real world example showing how a non-MySQL workload is affected by this setting.

On a virtual server, 2 vCPU, 8 GB RAM, running FreeBSD 9.1-RELEASE-p7, I have a huge zpool of about 4 TB. It uses gzip compression, and stores 1.8TB of emails (compressratio 1.61x) and 1TB documents (compressratio 1.15x). Documents and emails have their own dataset, on the same zpool.
As it's a secondary backup server, isolated from production, I can easily make some tests.

I've launched clamscan (the binary part of ClamAV virus scanner) against a small branch of the email storage tree (about 1/1000 of total emails) and measured the zpool IOs, CPU usage and total runtime of the scan.
Before each run, I've rebooted the server to clear cache.
clamscan is set up so that every temporary files are written into a UFS2 filesystem (/tmp).

One run was made with property primarycache set to all, the other run was made with primarycache set to metadata.

Total runtime with default settings primarycache=all is less than 15 minutes, for 20518 files:

Scanned directories: 1
Scanned files: 20518
Infected files: 2
Data scanned: 7229.92 MB
Data read: 2664.59 MB (ratio 2.71:1)
Time: 892.431 sec (14 m 52 s)

Total runtime with default settings primarycache=metadata is more than 33 minutes:

Scanned directories: 1
Scanned files: 20518
Infected files: 2
Data scanned: 7229.92 MB
Data read: 2664.59 MB (ratio 2.71:1)
Time: 2029.921 sec (33 m 49 s)

zpool iostat every 5 seconds, with different primarycache settings, ~10 minutes range.

zpool IO stats

CPU usage for clamscan process, and for kernel{zio_read_intr_0} kernel thread. 5 seconds sampling, with different primarycache settings, ~10 minutes range.

CPU stats

In both tests, the server is freshly rebooted, cache is empty. Nevertheless, when primarycache=all the kernel{zio_read_intr_0} thread consumes very few CPU cycles, and the clamscan process run's more than twice as fast as the same process with primarycache=metadata.
More importantly, clamscan manages to read the exact same amount of data in both tests, using 10 times less IO throughput when primarycache is set to all.

There is something weird. Let's make another test:

I create 2 brand new datasets, both with primarycache=none and compression=lz4, and I copy in each one a 4.8GB file (2.05x compressratio). Then I set primarycache=all on the first one, and primarycache=metadata on the second one.
I cat the first file into /dev/null with zpool iostat running in another terminal. And finally, I cat the second file the same way.

The sum of read bandwidth column is (almost) exactly the physical size of the file on the disk (du output) for the dataset with primarycache=all: 2.44GB.
For the other dataset, with primarycache=metadata, the sum of the read bandwidth column is ...wait for it... 77.95GB.

There is some sort of voodoo under the hood that I can't explain. Feel free to comment if you have any idea on the subject.

A FreeBSD user has posted an interesting explanation to this puzzling behavior in FreeBSD forums:

clamscan reads a file, gets 4k (pagesize?) of data and processes it, then it reads the next 4k, etc.

ZFS, however, cannot read just 4k. It reads 128k (recordsize) by default. Since there is no cache (you've turned it off) the rest of the data is thrown away.

128k / 4k = 32
32 x 2.44GB = 78.08GB

Damnit.

MySQL on ZFS (on FreeBSD)

logofreebsdLots on FreeBSD users are coming to ZFS since the release of FreeBSD 10, as it's so easy to install the system on top of that powerful filesystem. ZFS default behavior and settings are perfect for a wide range of workloads and uses, but it's not exactly what you need for databases hosting.
I'm running a FreeBSD 9.x server hosting http, php, mysql, mail, and many other things, so a databases-only optimization would be counterproductive. After a good dive into experts do's & don't's here are few steps I've taken to tune my server.

Datasets

If like me you've started hosting MySQL databases a long time ago (say ~15 years ago) you might have some DB using the old MyISAM engine, and some newer ones using InnoDB. Guess what. They use different block sizes, and they use different caching mechanisms.
On top of that, the block size for InnoDB is not the same when you deal with data files, and when you deal with log files.
In order to get the best block size and cache tuning possible, I've created 3 datasets: one for InnoDB data files, one for innodb logs, and one for everything else, including MyISAM databases.

Block size

The block size is engine dependent. MyISAM uses a 8k block size, InnoDB uses a 16k block size for data and 128k for logs. It's easy to setup datasets for a particular block size at creation with -o option, or after creation with zfs set. You must set the proper block size before putting any data on the dataset, otherwise pre-existing data won't use the desired block size.

Caching

MyISAM engine relies on the underlying filesystem caching mechanism, so you must ensure ZFS will cache both data and metadata (that's the default behavior). On the other hand, InnoDB uses an internal cache, so it would be a waste on memory to cache the same data into ZFS and InnoDB. On a dedicated MySQL server, the proper tuning would require to limit ARC size, and to disable data caching in ZFS. On a general-purpose server, ARC size should not be tweaked, but on InnoDB dedicated datasets it's easy to disable ZFS cache for data by setting the property primarycache to "metadata".

On MySQL's side

You must of course tell mysqld where to find data files and logs. I've set those values in my /var/db/mysql/my.cnf file:

innodb_data_home_dir = /var/db/mysql-innodb
innodb_log_group_home_dir = /var/db/mysql-innodb-logs

where /var/db/mysql-innodb and /var/db/mysql-innodb-logs are mount points for the dataset dedicated to InnoDB data files, and the dataset dedicated to InnoDB log files.

As my zpool is a mirror, I've added this setting too:

skip-innodb_doublewrite

Step by step

I've followed this course of action:

1st step: mailing to users, "MySQL will be unavailable for about 5 minutes, hang on."
2nd step: backup /var/db/mysql and edit your my.cnf
3rd step: shutdown your mysql server

sudo service mysql-server stop

4th step: move /var/db/mysql to /var/db/mysql-origin
5th step: create appropriate datasets:

zfs create -o recordsize=16k -o primarycache=metadata zmirror/var/db/mysql-innodb
zfs create -o recordsize=128k -o primarycache=metadata zmirror/var/db/mysql-innodb-logs
zfs create -o recordsize=8k zmirror/var/db/mysql

6th step: move data from /var/db/mysql-origin to your new datasets

cd /var/db/
sudo mv mysql-origin/ib_logfile* mysql-innodb-logs/
sudo mv mysql-origin/ibdata1 mysql-innodb/
sudo mv mysql-origin/* mysql/

and set proper rights:

sudo chown mysql:mysql mysql-innodb-logs mysql-innodb mysql
sudo chmod o= mysql mysql-innodb-logs mysql-innodb

7th step: restart your mysql server

sudo service mysql-server start & tail -f mysql/${HOSTNAME}.err

8th step: mailing to users, "MySQL's back online maintenance duration 5 min 14 sec."

Further reading & references

MySQL Innodb ZFS Best Practices
A look at MySQL on ZFS
Optimizing MySQL performance with ZFS
ZFS for Databases

Dtrace on FreeBSD 9.2

FreeBSD logoAs of FreeBSD 9.2-RELEASE, Dtrace is enabled by default in the GENERIC kernel. We where already able to activate Dtrace, on earlier releases, but we would have to make a custom kernel. Not always what you want because it breaks binary updates.

Now that Dtrace is enabled (and updated) in FreeBSD, everyone can use it without the hassle of a custom kernel. You still have to load Dtrace's modules. Let's see how it works.

first you go root and try a Dtrace related command:

$ sudo -Es
Password:
# dtruss ls
dtrace: failed to initialize dtrace: DTrace device not available on system

Looks like Dtrace is not loaded:

# kldstat 
Id Refs Address            Size     Name
 1   23 0xffffffff80200000 15b93c0  kernel
 2    1 0xffffffff817ba000 23d078   zfs.ko
 3    2 0xffffffff819f8000 84e0     opensolaris.ko
 4    1 0xffffffff81a01000 4828     coretemp.ko
 5    1 0xffffffff81c12000 2bce     pflog.ko
 6    1 0xffffffff81c15000 3078d    pf.ko
 7    1 0xffffffff81c46000 57bcf    linux.ko
 8    1 0xffffffff81c9e000 3c3d     wlan_xauth.ko

lets find Dtrace related modules:

# ls /boot/kernel/dtrace*
/boot/kernel/dtrace.ko			/boot/kernel/dtrace_test.ko.symbols
/boot/kernel/dtrace.ko.symbols		/boot/kernel/dtraceall.ko
/boot/kernel/dtrace_test.ko		/boot/kernel/dtraceall.ko.symbols

/boot/kernel/dtraceall.ko looks like a winner.

# kldload dtraceall
# kldstat 
Id Refs Address            Size     Name
 1   59 0xffffffff80200000 15b93c0  kernel
 2    1 0xffffffff817ba000 23d078   zfs.ko
 3   16 0xffffffff819f8000 84e0     opensolaris.ko
 4    1 0xffffffff81a01000 4828     coretemp.ko
 5    1 0xffffffff81c12000 2bce     pflog.ko
 6    1 0xffffffff81c15000 3078d    pf.ko
 7    1 0xffffffff81c46000 57bcf    linux.ko
 8    1 0xffffffff81c9e000 3c3d     wlan_xauth.ko
 9    1 0xffffffff81ca2000 ba2      dtraceall.ko
10    1 0xffffffff81ca3000 4ed3     profile.ko
11    3 0xffffffff81ca8000 402c     cyclic.ko
12   12 0xffffffff81cad000 23dbaa   dtrace.ko
13    1 0xffffffff81eeb000 fb3d     systrace_freebsd32.ko
14    1 0xffffffff81efb000 109bd    systrace.ko
15    1 0xffffffff81f0c000 45bb     sdt.ko
16    1 0xffffffff81f11000 4926     lockstat.ko
17    1 0xffffffff81f16000 bf0b     fasttrap.ko
18    1 0xffffffff81f22000 6673     fbt.ko
19    1 0xffffffff81f29000 4eeb     dtnfsclient.ko
20    1 0xffffffff81f2e000 1dd92    nfsclient.ko
21    1 0xffffffff81f4c000 47c0     nfs_common.ko
22    1 0xffffffff81f51000 55f2     dtnfscl.ko
23    1 0xffffffff81f57000 45cd     dtmalloc.ko
24    1 0xffffffff81f5c000 44fc     dtio.ko

tadaaaam:

# dtruss ls
SYSCALL(args) 		 = return
mmap(0x0, 0x8000, 0x3)		 = 6418432 0
issetugid(0x0, 0x0, 0x0)		 = 0 0
lstat("/etc\0", 0x7FFFFFFFC3C0, 0x0)		 = 0 0
lstat("/etc/libmap.conf\0", 0x7FFFFFFFC3C0, 0x0)		 = 0 0
open("/etc/libmap.conf\0", 0x0, 0x81FA20)		 = 3 0
...

Happy debugging/tracing/auditing.

Munin plugins for CRM114

I'm using CRM114-based SpamAssassin plugin for spam filtering at work, and at home. Client-side, I'm able to check CRM114 contribution by a simple look at headers of an email message. But that won't tell you how CRM114 is behaving on the server side. The main concern on the server, is to check the two "databases" spam.css and nonspam.css.
I've chosen to monitor both average packing density and documents learned metrics for both files. The first one goes from 0 to 1. When its value reaches 0.9 and beyond, you must make sure your antispam filtering does not become sluggish.
The second one represents the number of emails from which CRM114 has been trained. Basically, CRM114 is only trained from its mistakes : if an email is flagged as spam by mistake, you can train CRM114 to learn it as nonspam, but if an email is properly flagged as spam, you can't train CRM114 to learn it as spam.

I've designed two Munin plugins, they run on FreeBSD but portage to another UNIX is just a matter of path.

Monitor "average packing density" (crm114_packingdensity): shows how much your CRM114 bases are encumbered

#!/usr/local/bin/bash
#
# Parameters:
#
# 	config   (required)
# 	autoconf (optional - used by munin-config)
#
# Magick markers (optional - used by munin-config and som installation
# scripts):
#%# family=auto
#%# capabilities=autoconf

export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# config
if [ "$1" = "config" ]; then
    echo 'graph_title CRM114 css file stat average packing density'
    echo 'graph_vlabel Packing density'
    echo 'graph_category ANTISPAM'
    echo 'graph_args --upper-limit 1 --lower-limit 0'
    echo 'graph_info This graph shows the average packing density for CRM114 css files'
    echo 'spam.label spam'
    echo 'nonspam.label nonspam'
    exit 0
fi

cssutil -r -b /var/amavis/.crm114/spam.css | awk '/Average packing density/ {print "spam.value "$5}'
cssutil -r -b /var/amavis/.crm114/nonspam.css | awk '/Average packing density/ {print "nonspam.value "$5}'

Monitor "documents learned" (crm114_documentslearned): shows how many emails CRM114 has learned from

#!/usr/local/bin/bash
#
# Parameters:
#
# 	config   (required)
# 	autoconf (optional - used by munin-config)
#
# Magick markers (optional - used by munin-config and som installation
# scripts):
#%# family=auto
#%# capabilities=autoconf

export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# config
if [ "$1" = "config" ]; then
    echo 'graph_title CRM114 css file stat documents learned'
    echo 'graph_vlabel Documents learned'
    echo 'graph_category ANTISPAM'
    echo 'graph_args --lower-limit 0'
    echo 'graph_info This graph shows the documents learned count for CRM114 css files'
    echo 'spam.label spam'
    echo 'nonspam.label nonspam'
    exit 0
fi

cssutil -r -b /var/amavis/.crm114/spam.css | awk '/Documents learned/ {print "spam.value "$4}'
cssutil -r -b /var/amavis/.crm114/nonspam.css | awk '/Documents learned/ {print "nonspam.value "$4}'

Both plugins need read access to spam.css and nonspam.css, depending on your setup it will require special privileges. You might want to add in /usr/local/etc/munin/plugin-conf.d/plugins.conf:

[crm114_*]
user root

On s’amuse avec IPMI

AOC-IPMI20-E-copyright-supermicroJ'ai passé un week end plutôt divertissant en compagnie d'IPMI. Tout a commencé avec la lecture d'un article édifiant sur arstechnica.com présentant IPMI comme un parasite, une sangsue.
J'ai toujours su instinctivement que l'IPMI pouvait, quelque part, poser un souci en terme de sécurité. Mais jamais je n'aurai imaginé que la situation fût aussi dramatique.
Les failles sont à tous les niveaux, tant dans le protocole IPMI, que dans les implémentations semi-propriétaires des fabricants. Elles sont dans les logiciels utilisés, elles sont dans le verrouillage que les fabricants imposent aux BMC, les rendant impossible à auditer, etc. Sysadmin de tout poil, je vous invite fortement à lire l'article d'Ars, et les proses de Dan Farmer et HD Moore.

Bref, j'ai tout lu, et disposant d'exemplaires HP et SuperMicro, j'ai tenté moi-même d'exploiter les failles décrites. Je ne rentre pas dans les détails, tout est déjà très bien expliqué dans la littérature sus-mentionnée. Par contre je vais donner quelques pistes pour l'installation des outils sous FreeBSD.

ipmitool, l'outil de base doit être installé avec FreeIPMI, sinon les manipulations permettant d'exploiter les BMC vulnérables ne fonctionneront pas. Sous FreebSD on peut simplement utiliser les ports :

# cd /usr/ports/sysutils/ipmitool
# make install clean
On active l'option "FREEIPMI" dans la configuration, et c'est parti.

On active l'option "FREEIPMI" dans la configuration, et c'est parti.

Ensuite il est possible d'installer metasploit, outils puissant et complexe. Pour gagner du temps, et si vous ne souhaitez pas approfondir l'usage de ce logiciel, vous pouvez décocher l'option "DB" au moment de l'installation du port :

# cd /usr/ports/security/metasploit
# make install clean

Si vous souhaitez jouer un peu avec les outils et scripts développés par Dan Farmer, vous devrez installer en plus le module perl CaptureOutput.pm, nécessaire au fonctionnement de rak-the-ripper.pl :

# cd /usr/ports/devel/p5-IO-CaptureOutput
# make install clean

Si vous avez en plus une machine disposant d'une BMC qui tourne sur FreeBSD, voici ce qu'il faut faire pour accéder à ce contrôleur parasite via ipmitool :

# kldload ipmi

Cela charge le module ipmi, si ce n'est déjà fait. Vous aurez quelque chose de ce style dans la sortie de dmesg -a :

ipmi0: <IPMI System Interface> port 0xca2,0xca3 on acpi0
ipmi0: KCS mode found at io 0xca2 on acpi
ipmi0: IPMI device rev. 1, firmware rev. 2.35, version 2.0
ipmi0: Number of channels 2
ipmi0: Attached watchdog
ipmi1: <IPMI System Interface> on isa0
device_attach: ipmi1 attach returned 16

Et vous pourrez ensuite vous livrer à toutes sortes d'expériences en local (donc une fois de plus sans authentification…).
La prudence veut que vous désactiviez immédiatement ce module ensuite :

# kldunload ipmi

même si, ne nous voilons pas la face, ça retarderait juste de quelques dizaines de secondes un éventuel attaquant.

Mettez bien à jour le firmware de vos BMC. Et quand je dis bien à jour, c'est très sérieux, puisque des firmwares récents chez HP (juin) ne corrigent pas l'énorme faille du Cipher-0. Il faut utiliser le firmware 1.60 publié fin juillet pour avoir une chance de corriger cette faille de sécurité.

Running source dedicated server on FreeBSD 9.x

steam logo © steamI've covered this subject in french back in 2010, but things have evolved, and installing scrds on FreeBSD is not as straightforward as it used to be. Prerequisites are the same, you must first install the linux compatibility layer:

# As root, load the module & make sure it will be loaded after reboot:
kldload /boot/kernel/linux.ko
echo 'linux_enable="YES"' >> /etc/rc.conf

Then, and only after loading the linux module, install linux_base:

portinstall -PP linux_base-f10
(or portinstall linux_base-f10 if the command above fails)

Add linproc to /etc/fstab,

linproc         /compat/linux/proc      linprocfs       rw 0 0

and mount it:

mount -a

Install linux-steam:

portinstall linux-steam

Add a steamuser user (or whatever name you want). The user must be unprivileged, and must not be able to log into the machine. Set its home to /usr/local/steam and its shell to /usr/sbin/nologin.
Then, update the client linux-steam:

chown -R steamuser /usr/local/steam
cd /usr/local/steam
sudo -u steamuser ./steam
sudo -u steamuser ./steam (yes, do it twice, to make sure it's up to date)

Back in past, you would have used the steam client to install and update games, but people at steam thought it was way too easy. So they made it more complicated. Now you have to install a dedicated tool in order to install games and keep them updated: steamcmd.
Point your browser to the SteamCMD wiki page at Valvesoftware and read it. Then, download the linux version and extract in a dedicated directory (/usr/games for example).

cd /usr/games
fetch http://media.steampowered.com/client/steamcmd_linux.tar.gz
tar -xzf steamcmd_linux.tar.gz

By default, the steamcmd command will maintain your game library into /usr/local/steam/Steam/SteamApps, you might want to create a soft link in order to put your game library somewhere else (the force_install_dir option of steamcmd would not work on my server). At least, change owners for /usr/games/SteamCMD directory, to make sure steamuser can update its content:

mkdir /usr/local/steam/Steam
mkdir /usr/games/SteamApps
ln -s /usr/games/SteamApps /usr/local/steam/Steam/SteamApps
chown steamuser /usr/games/SteamApps
chown -R steamuser /usr/games/SteamCMD

Then you might have to change shebangs in SteamCMD/steam.sh and SteamCMD/steamcmd.sh to use your own bash (probably /usr/local/bin/bash instead of /bin/bash). After what you can launch steamcmd.sh:

cd /usr/games/SteamCMD
sudo -u steamuser ./steamcmd.sh

The program should auto-update, and present you with a Steam> prompt.
Then, you must login. For L4D2, and most games, you can login anonymously:

login anonymous

Choose your game from the list on the wiki, and use its ID to install/update:

app_update 222860 validate

It's possible to automate SteamCMD, for daily update of your games. For example you can create a shell script like this one:

#!/usr/local/bin/bash
cd /usr/games/SteamCMD || exit 1
/usr/local/bin/sudo -u steamuser ./steamcmd.sh +login anonymous +app_update 222860 validate +quit

Running your game server does not change much from my previous post. The path of game folder is the only important modification. I've created a shell script to launch L4D2 server:

#!/usr/local/bin/bash
ROOT="/patpro/games/SteamApps/common/Left 4 Dead 2 Dedicated Server"
SUDO="/usr/local/bin/sudo -u steamuser"
SCREEN=/usr/local/bin/screen
NICE="/usr/bin/nice -n -5"
STEAMRUNARGS="-ip PUT-YOUR-IP-ADDRESS-HERE -fps_max 0 -sys_ticrate 1000"

cd "${ROOT}" || exit 1
${SCREEN} ${NICE} ${SUDO} ./srcds_run ${STEAMRUNARGS}

Open TCP and UDP ports 26901 and 27015 in your firewall, and edit SteamApps/common/Left 4 Dead 2 Dedicated Server/left4dead2/cfg/server.cfg to tweak your settings.

Happy gaming!