Всем доброго дня, ночи! Этот пост будет полезен тем, кто использует шифрование данных LUKS и хочет производить decryptдешифровку дисков под Linux (Debian, Ubuntu) на стадии расшифровки root раздела. И такой информации в интернете я найти не смог.

Совсем недавно с увеличением количества дисков в полках, столкнулся с проблемой расшифровки дисков с использованием более чем известного метода через /etc/crypttab. Лично я выделяю несколько проблем использования этого метода, а именно то, что файл читается только после загрузки (mount) root-раздела, что негативно сказывается на импорте ZFS, в частности если они были собраны из разделов на *_crypt устройстве, или же mdadm рейды, собранные так же из разделов. Мы же все знаем, что можно использовать parted на LUKS контейнерах? И также проблема раннего старта других служб, когда массивов еще нет, а использовать уже что-то надо (я работаю с кластеризованным Proxmox VE 5.x и ZFS over iSCSI).

Немного о ZFSoverISCSI
iSCSI работает у меня через LIO, и собственно когда стартует iscsi-таргет и не видит ZVOL устройств, он их просто-напросто удаляет из конфигурации, что не дает возможности загружаться гостевым системам. Отсюда либо восстановление бэкапа json файла, либо ручное добавление устройств с идентификаторами каждой VM, что просто ужас, когда таких машин десятки и в конфигурации каждой более 1 диска.

И второй вопрос, который я рассмотрю, это чем производить расшифровку (это ключевой момент статьи). И об этом мы поговорим ниже, заходите под кат!

Чаще всего на просторах интернета используют ключевой файл (само-собой добавленный перед этим в слот командой — cryptsetup luksAddKey), либо в редких исключениях (в русско-язычном интернете очень скудная информация) — скрипт decrypt_derived, лежащий в /lib/cryptsetup/script/ (конечно, есть еще способы, но я использовал именно эти два, что и легло в основу статьи). Так же я стремился к полному автономному включению после перезагрузок, без каких либо дополнительных команд в консоли, чтобы у меня всё «взлетало» сразу. Поэтому, зачем ждать? —

Приступаем!

Предполагаем систему, например Debian, установленную на crypto-раздел sda3_crypt и десяток дисков, готовых к шифрованию и созданию чего душе угодно. Мы имеем ключевую фразу (passphrase) для разблокировки sda3_crypt и именно с этого раздела мы будем на запущенной (расшифрованной) системе снимать «хэш» от пароля и добавлять на остальные диски. Всё элементарно, в консоли выполняем:

/lib/cryptsetup/scripts/decrypt_derived sda3_crypt | cryptsetup luksFormat /dev/sdX

где X — это наши диски, разделы и т.д.

После шифрования дисков «хешем» от нашей ключевой-фразы, необходимо узнать UUID, либо ID — смотря кто и к чему привык. Берём данные из /dev/disk/by-uuid и by-id соответственно.

Следующий этап подготовка файлов и мини-скриптов для работы необходимых нам функций, приступаем:

cp -p /usr/share/initramfs-tools/hooks/cryptroot /etc/initramfs-tools/hooks/
cp -p /usr/share/initramfs-tools/scripts/local-top/cryptroot /etc/initramfs-tools/scripts/local-top/

далее

touch /etc/initramfs-tools/hooks/decrypt && chmod +x /etc/initramfs-tools/hooks/decrypt

Содержимое ../decrypt
#!/bin/sh

cp -p /lib/cryptsetup/scripts/decrypt_derived "$DESTDIR/bin/decrypt_derived"


далее

touch /etc/initramfs-tools/hooks/partcopy && chmod +x /etc/initramfs-tools/hooks/partcopy

Содержимое ../partcopy
#!/bin/sh

cp -p /sbin/partprobe "$DESTDIR/bin/partprobe"
cp -p /lib/x86_64-linux-gnu/libparted.so.2 "$DESTDIR/lib/x86_64-linux-gnu/libparted.so.2"
cp -p /lib/x86_64-linux-gnu/libreadline.so.7 "$DESTDIR/lib/x86_64-linux-gnu/libreadline.so.7"


еще немного

touch /etc/initramfs-tools/scripts/local-bottom/partprobe && chmod +x /etc/initramfs-tools/scripts/local-bottom/partprobe

Содержимое ../partprobe
#!/bin/sh

$DESTDIR/bin/partprobe


и последнее, перед update-initramfs, нужно отредактировать файл /etc/initramfs-tools/scripts/local-top/cryptroot, начиная со строки ~360, кусок кода ниже

Оригинал

                # decrease $count by 1, apparently last try was successful.
                count=$(( $count - 1 ))
                
                message "cryptsetup ($crypttarget): set up successfully"
                break


и приводим к такому виду

