Vue normale

SSH dans l'initramfs - Rebootez vos serveurs chiffrés sans stress

Par : Korben
6 mars 2026 à 09:08

Le chiffrement complet du disque, tout le monde vous dit que c'est la base. LUKS sous Linux, BitLocker sous Windows, FileVault sous macOS... sauf que personne vous dit quoi faire quand votre serveur reboot à 3h du mat et qu'il attend sagement sa passphrase.

Là, vous êtes coincé !!!!

Parce que oui, le truc vicieux avec le chiffrement intégral, c'est qu'au démarrage, le système ne peut pas lire le disque tant que vous n'avez pas tapé le mot de passe. Du coup, si votre machine est dans un datacenter ou chez un hébergeur, ben... faut se déplacer physiquement. Et ça c'est bien relou !!!

La solution, c'est d'embarquer un serveur SSH directement dans l' initramfs (oui, le mini OS qui tourne AVANT votre vrai système, sur le port 22). En gros, votre machine boot, charge l'initramfs, lance un serveur SSH... et vous n'avez plus qu'à vous connecter à distance pour taper la passphrase. Comme ça le disque se déverrouille et le boot continue. Voilà quoi, c'est simple la vie quand on lit Korben.info !! loool

L'initramfs, c'est quoi exactement ?

Alors pour ceux qui débarquent, l'initramfs c'est une archive compressée dans /boot/initramfs-linux.img qui contient un système Linux minimal. Son boulot, c'est de préparer le terrain avant de passer la main au vrai OS, genre charger les modules noyau, détecter le matériel, monter les systèmes de fichiers... et dans notre cas, demander la passphrase LUKS. Genre un second OS, mais en version bonsaï !

Installer Dropbear dans l'initramfs

Dropbear , c'est un serveur SSH ultra-léger (environ 110 Ko) parfait pour l'initramfs. L'article de jyn qui m'a inspiré pour cet article , recommande Arch Linux avec mkinitcpio, mais sachez que sous Debian/Ubuntu le paquet dropbear-initramfs fait le même boulot avec update-initramfs.

Sur Arch, vous installez mkinitcpio-systemd-extras puis vous modifiez /etc/mkinitcpio.conf pour ajouter les hooks réseau et Dropbear :

HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck systemd-network dropbear)

Attention, l'ordre des hooks compte. Le réseau doit être configuré AVANT Dropbear, sinon votre serveur SSH démarre sans interface réseau. Pas super utile donc !

Configurer le réseau dans l'initramfs

