Skip to content

Dracut secureboot tpm2

Démarrage Sécurisé Dracut UKI, Signature MOK et Déverrouillage TPM2 LUKS

Objectif d'administration système

Ce projet automatise le durcissement physique et cryptographique des postes de travail et ordinateurs portables (groupe Ansible desktop). Il répond à deux besoins : 1. Sécurité physique : Prévention des attaques de type Evil Maid (altération du noyau, de l'initramfs ou de la ligne de commande sur disque non chiffré) grâce au chiffrement intégral LUKS2 et à UEFI Secure Boot. 2. Expérience utilisateur fluide : Déverrouillage transparent au démarrage grâce au coprocesseur matériel TPM 2.0, sans saisie manuelle de mot de passe, avec secours via une clé de récupération séquestrée sur un serveur centralisé sécurisé ou un coffre-fort de mots de passe.


1. Chaîne de Confiance Matérielle (Root of Trust)

Dans une installation Linux chiffrée standard, la partition /boot et l'initramfs restent en clair. Un attaquant ayant un accès physique temporaire à la machine peut injecter un keylogger dans l'initramfs pour capturer la phrase de passe au démarrage suivant.

Pour fermer cette brèche, voici la chaîne de confiance mise en place :

graph TD
    Hardware["Composant Matériel : Puce TPM 2.0"] -->|"Mesure PCR 0 (Firmware/UEFI)"| SecureBoot{"UEFI Secure Boot Actif ?"}
    SecureBoot -->|"Non"| Abort["Refus de Boot / Échec Matériel"]
    SecureBoot -->|"Oui"| MOK["Vérification Clé MOK (Machine Owner Key)"]

    MOK -->|"Signature Valide"| UKI["Chargement Unified Kernel Image (UKI .efi)"]

    subgraph UKI_Package["Unified Kernel Image - Fichier unique scellé"]
        Stub["Systemd Boot Stub"]
        Kernel["Noyau Linux vmlinuz"]
        Initramfs["Initramfs Dracut (Modules LUKS/TPM2)"]
        Cmdline["Ligne de commande kernel (/etc/kernel/cmdline)"]
    end

    UKI --> UKI_Package

    UKI_Package --> PCRCheck{"Mesure PCRs 0+7 Conforme ?"}
    PCRCheck -->|"Firmware ou SecureBoot altéré"| FallbackPass["Demande de Clé de Récupération LUKS"]
    PCRCheck -->|"Mesures Identiques"| TPMRelease["Libération de la clé LUKS par le TPM2"]

    TPMRelease --> RootUnlock["Déchiffrement /dev/mapper/root"]
    RootUnlock --> BootOS["Démarrage du Système Debian"]

2. Les Composants Techniques

A. L'Unified Kernel Image (UKI)

Au lieu d'avoir un chargeur (GRUB), un noyau séparé (vmlinuz) et une archive initrd.img, l'UKI compile ces éléments dans un binaire exécutable UEFI unique (.efi).

La ligne de commande du noyau est scellée à l'intérieur du binaire (/etc/kernel/cmdline), ce qui empêche toute modification de paramètres au démarrage (comme init=/bin/bash ou la désactivation de sécurités) :

# /etc/kernel/cmdline (inclus dans l'UKI)
rd.luks.name=UUID_DU_DISQUE=nvme0n1p3_crypt rd.luks.options=tpm2-device=auto,discard root=/dev/mapper/vg0-root rootfstype=ext4 ro console=tty1 systemd.show_status=1 zswap.enabled=1 lockdown=confidentiality

Abstract

Le paramètre lockdown=confidentiality active le mode de verrouillage strict du noyau Linux en présence de Secure Boot, empêchant même le compte root de modifier la mémoire vive du kernel via /dev/mem ou de charger des modules non signés.


B. Machine Owner Key (MOK) et Signature

Plutôt que de dépendre uniquement des clés Microsoft préinstallées dans le firmware, nous générons une autorité de signature locale spécifique à notre infrastructure : * Clé privée : /etc/ssl/MOK.key (droits stricts 0400 root:root) * Certificat public : /etc/ssl/MOK.crt et format DER /etc/ssl/MOK.der * Enrôlement NVRAM : La clé publique est enregistrée dans la mémoire non-volatile de la carte mère via mokutil --import /etc/ssl/MOK.der. L'administrateur valide physiquement l'empreinte lors d'un reboot unique (écran bleu Shim UEFI Key Management).

Chaque UKI généré par Dracut est automatiquement signé avant d'être déposé dans la partition ESP (/boot/efi/EFI/Linux/ ou /boot/efi/EFI/BOOT/BOOTX64.EFI) :

sbsign --key /etc/ssl/MOK.key --cert /etc/ssl/MOK.crt --output /boot/efi/EFI/Linux/linux-signed.efi /boot/efi/EFI/Linux/linux.efi


C. Déverrouillage Matériel TPM 2.0 (PCR 0+7)

Le volume chiffré LUKS2 est scellé dans le TPM 2.0 à l'aide de l'utilitaire systemd-cryptenroll.

Nous lions le déchiffrement aux registres PCR suivants : * PCR 0 : Mesure du BIOS, du firmware UEFI de la carte mère et des microcodes processeur. * PCR 7 : Mesure de l'état d'activation de Secure Boot, de la liste des clés autorisées (PK, KEK, db) et de la clé MOK active.

systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-uuid/<UUID_LUKS>

Conséquence en cas d'attaque : Si le disque dur ou le SSD est volé et branché dans une autre machine, ou si un attaquant tente de désactiver Secure Boot ou d'insérer un firmware corrompu, les mesures des PCR 0 et 7 changent. Le TPM 2.0 refuse catégoriquement de libérer la clé cryptographique.


3. Séquestration de Clé de Secours et Élimination de la Passphrase Faible

L'automatisation du déverrouillage présente un risque opérationnel : si le firmware de la carte mère est mis à jour (mise à jour BIOS/UEFI), les registres PCR changent et la machine ne peut plus démarrer automatiquement.

La procédure automatisée applique une politique stricte en 4 étapes :

  1. Génération d'une clé de secours aléatoire haute entropie : Génération d'une clé de 512 bits (64 octets hexadécimaux) et ajout dans un slot de clé LUKS libre :

    RECOVERY_KEY=$(openssl rand -hex 32)
    echo -n "$RECOVERY_KEY" | cryptsetup luksAddKey /dev/disk/by-uuid/<UUID> --key-slot 2
    

  2. Séquestration sécurisée de la clé de secours : La clé de récupération est immédiatement sauvegardée sur un stockage sécurisé centralisé ou un coffre-fort de mots de passe hors-ligne avec des permissions d'accès restreintes (0400).

  3. Validation de l'enrôlement TPM2 : Vérification que le slot LUKS2 de type systemd-tpm2 est bien présent et actif dans les métadonnées (cryptsetup luksDump).

  4. Destruction du mot de passe initial de provisionnement : Pour interdire toute tentative d'attaque par dictionnaire sur le mot de passe utilisateur d'installation, le slot du mot de passe initial est révoqué via cryptsetup luksKillSlot. Le disque ne peut plus être ouvert que par le TPM 2.0 de la machine autorisée, ou manuellement via la clé de secours séquestrée.


4. Configuration Dracut (/etc/dracut.conf.d/10-tpm2.conf)

Pour que l'environnement initramfs soit capable d'interagir avec la puce TPM2 et de charger les volumes LVM/LUKS de manière autonome :

# Forcer la génération au format Unified Kernel Image (UKI)
build_uki="yes"
uki="/boot/efi/EFI/Linux/linux.efi"

# Modules Dracut indispensables intégrés dans l'image
add_dracutsysrootdir="yes"
add_dracutmodules+=" tpm2-tss systemd-cryptsetup lvm dm rootfs-block "

# Pilotes noyau obligatoires pour l'accès matériel direct
drivers+=" tpm tpm_tis tpm_crb tpm_tis_core nvme ext4 xfs "

# Compression rapide
compress="zstd"

5. Automatisation des Mises à Jour Kernel via Hook APT

Chaque mise à jour du noyau Debian (linux-image-amd64) doit reconstruire l'UKI et signer immédiatement le nouveau binaire. Un hook APT est déposé dans /etc/kernel/postinst.d/zz-dracut-uki :

#!/bin/sh
set -e
version="$1"
echo "[Kernel Hook] Reconstruction de l'UKI Dracut signée MOK pour le noyau $version..."
dracut --force --kver "$version"
sbsign --key /etc/ssl/MOK.key --cert /etc/ssl/MOK.crt \
       --output /boot/efi/EFI/Linux/linux.efi /boot/efi/EFI/Linux/linux.efi
echo "[Kernel Hook] UKI signée avec succès."

6. Procédure de Récupération d'Urgence

Si la machine affiche au boot la demande de déverrouillage manuel suite à une modification matérielle ou un reset BIOS :

  1. Se connecter sur votre serveur de gestion ou consulter votre gestionnaire de mots de passe de secours.
  2. Récupérer la clé de secours correspondant à la machine (ex: le fichier /root/luks-recovery-key-<hostname>.txt ou l'entrée correspondante dans votre coffre-fort).
  3. Saisir la chaîne hexadécimale à l'écran de démarrage.
  4. Une fois la session ouverte, resynchroniser les PCR TPM2 avec l'état actuel de la machine en réexécutant le playbook ci-dessous.

7. Playbook Ansible Prêt à l'Emploi (playbook-dracut-tpm2.yaml)

Ce playbook autonome est prêt à être exécuté sur n'importe quel poste client ou PC portable sous Debian ou distribution dérivée avec LUKS2 et puce TPM2 :

---
- name: Hardening du Boot - Dracut UKI, Signature MOK et TPM2 Auto-Unlock
  hosts: all
  become: true
  vars:
    # Registres PCR liés : 0 (firmware/UEFI) et 7 (Secure Boot / MOK)
    tpm2_pcrs: "0+7"
    # Répertoire de destination de l'exécutable UKI dans l'ESP
    esp_linux_dir: "/boot/efi/EFI/Linux"
    # Fichier local de sauvegarde de la clé de secours
    recovery_key_dest: "/root/luks-recovery-key-{{ inventory_hostname }}.txt"

  tasks:
    # -------------------------------------------------------------------------
    # 1. CONTRÔLE DES PRÉREQUIS MATÉRIELS ET SYSTÈME
    # -------------------------------------------------------------------------
    - name: Vérifier la présence du matériel TPM2 (/dev/tpmrm0 ou /dev/tpm0)
      ansible.builtin.stat:
        path: /dev/tpmrm0
      register: tpm_dev

    - name: Vérifier la présence de /etc/crypttab
      ansible.builtin.stat:
        path: /etc/crypttab
      register: crypttab_stat

    - name: Interrompre si le TPM2 ou /etc/crypttab est absent
      ansible.builtin.meta: end_host
      when: not tpm_dev.stat.exists or not crypttab_stat.stat.exists

    - name: Identifier le conteneur LUKS actif dans /etc/crypttab
      ansible.builtin.shell: |
        grep -v '^#' /etc/crypttab | grep -v '^$' | head -n 1
      register: crypttab_entry
      changed_when: false

    - name: Extraire le nom du mapper et l'UUID LUKS
      ansible.builtin.set_fact:
        luks_mapper: "{{ crypttab_entry.stdout.split()[0] }}"
        luks_target: "{{ crypttab_entry.stdout.split()[1] }}"
        luks_uuid: "{{ crypttab_entry.stdout.split()[1] | regex_replace('^UUID=', '') }}"

    - name: Vérifier que le volume est bien au format LUKS2
      ansible.builtin.command: "cryptsetup isLuks --type luks2 /dev/disk/by-uuid/{{ luks_uuid }}"
      changed_when: false

    # -------------------------------------------------------------------------
    # 2. INSTALLATION DES PAQUETS DE SÉCURITÉ REQUIS
    # -------------------------------------------------------------------------
    - name: Installer Dracut, les outils TPM2 et sbsigntool
      ansible.builtin.apt:
        name:
          - dracut
          - dracut-core
          - sbsigntool
          - tpm2-tools
          - systemd-cryptsetup
          - binutils
        state: present
        update_cache: true

    # -------------------------------------------------------------------------
    # 3. CONFIGURATION DE LA LIGNE DE COMMANDE KERNEL ET DRACUT
    # -------------------------------------------------------------------------
    - name: Détecter le point de montage racine et son type de système de fichiers
      ansible.builtin.set_fact:
        root_device: "{{ (ansible_facts['mounts'] | selectattr('mount', 'equalto', '/') | map(attribute='device') | first) | default('/dev/mapper/' + luks_mapper) }}"
        root_fstype: "{{ (ansible_facts['mounts'] | selectattr('mount', 'equalto', '/') | map(attribute='fstype') | first) | default('ext4') }}"

    - name: S'assurer du dossier /etc/kernel
      ansible.builtin.file:
        path: /etc/kernel
        state: directory
        mode: '0755'

    - name: Écrire /etc/kernel/cmdline (scellé dans l'UKI)
      ansible.builtin.copy:
        dest: /etc/kernel/cmdline
        mode: '0644'
        content: >-
          rd.luks.name={{ luks_uuid }}={{ luks_mapper }}
          rd.luks.options=tpm2-device=auto,discard
          root={{ root_device }}
          rootfstype={{ root_fstype }}
          ro console=tty1 systemd.show_status=1 zswap.enabled=1 lockdown=confidentiality

    - name: Configurer Dracut pour l'UKI et le TPM2 (/etc/dracut.conf.d/10-tpm2.conf)
      ansible.builtin.copy:
        dest: /etc/dracut.conf.d/10-tpm2.conf
        mode: '0644'
        content: |
          build_uki="yes"
          uki="{{ esp_linux_dir }}/linux.efi"
          add_dracutsysrootdir="yes"
          add_dracutmodules+=" tpm2-tss systemd-cryptsetup lvm dm rootfs-block "
          drivers+=" tpm tpm_tis tpm_crb tpm_tis_core nvme ext4 xfs "
          compress="zstd"

    # -------------------------------------------------------------------------
    # 4. GESTION DE L'AUTORITÉ DE SIGNATURE SECURE BOOT (MOK)
    # -------------------------------------------------------------------------
    - name: Vérifier si les clés MOK locales existent déjà
      ansible.builtin.stat:
        path: /etc/ssl/MOK.key
      register: mok_key_stat

    - name: Générer la paire de clés MOK locale si absente
      ansible.builtin.command: >
        openssl req -new -x509 -newkey rsa:2048 -nodes -days 3650
        -subj "/CN=Machine Owner Key - {{ inventory_hostname }}/"
        -keyout /etc/ssl/MOK.key -out /etc/ssl/MOK.crt
      when: not mok_key_stat.stat.exists

    - name: Convertir le certificat MOK au format DER pour l'UEFI
      ansible.builtin.command: openssl x509 -outform DER -in /etc/ssl/MOK.crt -out /etc/ssl/MOK.der
      when: not mok_key_stat.stat.exists

    - name: Protéger les permissions de la clé privée MOK
      ansible.builtin.file:
        path: /etc/ssl/MOK.key
        mode: '0400'
        owner: root
        group: root

    # -------------------------------------------------------------------------
    # 5. DÉVERROUILLAGE TPM2 ET CLÉ DE SECOURS
    # -------------------------------------------------------------------------
    - name: Vérifier si le TPM2 est déjà enrôlé dans les slots LUKS
      ansible.builtin.shell: "cryptsetup luksDump /dev/disk/by-uuid/{{ luks_uuid }} | grep -q 'systemd-tpm2'"
      register: tpm_enrolled
      changed_when: false
      failed_when: false

    - name: Enrôler le TPM2 avec les PCRs 0+7
      ansible.builtin.command: >
        systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs={{ tpm2_pcrs }} /dev/disk/by-uuid/{{ luks_uuid }}
      when: tpm_enrolled.rc != 0

    - name: Générer et séquestrer une clé de récupération de 512 bits si non existante
      ansible.builtin.stat:
        path: "{{ recovery_key_dest }}"
      register: recovery_file_stat

    - name: Créer et enregistrer la clé de secours
      when: not recovery_file_stat.stat.exists
      block:
        - name: Générer une chaîne aléatoire sécurisée de 64 caractères
          ansible.builtin.command: openssl rand -hex 32
          register: random_key
          changed_when: false

        - name: Ajouter la clé aléatoire comme clé de secours dans LUKS
          ansible.builtin.shell: |
            echo -n "{{ random_key.stdout }}" | cryptsetup luksAddKey /dev/disk/by-uuid/{{ luks_uuid }} -
          no_log: true

        - name: Écrire la clé de secours dans le fichier local sécurisé
          ansible.builtin.copy:
            dest: "{{ recovery_key_dest }}"
            mode: '0400'
            owner: root
            group: root
            content: "{{ random_key.stdout }}\n"

    # -------------------------------------------------------------------------
    # 6. GÉNÉRATION DE L'UKI ET SIGNATURE MOK
    # -------------------------------------------------------------------------
    - name: S'assurer que le dossier EFI Linux existe
      ansible.builtin.file:
        path: "{{ esp_linux_dir }}"
        state: directory
        mode: '0755'

    - name: Compiler l'image UKI avec Dracut
      ansible.builtin.command: dracut --force --kver "{{ ansible_kernel }}"

    - name: Signer l'UKI générée avec la clé MOK
      ansible.builtin.command: >
        sbsign --key /etc/ssl/MOK.key --cert /etc/ssl/MOK.crt
        --output {{ esp_linux_dir }}/linux.efi {{ esp_linux_dir }}/linux.efi

    # -------------------------------------------------------------------------
    # 7. HOOK KERNEL AUTOMATISÉ POUR LES MISES À JOUR APT
    # -------------------------------------------------------------------------
    - name: Déployer le hook APT postinst de reconstruction et signature automatique
      ansible.builtin.copy:
        dest: /etc/kernel/postinst.d/zz-dracut-uki
        mode: '0755'
        owner: root
        group: root
        content: |
          #!/bin/sh
          set -e
          version="$1"
          echo "[Kernel Hook] Reconstruction de l'UKI Dracut signée pour $version..."
          dracut --force --kver "$version"
          sbsign --key /etc/ssl/MOK.key --cert /etc/ssl/MOK.crt \
                 --output {{ esp_linux_dir }}/linux.efi {{ esp_linux_dir }}/linux.efi
          echo "[Kernel Hook] UKI signée avec succès."

Comment l'exécuter :

ansible-playbook -i "mon_pc_portable," -u root --ask-become-pass playbook-dracut-tpm2.yaml

Première activation de Secure Boot

Si votre clé MOK vient d'être créée, importez-la dans l'UEFI via :

mokutil --import /etc/ssl/MOK.der
Définissez un mot de passe temporaire à la demande de mokutil. Au redémarrage suivant, l'écran bleu UEFI Shim / MOK Management s'affiche : sélectionnez Enroll MOK, puis Continue, entrez le mot de passe temporaire et validez. Votre système démarre ensuite de façon fluide et totalement chiffrée sans mot de passe !