Отредактированный

                # decrease $count by 1, apparently last try was successful.
                count=$(( $count - 1 ))
                

                /bin/decrypt_derived $crypttarget | cryptsetup luksOpen /dev/disk/by-uuid/ *CRYPT_MAP*
                /bin/decrypt_derived $crypttarget | cryptsetup luksOpen /dev/disk/by-id/ *CRYPT_MAP*

                message "cryptsetup ($crypttarget): set up successfully"
                break


Обратите внимание, что здесь можно использовать либо UUID, либо ID. Главное чтобы нужные драйвера на устройства HDD/SSD были добавлены в /etc/initramfs-tools/modules. Узнать используемый драйвер можно командой udevadm info -a -n /dev/sdX | egrep 'looking|DRIVER'.

Теперь, когда мы закончили и все файлы на месте, запускаем update-initramfs -u -k all -v, в логировании не должно быть ошибок выполнения наших скриптов. Перезагружаемся, вводим ключевую-фразу и немного ждём, в зависимости от количества дисков. Далее система запустится и на конечной стадии запуска, а именно после «маунтинга» root-раздела, будет выполнена команда partprobe — она найдет и подцепит все созданные разделы на LUKS устройствах и любые массивы, будь то ZFS или mdadm соберутся без проблем! И всё это до загрузки основных служб и сервисов, которым нужны эти диски/массивы.

update1: Как заметил AEP, данный способ работает только для LUKS1.

