Aller au contenu

Chiffrement des disques (LUKS)

Un hôte chiffré (volume LUKS2 déclaré dans sa configuration disko) protège ses données en cas de vol ou de mise au rebut, mais réclame une passphrase à chaque démarrage. DNF industrialise ce point de friction avec deux garanties :

  • plusieurs moyens de déverrouillage coexistent en permanence, la perte d’un moyen (passphrase oubliée, YubiKey égarée) n’enferme jamais dehors ;
  • le prompt du boot se répond à distance, depuis le poste d’administration, sans écran ni clavier branchés sur l’hôte, et cela dès le tout premier démarrage qui suit l’installation.

Chaque moyen occupe son propre keyslot dans l’en-tête LUKS2. Tous sont valables en même temps, sur chaque volume chiffré de l’hôte.

MoyenOù il vitRôle
Passphrase partagéesops → luks-passphraseUne seule pour tout le parc, autorise la gestion des keyslots. C’est aussi elle qui formate le disque
Passphrase par hôtesops → luks/<hôte>/passphraseÀ confier à l’utilisateur de la machine sans exposer le parc
YubiKeys FIDO2Dérivé de la clé physiqueToucher la clé au boot, rien à taper
Fenêtre de terminal
just luks <hôte> # provisionne (ou converge) le chiffrement
just luks <hôte> pre # moitié hors ligne, sans toucher à la machine
just luks <hôte> passwd # fait tourner la passphrase par hôte
just luks <hôte> show # audit : table des keyslots + conformité
just luks <hôte> '' <ip> # cible une IP explicite (hôte pas encore résolu)

La recette est idempotente : chaque exécution converge et ne demande que ce qui manque. Elle est appelée automatiquement à deux moments, et il est rare d’avoir à la lancer soi-même :

Appelée parActionCe qu’elle fait
just install <hôte>preAvant le build, donc avant l’installation
just install-here <hôte>pre, sans compte nix (bootstrap-keys)Premier poste, depuis l’ISO
just configure <hôte>(défaut)Après le premier démarrage

Sans volume LUKS dans le disko, c’est un no-op journalisé.

La phase pre ne touche jamais la machine. Elle prépare ce qui doit exister avant que le système ne soit construit :

  1. la passphrase partagée dans sops si absente (demandée une seule fois pour tout le parc) ;
  2. la passphrase par hôte dans sops si absente, générée au hasard et sans rien demander (voir l’encadré ci-dessous) ;
  3. la clé d’hôte SSH de l’initrd, générée localement et rangée dans sops (luks/<hôte>/initrd-key, encodée en base64) ;
  4. l’inscription de l’hôte dans le manifeste public usr/secrets/luks.json (versionné en clair, il ne contient aucun secret) : c’est cette entrée qui active le module.

La phase par défaut ajoute ce qui demande la machine :

  1. repose la clé d’initrd depuis sops si elle manque sur la cible (elle n’en régénère jamais une nouvelle, cela invaliderait celle qu’a posée l’installation) ;
  2. sur une passerelle, relève l’IP WAN courante dans le manifeste (repli de just unlock, voir plus bas), à relancer si le fournisseur d’accès renumérote ;
  3. si aucune passphrase gérée ne déverrouille le volume (hôte installé avant la bascule vers le formatage partagé), demande sa passphrase d’installation une dernière fois et enrôle la partagée avec.

Puis committer et déployer :

Fenêtre de terminal
git add usr/secrets/luks.json
just commit "system(luks): <hôte>"
just apply <hôte>

Au déploiement, deux services idempotents convergent l’en-tête LUKS à chaque apply : luks-passphrase-sync (passphrases partagée et par hôte) et yubikey-luks-enroll (une entrée FIDO2 par YubiKey enrôlée sur le parc, voir Authentification forte).

  • Par hôte : just luks <hôte> passwd, puis commit et apply. L’ancien keyslot est supprimé et remplacé au déploiement suivant.
  • Partagée : modifier luks-passphrase via just sops, puis apply sur tout le parc — chaque hôte chiffré fait tourner son keyslot au passage.

Le service de synchronisation détecte les changements grâce à un registre local (/var/lib/luks-passphrase/ledger) : une valeur sops modifiée enrôle la nouvelle passphrase puis tue l’ancien slot. Les slots absents du registre (FIDO2, enrôlements manuels) ne sont jamais touchés.