Ensuite faut créer un fichier de config réseau dans /etc/systemd/network-initramfs/. En fait, c'est du systemd-networkd classique, donc si vous avez déjà configuré ça, c'est pareil. Un simple fichier .network avec DHCP fait le job en Ethernet (et pour un serveur, c'est clairement recommandé). Pour les plus paranos, une IP statique marche aussi, sauf que faudra pas oublier de la mettre à jour si vous changez de réseau.

La touche Tailscale

Après si votre serveur est derrière un NAT ou un firewall, bah... le SSH classique ne passe pas. Du coup, jyn a eu la bonne l'idée d'embarquer Tailscale dans l'initramfs aussi. Comme ça, la machine rejoint votre réseau privé Tailscale dès le boot, même avant le déchiffrement du disque.

Vous lancez setup-initcpio-tailscale, ça vous donne un lien d'authentification sur login.tailscale.com et c'est réglé. Après faut penser à configurer les ACL Tailscale pour que SEULE votre machine d'admin puisse se connecter à l'initramfs car OUI ON NE LAISSE PAS UN PUTAIN DE SSH ouvert sur un système pré-boot sans protection, HEIN ?? HEIN ?? Donc faites ça !!

Les précautions de sécurité

Vous vous en doutez, y'a quand même quelques pièges à éviter. D'abord, les clés SSH de Dropbear dans l'initramfs (stockées dans /etc/dropbear/) doivent être DIFFÉRENTES de celles d'OpenSSH dans /etc/ssh/. Parce que l'initramfs n'est pas chiffré (bah oui, il doit tourner avant le déchiffrement), donc ces clés sont techniquement accessibles à quelqu'un qui a un accès physique au disque.

Ensuite, attention, limitez ce que Dropbear peut faire. Pas de shell complet, juste la commande systemd-tty-ask-password-agent qui sert uniquement à taper la passphrase. Comme ça, même si quelqu'un arrive à se connecter, il ne peut rien faire d'autre.

Et désactivez aussi l'expiration des clés Tailscale pour la machine initramfs via --auth-key avec un token non-éphémère, sinon votre serveur va se retrouver éjecté du réseau au pire moment.

Reconstruire et tester

Une fois tout configuré, un petit mkinitcpio -P pour reconstruire l'initramfs et c'est bon. Si ça ne marche pas du premier coup, vérifiez les logs avec journalctl -b. Mais attention, testez ça sur une VM ou une machine avec accès console (IPMI, iDRAC, KVM-over-IP) d'abord, parce que si le réseau de l'initramfs ne monte pas, votre serveur devient une brique inaccessible... et là, c'est le vrai drame de votre vie qui commence (et la découverte de France Travail) !

Au prochain reboot, votre serveur va donc démarrer, charger l'initramfs, se connecter à Tailscale, lancer Dropbear... et attendre patiemment que vous tapiez la passphrase depuis votre canapé.

Si vous gérez des serveurs chiffrés à distance, c'est le genre de setup un peu touchy à la base mais qui change la vie. Comme ça, plus besoin de supplier / soudoyer / menacer (chacun sa technique) le technicien du datacenter d'astreinte de brancher un clavier ^^.

Découvrir le tuto complet de Jyn ici !

Une infrastructure VPN hybride avec Headscale

5 mars 2026 à 18:12

Lorsque je travaille sur des projets personnels, j’ai besoin d’un environnement de test que je peux déployer rapidement et facilement.

Souvent, mon poste de travail n’est pas suffisant pour répondre à mes besoins. Je m’arme donc de deux serveurs clients légers sur lesquels je déploie mes machines virtuelles. Ces clients légers sont adaptés pour des tests rapides et sont pensés pour ne pas consommer trop d’énergie (ils sont allumés 24/7, donc j’essaye de faire attention).

Mais lorsque je fais des tests un peu plus poussés, ces serveurs sont vite limités (avec un Home-Assistant, un serveur média, le Omada Controller, des noeuds Kubernetes, et quelques autres services, ça commence à tirer sur la corde).

Pour continuer mes expériences et mon apprentissage, je loue alors un serveur dédié chez OVH sur lequel j’ai installé un Proxmox.

Mais avoir 2 infrastructures séparées, ça n’est pas très pratique. J’ai donc décidé de les relier entre elles.

Depuis ma workstation, j’ai un client tailscale (avec un serveur headscale) me permettant d’accéder à un bastion sur l’infra à distance.

Simple, efficace, pas cher.

Information

Tailscale est un VPN basé sur WireGuard qui permet de connecter des machines entre elles de manière sécurisée. Il intègre des ACLs, un DNS, un système de partage de fichiers et bien d’autres fonctionnalités.

En téléchargeant l’agent sur une machine, celle-ci peut rejoindre un réseau Tailscale et communiquer avec les autres machines du même compte. Dans une entreprise, cela peut permettre de donner accès à des ressources internes à des employés en télétravail ou gérer qui a accès à quels services.

Mais une limite me dérange un peu : si un hôte sur mon infra-cloud doit contacter un hôte dans mon LAN, je dois installer un client sur chacun des postes.

Devoir installer un agent sur chaque machine est un peu lourd.

En réponse à ça, il est possible d’utiliser les routes tailscales pour qu’un hôte devienne le point d’entrée vers un réseau.

Sur cette page, je vais vous expliquer comment j’ai configuré mon infrastructure pour que mes deux réseaux soient interconnectés (en installant un réseau Tailscale).
Installer Headscale

Euh… On parlait pas de Tailscale à la base ?

En réalité, je n’ai jamais utilisé Tailscale directement. Headscale est un serveur Tailscale auto-hébergé utilisant les clients Tailscale (et son réseau DERP).

Ainsi, l’authentification des clients se fait directement sur mon serveur, et je peux gérer les ACLs directement depuis ce dernier. Voici le schéma de ce que je veux mettre en place :

Headscale VPN hybride

Du fait de la nature de WireGuard, le trafic ne passe pas par le serveur Headscale, mais directement entre les clients. Headscale sert principalement à gérer les ACLs et à propager les routes (on verra ça plus tard).

Pour installer Headscale, je vais utiliser Docker Compose sur un VPS gratuit chez Oracle Cloud (je voulais qu’il soit hors des réseaux que je veux connecter).

J’utilise Traefik comme reverse proxy pour exposer le port 8080 de mon conteneur Headscale, mais il n’est pas forcément obligatoire d’exposer le port 8080.

services:
headscale:
image: headscale/headscale:0.22.3
volumes:

  • ./config:/etc/headscale/
  • ./data:/var/lib/headscale
    ports:

    - 8080:8080

  • 3478:3478/udp # STUN
    command: headscale serve
    restart: unless-stopped
    labels:
  • "traefik.enable=true"
  • "traefik.http.routers.headscale.rule=Host(headscale.une-tasse-de.cafe)"
  • "traefik.http.routers.headscale.entrypoints=secure"
  • "traefik.http.routers.headscale.tls.certresolver=letsencrypt"
  • "traefik.http.services.headscale.loadbalancer.server.port=8080"
    networks:
  • traefik-net

networks:
traefik-net:
external: true
driver: overlay
name: traefik-net

Si vous n'utilisez pas Traefik

Une fois traefik (ou un autre reverse proxy) configuré pour exposer le port 8080 du conteneur, je vais créer mon fichier ./config/config.yaml à partir de la template fournie par Headscale.

curl https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -o ./config/config.yaml

Voici les valeurs que j’ai modifiées pour mon cas d’usage :

server_url: https://headscale.une-tasse-de.cafe
listen_addr: 0.0.0.0:8080
dns_config:
base_domain: une-tasse-de.cafe

Pour la partie DNS, Tailscale va automatiquement ajouter un enregistrement DNS à chaque machine qui rejoint le réseau. Ainsi, je peux accéder à mes machines par leur nom de domaine via la syntaxe nom-machine.nom-utilisateur.base-domain.

Par exemple, si je nomme mon hôte cloud-router et que je suis l’utilisateur router, je pourrais accéder à mon hôte via cloud-router.router.une-tasse-de.cafe.
Ajouter un client Tailscale

Il existe deux méthodes d’authentification sur Headscale :

Ajout de notre token dans la base Headscale,
Authentification par token pré-authentifié.

Pour notre premier client Tailscale, testons la première méthode.

Je vais ajouter mon laptop (qui doit pouvoir accéder aux deux réseaux lorsque je suis en déplacement).

$ curl -fsSL https://tailscale.com/install.sh | sh
$ sudo tailscale up --login-server https://headscale.une-tasse-de.cafe
To authenticate, visit:

    https://headscale.une-tasse-de.cafe/register/nodekey:0592da68e42380d988c7a17c7c47728f2643e6cfb7988258bb3af7b193cba272

Via ce lien, on tombe sur cette page :

08de96fe92b24ca0d6628091b854075f.png

L’URL générée par Headscale ne sert qu’à donner la commande à exécuter pour valider l’authentification du client. Cette commande peut être exécutée depuis le conteneur Headscale, ou en exposant un socket gRPC à l’extérieur du conteneur et en y accédant depuis la cli Headscale.

Avant de valider l’authentification, je vais également créer un utilisateur quentin sur Headscale.

docker compose exec headscale headscale ns create quentin
docker compose exec headscale headscale nodes register --user quentin --key nodekey:0592da68e42380d988c7a17c7c47728f2643e6cfb7988258bb3af7b193cba272

Success s’affiche sur le terminal, cela nous indique que nous avons bien rejoint le réseau Tailscale.

Astuce

Si vous n’exposez pas le port 8080 de votre conteneur, vous pouvez toujours obtenir le token dans l’URL renvoyée par la commande tailscale up et l’ajouter directement dans la base Headscale.

Après l’étape de l’authentification, un tailscale status nous affiche les hôtes disponibles sur le réseau :

$ tailscale status
fd7a:115c:a1e0::1 laptop quentin linux -

On se sent un peu seul ici… je vais ajouter mon “routeur” coté cloud !

J’installe une machine cloud-router qui va rejoindre notre réseau Tailscale d’une seconde façon : via un token pré-authentifié.

Dans le premier cas, un administrateur (nous) a dû se connecter sur le serveur Headscale pour valider la connexion. Mais c’est assez peu flexible et sauf si votre utilisateur garde son terminal ouvert jusqu’à ce que vous ayez validé l’utilisateur : il n’est pas possible de valider un client en asynchrone.

C’est dans cette situation que les clés pré-authentifiées peuvent être un atout. Ce token est lié à un utilisateur, c’est pourquoi je vais d’abord créer router qui rassemblera les machines des différents réseaux.

docker compose exec headscale headscale ns create router

Maintenant, je demande un token pré-authentifié d’une durée de 24h.

$ docker compose exec headscale headscale --user router preauthkeys create --expiration 24h
9b4bfbb0ab0977fc6c9a907e90c6784ba3adfb381b73f1f5

Cette commande va me créer un token à usage unique pour authentifier automatiquement le client tailscale qui l’utilisera.

Astuce

Il est possible de faire un token réutilisable plusieurs fois en rajoutant --reusable.

sudo tailscale up --login-server https://headscale.une-tasse-de.cafe --auth-key 9b4bfbb0ab0977fc6c9a907e90c6784ba3adfb381b73f1f5

Nous n’avons pas eu à valider le client sur notre Headscale cette fois-ci, le client a pu rejoindre le réseau Tailscale directement.

Un tailscale status nous affiche bien nos deux clients :

$ tailscale status
fd7a:115c:a1e0::1 laptop quentin linux -
fd7a:115c:a1e0::2 cloud-router.router.une-tasse-de.cafe router linux -

J’ajoute maintenant un second hôte home-router qui sera le point d’entrée/sortie pour accéder au réseau distant.

$ tailscale status
fd7a:115c:a1e0::1 laptop quentin linux -
fd7a:115c:a1e0::2 cloud-router.router.une-tasse-de.cafe router linux -
fd7a:115c:a1e0::3 home-router.router.une-tasse-de.cafe router linux -

Maintenant, nous avons un hôte dans chacun des réseaux. L’hôte home-router peut accéder à la machine cloud-router , mais pas au réseau derrière (192.168.128.0/24).

Il m’est possible de configure les machines pour rediriger les paquets provenant du réseau Tailscale, mais il est possible de configurer ces routes directement sur Headscale, et c’est ce que je vais faire.

Sur la machine cloud-router, ayant une interface dans le réseau 192.168.128.0/24, je vais informer Headscale que je souhaite partager l’accès à ce réseau.

Pour cela, je peux configurer mon client via tailscale set --advertise-routes 192.168.128.0/24 --advertise-exit-node (toujours depuis la machine cloud-router).

Mais la route ne va pas automatiquement se propager sur les hôtes, il faut encore la valider directement sur le serveur Headscale.

$ docker compose exec headscale headscale route list
ID | Machine | Prefix | Advertised | Enabled | Primary
1 | cloud-router | 192.168.128.0/24 | true | false | false

La route est bien connue par Headscale, mais elle n’est pas encore activée.

Pour l’activer, je peux le faire depuis la cli docker compose exec headscale headscale route enable -r 1 où 1 correspond à l’ID de la route.

Sur l’hôte home-router, je configure également une route tailscale set --advertise-routes 192.168.1.0/24 (qui devra aussi être activée par docker compose exec headscale headscale route enable -r 2).

$ docker compose exec headscale headscale route list
ID | Machine | Prefix | Advertised | Enabled | Primary
1 | cloud-router | 192.168.128.0/24 | true | true | true
2 | home-router | 192.168.1.0/24 | true | true | true

Par défaut, les clients n’acceptent pas les routes propagées. Pour changer ça, il faut configurer le paramètre tailscale set --accept-routes.

Je vais rentrer ce paramètre sur nos 3 hôtes :

laptop
cloud-router
home-router

Depuis laptop (sur un réseau différent, ex 4G), je peux alors pinguer une adresse du réseau 192.168.1.0/24 et 192.168.128.0/24.

Maintenant, configurons les ACLs pour que seuls les utilisateurs ‘quentin’ et ‘routeur’ aient accès aux routes : ***

Dans mes paramètres Headscale config.yaml, j’ai ajouté le chemin du fichier ACL :

acl_policy_path: "/etc/headscale/acl.json"

Ce fichier est à créer à coté de config.yaml, voici un exemple de configuration :

{
"acls": [
{
"action": "accept",
"src": ["quentin", "router"],
"dst": ["192.168.1.0/24:", "192.168.128.0/24:", "router:*"]
},
]
}

Ainsi, les machines des utilisateurs quentin et router peuvent accéder aux réseaux 192.168.1.0/24 et 192.168.128.0/24 ainsi qu’aux autres hôtes du réseau Tailscale appartennant à l’utilisateur router (comme cloud-router et home-router).

Astuce

Si je veux restreindre le nombre de machines joignables, je peux juste préciser les IP individuellements (192.168.1.200/32, 192.168.128.15/32).

Je dois redémarrer mon conteneur Headscale pour prendre en compte les changements.
Configurer les routes

N’importe quelle machine appartennant à l’utilisateur router peut maintenant joindre les réseaux distants. Mais je ne veux pas avoir à installer un agent tailscale sur chacune des machines devant joindre ces plages (et c’est là que le terme router prend tout son sens dans le nom des machines).

Je vais alors configurer home-router et cloud-router pour être des passerelles vers les réseaux qu’elles connaissent.

Sur chacune d’entres elles, j’active le routage des paquets :

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

Depuis une machine quelconque (AKA sans le moindre agent tailscale) de mon réseau 192.168.1.0/24, je vais essayer de joindre une machine du réseau 192.168.128.0/24 via home-router (dont l’IP est 192.168.1.181)

root@quelconque:~# ip route add 192.168.128.0/24 via 192.168.1.181
root@quelconque:~# ping -c1 192.168.128.1
PING 192.168.128.1 (192.168.128.1) 56(84) bytes of data.
64 bytes from 192.168.128.1: icmp_seq=1 ttl=63 time=100 ms

Je fais également ça de l’autre coté (toujours sur une machine sans agent tailscale) :

root@autre-machine-quelconque:~# ip route add 192.168.1.0/24 via 192.168.128.10
root@autre-machine-quelconque:~# ping -c1 192.168.128.10
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=62 time=92.5 ms

Parfait, j’ai bien mes passerelles vers les réseaux respectifs !
OPNSense

Propager une route statique, c’est rigolo lorsque j’ai 3-4 machines à configurer mais ça devient vite fastidieux de devoir s’assurer que chaque machine possède la bonne route.

Mais par chance, mon routeur virtuel (coté cloud) est un OPNSense sur lequel je peux configurer des passerelles et des routes !

Ainsi, je peux aller sur l’interface web pour prévenir mon routeur de l’IP de la passerelle.

65c980822686fdfd2974bf8d2ad17045.png

Une fois qu’il connaît la passerelle, je lui demande de créer une route passant par cette passerelle pour accéder à mon réseau 192.168.1.0/24.

943d76b20e5b8ccef86f00ea7e1ff917.png

Ainsi dès qu’une VM va essayer de joindre mon réseau LAN, le routeur OPNSense va automatiquement rediriger les paquets vers la passerelle cloud-router.

Malheureusement, pour le chemin inverse, je n’ai pas encore d’autre solution que de configurer les routes de manière statique sur mes machines. La raison est que j’utilise encore ma box Orange qui ne propose aucune option pour ajouter des routes personnalisées.

Pour les routes statiques, je peux les configurer dans le fichier /etc/network/interfaces de cette manière :

allow-hotplug ens18
iface ens18 inet static
address 192.168.1.42
netmask 255.255.255.0
gateway 192.168.1.1
post-up ip route add 192.168.128.0/24 via 192.168.1.181

Conclusion

Je vais essayer de prévoir la principale question que vous pourriez vous poser :

Pourquoi Tailscale et pas un simple Wireguard ?

Parce qu’en réalité, j’ai beaucoup plus que 3 machines dans mon réseau VPN, et l’usage de Tailscale me permet de gérer les ACLs avec des permissions assez poussées sans avoir à bricoler des IPTables (si j’étais passé par du WireGuard classique).

Plusieurs options étaient alors possibles :

FireZone
ZeroTier
Netmaker
WireGuard + IPTables

Ayant déjà bricolé avec Tailscale, je me suis dirigé assez naturellement vers cette solution. Mais je vous invite fortement à tester ces autres options (et à me faire un retour si vous le souhaitez). Le combo Tailscale + Headscale me convient parfaitement mais je ne suis pas fermé à d’autres solutions.

Et concrètement, it just works. J’ai pu rapidement mettre en place mon infrastructure et la faire fonctionner sans trop de difficultés.


Direct link
❌