Historia zaczęła się dawno, jeszcze przy wydaniu CentOS 7 (RHEL 7). Jeśli używałeś szyfrowania dysków w CentOS 6, nie było żadnych problemów z automatycznym odblokowywaniem dysków po podłączeniu pamięci USB z odpowiednimi kluczami. Jednak po wydaniu siódemki nagle wszystko przestało działać tak, jak przywykłeś. Można było znaleźć rozwiązanie w powrocie dracuta do sysvinit za pomocą prostej linii w konfiguracji: echo ‘omit_dracutmodules+=" systemd "’ > /etc/dracut.conf.d/luks-workaround.conf
Co natychmiast pozbawiało nas całej przyjemności systemd — szybkie i równoległe uruchamianie usług systemowych, co znacznie skracało czas uruchamiania systemu.
I tak to trwa:
Nie czekając na rozwiązanie, sam stworzyłem je dla siebie, a teraz dzielę się nim z publicznością, kto jest zainteresowany, niech czyta dalej.

Wprowadzenie
Systemd, kiedy tylko zacząłem pracować z CentOS 7, nie wzbudzał żadnych emocji, ponieważ poza niewielką zmianą w składni zarządzania usługami, na początku nie odczułem szczególnej różnicy. Z biegiem czasu systemd mi się spodobał, ale pierwsze wrażenie było nieco zepsute, ponieważ deweloperzy dracuta nie poświęcali zbyt wiele czasu na wsparcie procesu uruchamiania z systemd w połączeniu z szyfrowaniem dysków. W zasadzie działało, ale wpisywanie hasła do dysku przy każdym uruchomieniu serwera — nie jest najciekawszą rzeczą.
Wypróbowałem mnóstwo rekomendacji i studiując podręcznik, zrozumiałem, że w trybie systemd można skonfigurować USB, ale tylko przy ręcznym powiązaniu każdego dysku z kluczem na dysku USB, przy czym sam dysk USB można powiązać tylko po jego UUID, LABEL nie działał. Utrzymywanie tego u siebie nie było zbyt wygodne, dlatego ostatecznie zanurzyłem się w oczekiwanie i, czekając prawie 7 lat, zrozumiałem — nikt nie ma zamiaru rozwiązać problemu.
Problemy
Oczywiście, napisać swój plugin do dracut może prawie każdy, ale sprawić, by działał, to już nie takie proste. Okazało się, że ze względu na równoległą naturę uruchamiania systemd włączenie własnego kodu i zmiana przebiegu uruchamiania nie jest takie łatwe. Dokumentacja do dracuta nie wyjaśniała wszystkiego. Jednak w wyniku długotrwałych eksperymentów udało mi się rozwiązać problem.
Jak to działa
U podstaw leżą trzy jednostki:
- luks-auto-key.service — szuka nośników z kluczami dla LUKS
- luks-auto.target — pełni rolę zależności dla wbudowanych jednostek systemd-cryptsetup
- luks-auto-clean.service — usuwa tymczasowe pliki utworzone przez luks-auto-key.service
luks-auto-generator.sh to skrypt, który jest uruchamiany przez systemd i generuje jednostki na podstawie parametrów jądra. Podobne generatory tworzą jednostki fstab itd.
luks-auto-generator.sh
Dzięki drop-in.conf zmienia się zachowanie standardowego systemd-cryptsetup, dodając im zależność luks-auto.target.
luks-auto-key.service i luks-auto-key.sh
Ten jednostka uruchamia skrypt luks-auto-key.sh, który, opierając się na kluczach rd.luks.*, znajduje nośniki z kluczami i kopiuje je do tymczasowego katalogu do późniejszego użycia. Po zakończeniu procesu klucze z tymczasowego katalogu są usuwane przez luks-auto-clean.service.
Źródła:
/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@luksx2d${_UUID_ESC}.service.d"
cat > "${SYSTEMD_RUN}/systemd-cryptsetup@luksx2d${_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-byx2duuid-${_UUID_ESC}.device
After=dev-disk-byx2duuid-${_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 "Instalacja
mkdir -p \/usr\/lib\/dracut\/modules.d\/99luks-auto\/
# umieszczamy tutaj prawie wszystkie pliki
chmod +x \/usr\/lib\/dracut\/modules.d\/99luks-auto\/*.sh
# tworzymy plik \/etc\/dracut.conf.d\/luks-auto.conf
# I generujemy nowy initramfs
dracut -f
Podsumowanie
Dla wygody zachowałem zgodność z parametrami wiersza poleceń rdzenia zarówno dla trybu sysvinit, co ułatwia użycie w starszych instalacjach.
Źródło: habr.com