Tout hôte chiffré suit la même séquence, sans rien à régler :

  1. YubiKey — une clé enrôlée et branchée fait clignoter sa diode : la toucher ouvre le volume, rien à taper. Aucune clé branchée, l’étape dure ~6 s ; une clé que personne ne touche, ~30 s (limite du firmware).
  2. Passphrase — une invite est ensuite posée, sans limite de temps. Elle accepte la passphrase par hôte, la partagée ou celle d’installation.

L’invite de l’étape 2 est une seule et même demande, servie en parallèle sur la console physique (/dev/tty1) et par ssh sur le port 2222. Rien à choisir, aucune fenêtre à rater : le premier qui répond ouvre le volume.

À distance, l’initrd démarre un sshd dédié sur le port 2222, avec sa propre clé d’hôte persistante, accessible uniquement avec la clé de déploiement nix. just unlock y répond :

Fenêtre de terminal
just unlock <hôte> # passphrase lue dans sops, sinon demandée au terminal

just unlock attend que le port 2222 apparaisse (un point par tentative), lit la passphrase partagée dans sops et la pousse dans l’initrd, puis vérifie que la machine a bien quitté son initrd avant d’annoncer un succès. Si elle est refusée, il réessaie avec la passphrase par hôte. C’est ce que just full-install appelle tout seul après le premier redémarrage. Sur un hôte non chiffré, c’est un no-op journalisé.

Aucune passphrase stockée acceptée, ou pas de clé d’infrastructure sur le poste : depuis un terminal, just unlock présente le prompt LUKS via systemd-tty-ask-password-agent. Il sonde le 2222 sur l’IP explicite (si fournie), le nom d’hôte, puis l’IP WAN enregistrée dans le manifeste. Dans les deux cas, la passphrase acceptée, le démarrage continue et le port 22 revient.

just luks <hôte> show n’écrit rien : il interroge la machine et rend compte. Il sort en erreur dès qu’un contrôle échoue, donc il s’utilise aussi dans un script.

Fenêtre de terminal
just luks <hôte> show

Un premier bloc dresse la table des keyslots de chaque volume. Rien n’y est supposé : chaque passphrase gérée est réellement essayée contre l’en-tête, donc la colonne « Check » dit ce que le volume accepte aujourd’hui, pas ce qu’un registre a mémorisé.

/dev/disk/by-partlabel/disk-main-luks
Slot Content Source Check
---- ------------------- --------------------------- -----------------
0 Shared passphrase sops luks-passphrase opens the volume
1 Host passphrase sops luks/poste/passphrase opens the volume
2 YubiKey alice/main systemd-fido2 token enrolled
7 Unknown - not managed by DNF
Slots in use: 4/32

Un second bloc audite le déverrouillage distant, point par point :

ContrôleCe qu’il attrape
Hôte présent dans luks.jsonLe module resterait inerte
Clé d’initrd sur la cible (+ empreinte)Prochain apply sans déverrouillage distant
Sauvegarde sops de même empreinteUne réinstallation perdrait l’accès distant
sshd d’initrd activé dans la configurationnix.pub manquant, ou hôte hors manifeste
Chargeur d’amorçage postérieur à la cléLe piège : clé absente de l’initrd qui démarre
Pilotes des cartes filaires présents dans l’initrdPas de réseau en stage 1 : rien ne répond sur 2222
Système démarré = configuration auditéeUn apply en attente rendrait l’audit trompeur
IP WAN relevée (passerelles)Injoignable en initrd une fois le VPN tombé
EmplacementContenuStatut
usr/secrets/luks.jsonManifeste des hôtes provisionnés + IP WAN des passerellesPublic, versionné
secrets.yaml → luks-passphrasePassphrase partagée du parcChiffré sops
secrets.yaml → luks/<hôte>/passphrasePassphrase par hôteChiffré sops
secrets.yaml → luks/<hôte>/initrd-keyClé d’hôte SSH de l’initrd (base64)Chiffré sops
/var/lib/luks-initrd/ (cible)Copie de travail de la clé d’initrdPersistant, hors dépôt
/var/lib/luks-passphrase/ledger (cible)Registre des keyslots gérés (détection des rotations)Persistant, hors dépôt

Le module expose deux options : darkone.system.luks.enable (défaut true) et darkone.system.luks.sshPort (défaut 2222).