dracut + systemd + LUKS + usbflash = авторазблокировка

    История началась давно, еще при выходе Centos 7 (RHEL 7). Если вы использовали шифрование на дисках с Centos 6, то не было никаких проблем с автоматической разблокировкой дисков при подключении USB флешки с нужными ключами. Однако, при выходе 7-ки внезапно все не работало как вы привыкли. Тогда можно было найти решение в возврате dracut к sysvinit с помощью незамысловатой строчки в конфиге: echo 'omit_dracutmodules+=" systemd "' > /etc/dracut.conf.d/luks-workaround.conf

    Что сразу лишало нас всей прелести systemd — быстрый и параллельный запуск системных служб, что значительно сокращало время запуска системы.

    Воз и ныне там: 905683

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



    Введение


    Systemd, когда я только начал работать с Centos 7 не вызвал никаких эмоций, так как кроме незначительного изменения в синтаксисе управления службами, я не почувствовал вначале особой разницы. Впоследствии systemd мне понравился, но первое впечатление было немного подпорчено, так как разработчики dracut не особо уделяли время на поддержку процесса загрузки с помощью systemd совместно с шифрованием дисков. В целом оно работало, но вводить пароль от диска при каждом зауске сервера — не самое интересное занятие.

    Опробовав кучу рекомендаций и учитавшись мануалом я понял, что в режиме systemd возможна конфигурация с USB, но только при ручной ассоциации каждого диска с ключем на USB диске, причем сам USB диск можно связать только по его UUID, LABEL не работал. Поддерживать такое у себя было не очень удобно, поэтому в итоге я погрузился в ожидание и, прождав без малого 7 лет, я понял — никто не собирается решать проблему.

    Проблемы


    Конечно, написать свой плагин к dracut может почти каждый, но сделать его рабочим уже не так просто. Оказалось, что за счет параллельной природы запуска systemd сделать включение своего кода и поменять ход загрузки не так и легко. Документация к dracut поясняла далеко не все. Однако вследствии длительных эксперементов я смог решить задачу.

    Как оно работает


    В основе лежит три юнита:

    1. luks-auto-key.service — ищет накопители с ключами для LUKS
    2. luks-auto.target — выполняет роль зависимости для встроенных юнитов systemd-cryptsetup
    3. luks-auto-clean.service — очищает времнные файлы, созданные luks-auto-key.service

    И luks-auto-generator.sh — скрипт, который запускается systemd и осуществляет генерацию юнитов на основе параметров ядра. Аналогичные генераторы создают юниты fstab и т.п.

    luks-auto-generator.sh


    С помощью drop-in.conf меняется поведение стандартных systemd-cryptsetup, добавляя им в зависимость luks-auto.target.

    luks-auto-key.service и luks-auto-key.sh


    Этот юнит запускает скрипт luks-auto-key.sh, который, основываясь на ключах rd.luks.*, находит носители с ключами и копирует их во временный каталог для дальнейшего использования. После завершения процесса, ключи из временного каталога удаляет luks-auto-clean.service.

    Исходники:


    /usr/lib/dracut/modules.d/99luks-auto/module-setup.sh
    #!/bin/bash
    
    check () {
            if ! dracut_module_included "systemd"; then
                    "luks-auto needs systemd in the initramfs"
                    return 1
            fi
            return 255
    }
    
    depends () {
            echo "systemd"
            return 0
    }
    
    install () {
            inst "$systemdutildir/systemd-cryptsetup"
    		inst_script "$moddir/luks-auto-generator.sh" "$systemdutildir/system-generators/luks-auto-generator.sh"
    		inst_script "$moddir/luks-auto-key.sh" "/etc/systemd/system/luks-auto-key.sh"
    		inst_script "$moddir/luks-auto.sh" "/etc/systemd/system/luks-auto.sh"
    		inst "$moddir/luks-auto.target" "${systemdsystemunitdir}/luks-auto.target"
    		inst "$moddir/luks-auto-key.service" "${systemdsystemunitdir}/luks-auto-key.service"
    		inst "$moddir/luks-auto-clean.service" "${systemdsystemunitdir}/luks-auto-clean.service"
    		ln_r "${systemdsystemunitdir}/luks-auto.target" "${systemdsystemunitdir}/initrd.target.wants/luks-auto.target"
    		ln_r "${systemdsystemunitdir}/luks-auto-key.service" "${systemdsystemunitdir}/initrd.target.wants/luks-auto-key.service"
    		ln_r "${systemdsystemunitdir}/luks-auto-clean.service" "${systemdsystemunitdir}/initrd.target.wants/luks-auto-clean.service"
    }
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto-generator.sh
    
    #!/bin/sh
    # -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
    # ex: ts=8 sw=4 sts=4 et filetype=sh
    
    . /lib/dracut-lib.sh
    
    SYSTEMD_RUN='/run/systemd/system'
    CRYPTSETUP='/usr/lib/systemd/systemd-cryptsetup'
    TOUT=$(getargs rd.luks.key.tout)
    if [ ! -z "$TOUT" ]; then
    	mkdir -p "${SYSTEMD_RUN}/luks-auto-key.service.d"
    	cat > "${SYSTEMD_RUN}/luks-auto-key.service.d/drop-in.conf"  <<EOF
    [Service]
    Type=oneshot
    ExecStartPre=/usr/bin/sleep $TOUT
    
    EOF
    fi
    mkdir -p "$SYSTEMD_RUN/luks-auto.target.wants"
    for argv in $(getargs rd.luks.uuid -d rd_LUKS_UUID); do
    	_UUID=${argv#luks-}
    	_UUID_ESC=$(systemd-escape -p $_UUID)
    	mkdir -p "${SYSTEMD_RUN}/systemd-cryptsetup@luks\x2d${_UUID_ESC}.service.d"
    	cat > "${SYSTEMD_RUN}/systemd-cryptsetup@luks\x2d${_UUID_ESC}.service.d/drop-in.conf"  <<EOF
    [Unit]
    After=luks-auto.target
    ConditionPathExists=!/dev/mapper/luks-${_UUID}
    
    EOF
    	cat > "${SYSTEMD_RUN}/luks-auto@${_UUID_ESC}.service"  <<EOF
    [Unit]
    Description=luks-auto Cryptography Setup for %I
    DefaultDependencies=no
    Conflicts=umount.target
    IgnoreOnIsolate=true
    Before=luks-auto.target
    BindsTo=dev-disk-by\x2duuid-${_UUID_ESC}.device
    After=dev-disk-by\x2duuid-${_UUID_ESC}.device luks-auto-key.service
    Before=umount.target
    
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    TimeoutSec=0
    ExecStart=/etc/systemd/system/luks-auto.sh ${_UUID}
    ExecStop=$CRYPTSETUP detach 'luks-${_UUID}'
    Environment=DRACUT_SYSTEMD=1
    StandardInput=null
    StandardOutput=syslog
    StandardError=syslog+console
    
    EOF
    ln -fs ${SYSTEMD_RUN}/luks-auto@${_UUID_ESC}.service $SYSTEMD_RUN/luks-auto.target.wants/luks-auto@${_UUID_ESC}.service
    done
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto-key.service
    
    [Unit]
    Description=LUKS AUTO key searcher
    After=cryptsetup-pre.target
    Before=luks-auto.target
    DefaultDependencies=no
    
    [Service]
    Environment=DRACUT_SYSTEMD=1
    Type=oneshot
    ExecStartPre=/usr/bin/sleep 1
    ExecStart=/etc/systemd/system/luks-auto-key.sh
    RemainAfterExit=true
    StandardInput=null
    StandardOutput=syslog
    StandardError=syslog+console
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto-key.sh
    
    #!/bin/sh
    # -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
    # ex: ts=8 sw=4 sts=4 et filetype=sh
    export DRACUT_SYSTEMD=1
    
    . /lib/dracut-lib.sh
    MNT_B="/tmp/luks-auto"
    ARG=$(getargs rd.luks.key)
    IFS=$':' _t=(${ARG})
    KEY=${_t[0]}
    F_FIELD=''
    F_VALUE=''
    if [ ! -z $KEY ] && [ ! -z ${_t[1]} ];then
    	IFS=$'=' _t=(${_t[1]})
    	F_FIELD=${_t[0]}
    	F_VALUE=${_t[1]}
    	F_VALUE="${F_VALUE%\"}"
    	F_VALUE="${F_VALUE#\"}"
    fi
    mkdir -p $MNT_B
    
    finding_luks_keys(){
    	local _DEVNAME=''
    	local _UUID=''
    	local _TYPE=''
    	local _LABEL=''
    	local _MNT=''
    	local _KEY="$1"
    	local _F_FIELD="$2"
    	local _F_VALUE="$3"
    	local _RET=0	
    	blkid -s TYPE -s UUID -s LABEL -u filesystem | grep -v -E -e "TYPE=\".*_member\"" -e "TYPE=\"crypto_.*\"" -e "TYPE=\"swap\"" | while IFS=$'' read -r _line; do
    		IFS=$':' _t=($_line);
    		_DEVNAME=${_t[0]}
    		_UUID=''
    		_TYPE=''
    		_LABEL=''
    		_MNT=''
    		IFS=$' ' _t=(${_t[1]});
    		for _a in "${_t[@]}"; do
    			IFS=$'=' _v=(${_a});
    			temp="${_v[1]%\"}"
    			temp="${temp#\"}"
    			case ${_v[0]} in
    				'UUID')
    					_UUID=$temp
    				;;
    				'TYPE')
    					_TYPE=$temp
    				;;
    				'LABEL')
    					_LABEL=$temp
    				;;
    			esac
    		done
    		if [ ! -z "$_F_FIELD" ];then
    			case $_F_FIELD in
    				'UUID')
    					[ ! -z "$_F_VALUE" ] && [ "$_UUID" != "$_F_VALUE" ] && continue
    				;;
    				'LABEL')
    					[ ! -z "$_F_VALUE" ] && [ "$_LABEL" != "$_F_VALUE" ] && continue
    				;;
    				*)
    					[ "$_DEVNAME" != "$_F_FIELD" ] && continue
    				;;
    			esac
    		fi
    		_MNT=$(findmnt -n -o TARGET $_DEVNAME)
    		if [ -z "$_MNT" ]; then
    			_MNT=${MNT_B}/KEY-${_UUID}
    			mkdir -p "$_MNT" && mount -o ro "$_DEVNAME" "$_MNT"
    			_RET=$?
    		else
    			_RET=0
    		fi
    		if [ "${_RET}" -eq 0 ] && [ -f "${_MNT}/${_KEY}" ]; then
    			cp "${_MNT}/${_KEY}" "$MNT_B/${_UUID}.key"
    			info "Found ${_MNT}/${_KEY} on ${_UUID}"
    		fi
    		if [[ "${_MNT}" =~ "${MNT_B}" ]]; then
    			umount "$_MNT" && rm -rfd --one-file-system "$_MNT"						
    		fi
    	done
    	return 0
    }
    finding_luks_keys $KEY $F_FIELD $F_VALUE
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto.target
    
    [Unit]
    Description=LUKS AUTO target
    After=systemd-readahead-collect.service systemd-readahead-replay.service
    After=cryptsetup-pre.target luks-auto-key.service
    Before=cryptsetup.target
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto.sh
    
    #!/bin/sh
    # -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
    # ex: ts=8 sw=4 sts=4 et filetype=sh
    export DRACUT_SYSTEMD=1
    . /lib/dracut-lib.sh
    
    MNT_B="/tmp/luks-auto"
    CRYPTSETUP='/usr/lib/systemd/systemd-cryptsetup'
    
    for i in $(ls -p $MNT_B | grep -v /);do
    	info "Trying $i on $1..."
    	$CRYPTSETUP attach "luks-$1" "/dev/disk/by-uuid/$1" $MNT_B/$i 'tries=1'
    	if [ "$?" -eq "0" ]; then
    		info "Found $i for $1"
    		exit 0
    	fi
    done
    warn "No key found for $1.  Fallback to passphrase mode."
    


    /usr/lib/dracut/modules.d/99luks-auto/luks-auto-clean.service
    [Unit]
    Description=LUKS AUTO key cleaner
    After=cryptsetup.target
    DefaultDependencies=no
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/rm -rfd --one-file-system /tmp/luks-auto
    


    /etc/dracut.conf.d/luks-auto.conf
    add_dracutmodules+=" luks-auto "


    Установка
    
    mkdir -p /usr/lib/dracut/modules.d/99luks-auto/
    # размещаем тут почти все файлы
    chmod +x /usr/lib/dracut/modules.d/99luks-auto/*.sh
    # создаем файл /etc/dracut.conf.d/luks-auto.conf
    # И генерируем новый initramfs
    dracut -f
    


    Заключение


    Для удобства я сохранил совместимость с параметрами командной строки ядра как для режима sysvinit, что облегчает использование в старых инсталляция.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 2

      0
      Конечно, написать свой плагин к dracut может почти каждый, но сделать его рабочим, уже не так просто.
      Спасибо за статью, будем разбираться
        0
        вводить пароль от диска при каждом зауске сервера — не самое интересное занятие

        Если задача именно в этом — автоматическая разблокировка зашифрованного диска при запуске системы — то ещё проще использовать TPM2. В ubuntu 20.04 это делается примерно так (только надо ещё соответствующие пакеты поставить, минимально необходимый список не подскажу, но достаточный примерно такой: clevis clevis-luks clevis-tpm2 clevis-initramfs clevis-systemd):
        $ sudo clevis luks bind -d /dev/nvme0n1p3 tpm2 '{"pcr_bank": "sha256", "pcr_ids": "0,2,3,7"}'

        +secure boot, +автоподписывание собираемых DKMS модулей, и вот уже более-менее защищенная система получилась (если её не включать, конечно). Отчуждаемый носитель по определению надежнее, но тут уже вопрос рисков, от которых защищаемся.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое