История началась давно, еще при выходе Centos 7 (RHEL 7). Если вы использовали шифрование на дисках с Centos 6, то не было никаких проблем с автоматической разблокировкой дисков при подключении USB флешки с нужными ключами. Однако, при выходе 7-ки внезапно все не работало как вы привыкли. Тогда можно было найти решение в возврате dracut к sysvinit с помощью незамысловатой строчки в конфиге: echo 'omit_dracutmodules+=" systemd "' > /etc/dracut.conf.d/luks-workaround.conf
Что сразу лишало нас всей прелести systemd — быстрый и параллельный запуск системных служб, что значительно сокращало время запуска системы.
Воз и ныне там: 905683
Не дождавшись решения, я сделал его для себя сам, а теперь делюсь и с общественностью, кому интересно, читайте дальше.
![](https://habrastorage.org/r/w1560/webt/cr/tt/ao/crttaomzbycjgrqscdpfeoejnuw.png)
Systemd, когда я только начал работать с Centos 7 не вызвал никаких эмоций, так как кроме незначительного изменения в синтаксисе управления службами, я не почувствовал вначале особой разницы. Впоследствии systemd мне понравился, но первое впечатление было немного подпорчено, так как разработчики dracut не особо уделяли время на поддержку процесса загрузки с помощью systemd совместно с шифрованием дисков. В целом оно работало, но вводить пароль от диска при каждом зауске сервера — не самое интересное занятие.
Опробовав кучу рекомендаций и учитавшись мануалом я понял, что в режиме systemd возможна конфигурация с USB, но только при ручной ассоциации каждого диска с ключем на USB диске, причем сам USB диск можно связать только по его UUID, LABEL не работал. Поддерживать такое у себя было не очень удобно, поэтому в итоге я погрузился в ожидание и, прождав без малого 7 лет, я понял — никто не собирается решать проблему.
Конечно, написать свой плагин к dracut может почти каждый, но сделать его рабочим уже не так просто. Оказалось, что за счет параллельной природы запуска systemd сделать включение своего кода и поменять ход загрузки не так и легко. Документация к dracut поясняла далеко не все. Однако вследствии длительных эксперементов я смог решить задачу.
В основе лежит три юнита:
И luks-auto-generator.sh — скрипт, который запускается systemd и осуществляет генерацию юнитов на основе параметров ядра. Аналогичные генераторы создают юниты fstab и т.п.
С помощью drop-in.conf меняется поведение стандартных systemd-cryptsetup, добавляя им в зависимость luks-auto.target.
Этот юнит запускает скрипт luks-auto-key.sh, который, основываясь на ключах rd.luks.*, находит носители с ключами и копирует их во временный каталог для дальнейшего использования. После завершения процесса, ключи из временного каталога удаляет luks-auto-clean.service.
Github: MagicGTS/dracut-luks-autounlock
Для удобства я сохранил совместимость с параметрами командной строки ядра как для режима sysvinit, что облегчает использование в старых инсталляция.
Что сразу лишало нас всей прелести systemd — быстрый и параллельный запуск системных служб, что значительно сокращало время запуска системы.
Воз и ныне там: 905683
Не дождавшись решения, я сделал его для себя сам, а теперь делюсь и с общественностью, кому интересно, читайте дальше.
![](https://habrastorage.org/webt/cr/tt/ao/crttaomzbycjgrqscdpfeoejnuw.png)
Введение
Systemd, когда я только начал работать с Centos 7 не вызвал никаких эмоций, так как кроме незначительного изменения в синтаксисе управления службами, я не почувствовал вначале особой разницы. Впоследствии systemd мне понравился, но первое впечатление было немного подпорчено, так как разработчики dracut не особо уделяли время на поддержку процесса загрузки с помощью systemd совместно с шифрованием дисков. В целом оно работало, но вводить пароль от диска при каждом зауске сервера — не самое интересное занятие.
Опробовав кучу рекомендаций и учитавшись мануалом я понял, что в режиме systemd возможна конфигурация с USB, но только при ручной ассоциации каждого диска с ключем на USB диске, причем сам USB диск можно связать только по его UUID, LABEL не работал. Поддерживать такое у себя было не очень удобно, поэтому в итоге я погрузился в ожидание и, прождав без малого 7 лет, я понял — никто не собирается решать проблему.
Проблемы
Конечно, написать свой плагин к dracut может почти каждый, но сделать его рабочим уже не так просто. Оказалось, что за счет параллельной природы запуска systemd сделать включение своего кода и поменять ход загрузки не так и легко. Документация к dracut поясняла далеко не все. Однако вследствии длительных эксперементов я смог решить задачу.
Как оно работает
В основе лежит три юнита:
- luks-auto-key.service — ищет накопители с ключами для LUKS
- luks-auto.target — выполняет роль зависимости для встроенных юнитов systemd-cryptsetup
- 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.
Исходники:
Github: MagicGTS/dracut-luks-autounlock
/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, что облегчает использование в старых инсталляция.