Publié le 05/11/2022, dernière modification le 13/11/2022.
Depuis longtemps j'administre mes serveurs personnels « à l'ancienne », c'est à dire en installant directement les logiciels dont j'ai besoin sur le système d'exploitation. Pour la plupart de mes usage cela fonctionne très bien car les paquets sont plutôt bien faits. Cependant, avec le temps, j'ai eu de plus en plus envie d'installer des applications web que, pour divers raisons (en particulier la sécurité), je souhaite isoler du reste de ma machine. Je me suis donc lancé à la recherche d'une solution de virtualisation ou de conteneurisation simple et performante.
Avant de me décider, j'ai testé un bon nombre de solutions qui ne m'ont pas convenu. Tout d'abord, bien que j'aime beaucoup les machines virtuelles, cela me semblait surdimensionné pour l'usage que je comptais en faire et également très consommateur en ressources. Les différents hyperviseurs ne m'ont donc pas particulièrement convaincu, à l'exception de Proxmox VE qui, en plus de KVM, supporte les conteneurs LXC.
Malheureusement, après quelques tests je me suis vite rendu compte que Proxmox VE est énorme usine à gaz très consommatrice en ressources. Certes c'est très simple et agréable à utiliser, mais sur un tout petit serveur on sent que ça demande plus de performances.
J'ai donc ensuite testé l'utilisation directe de conteneurs sur mon OS préféré. Après m'être pris la tête durant plusieurs heures avec Docker puis LXC, j'ai laissé tomber l'idée. J'ai appris à haïr les dockerfile et toute la documentation associée ainsi que les interfaces tun/tap. Bref, ayant un simple usage personnel je n'avais clairement pas de temps à perdre à apprendre des technologies qui m'ont fait mauvaise impression.
Cela fait pas mal de temps que je connais FreeBSD, j'avais d'ailleurs testé quelques versions il y a un certain nombre d'années. Ce système m'a toujours laissé une bonne impression, bien qu'à l'époque je ne me sentais pas encore prêt à l'utiliser de manière pérenne malgré toutes ses qualités un excellent handbook. Tout à changé lorsqu'un ami, fan de FreeBSD, m'a parlé d'une solution de gestion des jails : Bastille.
Les jails n'ont rien de nouveau, elles sont apparues sur FreeBSD en l'an 2000. Lorsque je les ai découvertes, elles étaient décrites comme un chroot amélioré offrant une meilleur isolation et donc un niveau de sécurité supplémentaire. Aujourd'hui, elles sont parfois directement comparées aux conteneurs qui semblent effectivement s'en approcher.
Vu mes mauvaises expériences passées avec les conteneurs et mon manque de temps libre, la motivation ne m'est venue que progressivement. Deux éléments extérieurs sont venus me décider à franchir le pas. Tout d'abord, j'ai été en mesure de récupérer un ordinateur portable professionnel désaffecté suite à une touche de clavier cassée. C'est idéal pour le transformer en serveur personnel de tests, l'administration par SSH rendant inutile l'usage du clavier. Ensuite, le rachat de Twitter par Elon Musk m'a poussé à retourner dans le Fédiverse. Pour ça, j'ai eu envie d'utiliser ma propre instance et, contrairement à mon habitude, à la conteneuriser.
Le mot d'ordre que je me suis donné est minimalisme. Je voulais traiter ce nouveau serveur comme un vrai serveur de production et ne surtout rien installer de superflu. Bien entendu, le système de base dispose d'un serveur SSH me permettant de me connecter à un compte utilisateur à l'aide d'une clé.
Une fois Bastille installé, je n'ai eu qu'à suivre le guide de démarage pour créer tout ce dont j'avais besoin. Je ne dirais rien de la création d'une nouvelle interface réseau locale qui est d'une simplicité enfantine, sinon que, malgré ma très nette préférence pour IPv6, j'ai décidé de laisser toutes mes jails en IPv4 à l'exception d'une. La raison est que l'une des jails va accueillir un logiciel ne supportant malheureusement pas IPv6.
La première jail que j'ai créé est destinée à accueillir un serveur de base de données dont je me servirai pour mes différents logiciels. Forcément, j'ai choisi PostgreSQL. Son installation m'a donné un peu de fil à retordre à cause d'une erreur lors de l'initialisation de la base de données. J'ai du modifier le fichier de configuration de la jail (/usr/local/bastille/jails/postgresql/jail.conf
) afin d'y ajouter allow.sysvipc = 1;
.
Une bonne pratique est d'avoir un utilisateur différent pour chaque application et de ne donner accès à cet utilisateur qu'aux bases de données utilisées par cette application. Ici j'ai donc créé un utilisateur et une base de données. Par défaut PostgreSQL permet de s'authentifier sur n'importe quel utilisateur depuis le réseau local, ce qui n'est pas un problème tant qu'il est dans un conteneur car cela sera de facto réduit aux action d'administration manuelle. L'authentification des utilisateurs applicatif se fera sur le réseau, ce qui de base n'est pas permis. J'ai donc édité le fichier /var/db/postgres/data15/pg_hba.conf
afin d'activer l'authentification par mot de passe sur le réseau local :
# Internal network
host all all 10.0.0.0/8 scram-sha-256
Afin d'utiliser au minimum le système de base, j'ai décidé de mettre le reverse-proxy dans une jail dédiée. Ce reverse-proxy est chargé d'accepter toutes les connexions HTTP et HTTPS, de gérer la couche TLS et de rediriger chaque requête vers la jail hébergeant le service correspondant.
Pour ce rôle j'ai beaucoup hésité entre Nginx et HAProxy. J'ai finalement choisi Nginx car ce dernier fait également serveur web, ce qui me permet d'obtenir des certificats de sécurité à l'aide du protocole ACME en utilisant le défi http-01
. En parlant de certificats, j'ai également installé ACMEd afin de les récupérer. Ce choix a été bien plus rapide vu que je suis l'auteur de ce logiciel et qu'en plus FreeBSD l'intègre dans ses paquets.
Si, comme moi, vous êtes un auto-radicalisé IPv6, n'oubliez pas de modifier le fichier de configuration de la jail (/usr/local/bastille/jails/reverse-proxy/jail.conf
) afin de remplacer ip6 = disable;
par ip6.addr = fc00::2;
. Notez que, bien que très peu usitée de part les propriétés d'IPv6, la plage d'adresses fc00::/7
est réservée aux réseaux locaux. Le reverse-proxy supportant à la fois IPv4 et IPv6, Nginx est en mesure d'écouter sur les deux et de transférer les requêtes HTTP à l'ensemble de nos services qui, eux, n'écoutent qu'en IPv4.
Dans cette troisième jail, plutôt que Mastodon, j'ai choisi d'utiliser Pleroma qui est une alternative plus légère, compatible avec les autres logiciels du Fédiverse et, je trouve, plus simple à administrer. Son guide d'installation pour FreeBSD est un peu daté, mais on s'y retrouve.
Le guide d'installation n'étant pas prévu pour une telle installation « modulaire » sur 3 jails différentes, il faut adapter un peu mais ça n'a rien de compliqué. J'ai mis Pleroma à écouter directement sur le réseau local, la configuration Nginx (certificat inclut) étant faite dans l'autre jail. De même, je n'ai pas eu besoin de la partie concernant la création de l'utilisateur PostgreSQL et de la base de données associée.
J'ai omis un détail très important : la configuration du pare-feu de l'hôte. Comme recommandé dans le guide de Bastille, j'ai utilisé PF et j'ai commencé avec la configuration proposée. J'ai ensuite adaptée cette configuration de manière à passer les flux entrant sur les ports 80 (HTTP) et 443 (HTTPS) sur le reverse proxy ainsi qu'accepter certains paquets ICMP.
Voici le contenu de mon fichier /etc/pf.conf
:
ext_if="em0"
int_if="bastille0"
rev_proxy_ip="10.0.0.2"
rev_proxy_ip6="fc00::2"
icmp_types = "{ echorep, echoreq, unreach, timex }"
icmp6_types = "{ echorep, echoreq, unreach, timex, toobig, neighbrsol, neighbradv, routersol, routeradv }"
set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
table <jails> persist
nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"
rdr pass inet proto tcp from any to any port 80 -> $rev_proxy_ip port 80
rdr pass inet6 proto tcp from any to any port 80 -> $rev_proxy_ip6 port 80
rdr pass inet proto tcp from any to any port 443 -> $rev_proxy_ip port 443
rdr pass inet6 proto tcp from any to any port 443 -> $rev_proxy_ip6 port 443
block in all
pass out quick keep state
antispoof for $ext_if inet
antispoof for $ext_if inet6
pass in inet proto tcp from any to any port ssh flags S/SA keep state
pass in inet6 proto tcp from any to any port ssh flags S/SA keep state
pass inet proto icmp all icmp-type $icmp_types keep state
pass inet6 proto icmp6 all icmp6-type $icmp6_types keep state