Комментарии (19)


  1. kvazimoda24
    23.06.2019 19:19

    Главная проблема, как ввести ключевую фразу на сервере? У сервера очень часто не подключены монитор с клавиатурой. Или админ находится не в серверной в момент перезагрузки. Отсюда вижу два варианта решения проблемы: либо запихивать в initramfs ssh сервер, либо отказаться от шифрования раздела root, при этом зашифровав всё остальное.


    1. quality Автор
      23.06.2019 19:32

      Это вовсе не проблема. Это отдельный момент, но решается очень просто: apt install dropbear. И добавить открытый ключ в authorized_keys дропбира, иначе зайти не получится. И grub настраиваем на поднятие интерфейса, говоря ядру ip= в cmd_linux. Но не забываем на «инкогнито» сетевые карты прописать драйвер в /etc/initramfs-tools/modules и пересобрать образ. В следующей статье я опишу более интересный способ, и dropbear там будет присутствовать.


      1. AEP
        23.06.2019 21:34

        Есть еще способ, о котором я планирую написать подробно позже (но пока отладил только на Arch Linux). Смысл: большинство BIOS'ов (но не все — например, с «Intel Corporation S1200SP/S1200SP, BIOS S1200SP.86B.03.01.0042.013020190050 01/30/2019» фокус не проходит) не чистит память при перезагрузке, так что там можно сохранить второй ключ. При этом первый ввод ключевой фразы осуществляется либо с клавиатуры, либо через ssh, а перед перезагрузкой сервер прочитает второй ключ с зашифрованного диска и сложит себе в память. BIOS его не почистит, а initramfs подхватит.


        1. quality Автор
          23.06.2019 21:41

          Интересный момент, согласен, но здесь присутствует уязвимость «холодной перезагрузки» (если не ошибаюсь), когда можно выкрасть ключ. Почистить память достаточно просто, надо включить Check Memory в BIOS. Да, загрузка будет немного дольше, в зависимости от объёма ОЗУ, но память будет чиста, и похитить ключ нельзя (но это конечно можно провернуть, только при физическом доступе к серверу).


      1. YourChief
        23.06.2019 21:47
        +1

        А от чего призваны защитить все эти манипуляции с шифрованием рут-диска? Можно обесточить сервер, подменить инитрамдрайв и выудить ключ при вводе таким образом.


        1. quality Автор
          23.06.2019 22:20

          Это всё теоретически и можно сделать, но увы, только при физическом доступе к серверу, что очень мало-вероятно и на практике невозможно.


          1. YourChief
            23.06.2019 22:24

            Так а от чего тогда шифрование? Если физического доступа нет, то никаких проблем нет. Я пытаюсь понять как выглядит случай, когда эти меры помогают.


            1. rPman
              24.06.2019 10:35

              Мое мнение, шифрование диска — это дешевый метод защиты от дешевой атаки с доступом к серверам (вытащить незашифрованные диски большого ума не надо), а шифрованные — необходимо подменить загрузчик.

              С загрузчиком у пользователя есть варианты, например перенести его на свое устройство, т.е. сервер стартует, грузит загрузчик по сети, загрузчик проверяет как может железо на подмену (вплоть до бенчмарков cpu и перепрошивки в биос своих идентификаторов, в этом случае виртуализацией не спастись), и уже тут тоже есть где разгуляться.

              На каждый защитный шаг есть своя атака но итоговый взлом поднимает его цену до небес… т.е. начинает работать принцип неуловимого джо.


              1. YourChief
                24.06.2019 11:10

                Про проверку железа — по описанию похоже на то, что делает TPM при сверке PCR, зависящих от платформы и запускаемого кода. Только там ещё можно сделать так, чтобы секрет не расшифровался, если не сходятся регистры, и эта проверка энфорсится внутри самого TPM. Тогда действительно получится прочная защита. А отредактировать рамдрайв много ума не нужно — это обычный CPIO-архив. Усилий потребуется не сильно больше или может даже меньше, чем для настройки всей этой конструкции.

                У меня нет дискретного TPM (а софтварный, внутри ME, вызывает сомнения). Поэтому я пошёл по пути подписи всей цепи загрузки. Я использую UEFI Secure Boot с загрузчиком, подписанным моими сертификатами, ядром и рамдрайвом, подписанными моим ключом: github.com/Snawoot/linux-secureboot-kit


                1. dartraiden
                  25.06.2019 20:11

                  У меня нет дискретного TPM (а софтварный, внутри ME, вызывает сомнения). Поэтому я пошёл по пути подписи всей цепи загрузки
                  Одно (шифрование с задействованием TPM) другому (SecureBoot) не мешает, они, скорее, дополняют друг друга.

                  Собственно, если злоумышленник получит физический доступ к ПК, то он просто обнулит настройки прошивки, вытащив батарейку, войдёт в настройки и отключит SecureBoot. После чего, всё будет грузиться, невзирая на наличие или отсутствие подписей.

                  Если же используется шифрование и задействован один из регистров TPM (BIOS configuration), то отключение SecureBoot не позволит расшифровать накопитель, поскольку настройки изменились и «регистры не сойдутся».

                  Внешний TPM. конечно, хорош, но за неимением его и fTPM лучше, чем ничего, хотя, такие «софтверные» реализации в большей степени подвержены атакам.


                  1. YourChief
                    25.06.2019 20:21
                    +1

                    Да, так и есть, но конкретно в линуксе как раз вопрос стоит ребром: либо то, либо то, потому что TrustedGRUB2 (GRUB с поддержкой TPM) не поддерживает UEFI (а SecureBoot без него невозможен). Учитывая, что MBR boot это просто легаси, я не вижу сейчас вообще рабочих решений для линукса, которые могли бы задействовать TPM. Кстати, прямо сейчас я дописываю пост на эту тему, думаю к ночи опубликую.


                    1. dartraiden
                      25.06.2019 20:23
                      +1

                      В мире Linux поддержка TPM в плачевном состоянии, согласен (низкий поклон Мэтью Гаррету за его вклад в поддержку SecureBoot). Что говорить, значительная часть пользователей до сих пор верит байкам о том, что SecureBoot — изобретение клятого микрософта, чтобы затруднить продвижение Linux на десктопах.

                      То, что SecureBoot — это их единственная защита от вредоносных OROM-ов, они предпочитают не задумываться.


  1. AEP
    23.06.2019 21:41

    В статье используется скрипт decrypt_derived. Он вызывает dmsetup table --target crypt --showkeys, но это работает только для LUKS1. С новым пакетом cryptsetup будет ошибка:

    device $1 uses the kernel keyring

    Обычный ключевой файл с правами 0400 надежнее.


    1. quality Автор
      23.06.2019 22:29

      Спасибо за уточнение, но в продакшене используется на данный момент Debian 9.x, cryptsetup 2.x доступен только с версии Debian Buster (10) в репозитории. Как только соберусь обновляться, сначала тестовый стенд обкатаю, и всё проверю, но думаю найдется способ использовать этот метод и я дополню статью. Обычный ключевой файл можно использовать только с /etc/crypttab, так как лежать он будет в root-разделе, но тогда теряется смысл данной статьи. Да и удалить можно случайно!


      1. AEP
        23.06.2019 22:36

        При обновлении ничего не сломается, проблема проявляется только при создании новых томов LUKS с помощью новых версий cryptsetup. В качестве временного решения можно добавить опцию --type luks1 к luksFormat.


  1. rantal
    23.06.2019 23:42
    +2

    Я в последнее время делаю примерно так:

    Подтягиваем пакеты и делаем шифрованный раздел — заготовку под LVM

    apt install -y cryptsetup-luks dropbear-initramfs net-tools keyutils
    cryptsetup luksFormat /dev/sda3
    cryptsetup luksOpen /dev/sda3 LVM
    


    Подготавливаемся к удаленной разблокировке по SSH:
    echo "IP=наш_ip-адрес::наш_шлюз:наша_маска::наше_имя_интерфейса:off" >> /etc/initramfs-tools/initramfs.conf
    sed -i "s/^#CRYPTSETUP=$/CRYPTSETUP=y/" /etc/cryptsetup-initramfs/conf-hook
    


    Переносим в dropbear SSH-ключ нашего хоста, чтобы при подключении для разблокировки ssh не ругался на то, что ключ изменился:
    /usr/lib/dropbear/dropbearconvert openssh dropbear /etc/ssh/ssh_host_rsa_key /etc/dropbear-initramfs/dropbear_rsa_host_key
    
    


    Готовим dropbear для удаленной разблокировки пользователем с нашим ssh-ключем:
    echo "no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command=\"/bin/cryptroot-unlock\" ssh-rsa НАШ_ПУБЛИЧНЫЙ_КЛЮЧ" >> /etc/dropbear-initramfs/authorized_keys
    


    Подготавливаем crypttab для созданного шифрованного раздела:
    blkid /dev/sda3
    echo LVM UUID=НАШ_UUID_ИЗ_ВЫВОДА_ПРЕДЫДУЩЕЙ КОМАНДЫ crypt_disks luks,discard,initramfs,keyscript=decrypt_keyctl >> /etc/crypttab
    


    Обновляем initramfs:
    update-initramfs -u -k all
    
    


    После этого хорошо бы проверить, что внутри нашего нового initramfs попала информация о нашем зашифрованном разделе. Я делаю примерно так:
    unmkinitramfs /boot/НАШ_INITRAMFS /tmp/initramfs
    

    и проверяю в debian 9 содержимое /tmp/initramfs/conf/conf.d/cryptroot, а в debian 10 — содержимое /tmp/initrams/cryptroot/crypttab

    Далее — по накатанной дороге:
    pvcreate /dev/mapper/LVM
    vgcreate vg0 /dev/mapper/LVM
    
    ... and so on ...
    

    и работаем с LVM, как нам удобно. В частности, root я обычно кладу туда же (boot оставляю нешифрованным на /dev/sda1).

    После всех этих операций получаем следующее:
    при перезагрузке на стадии initramfs загрузка приостанавливается, и поднимается dropbear, к которому можно подключиться по ssh от имени пользователя root по ключу, публичную часть которого мы задали на стадии настройки. При подключения по ssh после аутентификации мы получаем предложение ввести passphase, которую мы задали при формировании зашифрованного раздела. Так же passphase можно ввести в обычной консоли с клавиатуры. После ввода корректной passphase производится разблокировка диска, соединение ssh сбрасывается, dropbear останавливается и продолжается нормальная загрузка (при этом зашифрованный диск уже доступен, lvm с него подцепляется автоматически).

    Конечно, при желании, в crypttab можно запихнуть не один зашифрованный раздел, а несколько. Например, предположим, что у нас есть желание получить зашифрованный swap на разделе /dev/sda2:
    cryptsetup luksFormat /dev/sda2
    cryptsetup luksOpen /dev/sda2 swap
    echo swap /dev/sda3 crypt_disks luks,swap,discard,initramfs,keyscript=decrypt_keyctl >> /etc/crypttab
    update-initramfs -u -k all
    


    Если при этом задать тот же passphase, что и для первого зашифрованного раздела, то при загрузке его потребуется ввести один раз, т.к. скрипт decrypt_keyctl в initramfs кеширует введенное значение и пытается применить его ко всем дискам, которые требуется расшифровать. Если поставить разные passphase, то придется по очереди ввести их все.


    1. quality Автор
      24.06.2019 09:28

      Вы используете /etc/crypttab, представьте, что будет, когда туда записать более 50 дисков? Идея была в расшифровке дисков до «маунта» root-раздела, а не после.


      1. rantal
        24.06.2019 11:31

        Ещё раз, диски расшифровывается ДО монтирования root-раздела на стадии работы initramfs.


  1. Prototik
    24.06.2019 09:41

    Заметочка для тех, у кого ArchLinux — всё это уже есть из коробки, достаточно прописать нужные параметры ядра:


    ? cat /boot/cmdline           
    # LUKS (Lol Ur Keys Sick)
    rd.luks.name=bffa9547-b6a5-43e8-a199-4f92bb225c91=fenix
    rd.luks.options=timeout=0
    
    # Root fs (I heard kernel panics without it, I want happy kernel)
    root=/dev/mapper/fenix
    rootflags=subvol=@archlinux,x-systemd.device-timeout=0
    
    # We love systemd, right?
    init=/usr/lib/systemd/systemd
    
    # But hate it for a little verbosity
    quiet
    loglevel=3
    rd.systemd.show_status=auto
    rd.udev.log_priority=3

    Если нужен ssh на initrd фазе — пакет mkinitcpio-dropbear или mkinitcpio-tinyssh.


    Так же прошу заметить, что эти опции ядра для systemd initrd HOOKS=(base systemd autodetect modconf block keyboard sd-vconsole sd-encrypt fsck filesystems), для busybox initrd название опций другое