Hi List! I spent sereval hours today figuring out how to do an useful RAID with full-disk encryption for headless servers that will use an usb stick to read the keyfile from and falling back to askpass to enter the pw from console if no stick is found and also managed to silence systemd. This post has the intention of being a reference post of the type "Im finally done after a day of doing stuff in a vm and hopefully this will save someone a few hours to get a similar setup set-up." Also included: a patch for cryptoroot to make it fall back to askpass, if no keyfile can be found on usb-sticks. There are many outdated and incomplete guides, mostly written for oldoldold-stable, which will make you do more than necessary. :P The fewer changes to a system, the better, IMO. So... I ran the default netinstall iso and partitioned the VM as follows: /dev/sda1: /boot /dev/sda2: RAID0: md0 /dev/md0: crypto_LUKS: md0_crypt (defaults + passphrase) /dev/mapper/md0_crypt: ext4: / Ill test a more useful raid level on the live setup, once the hardware is assembled, but I expect it to behave identically. After the installer finished, and rebooted the VM, I got to enter the passphrase for the first time, it booted fine. An encrypted root-fs on a raid seems to work out of the box by now - no need to fiddle with grub as some guides suggest! The next step was to get the passwordless usb-stick (/dev/sdb) auto-decryption working. (Again: it works nearly out of the boy by now, there are MANY outdated guides out there suggesting you to download some unnecessary scripts) It all boiled down to: # mkfs.ext4 -L keys /dev/sdb # mount LABEL=keys /mnt # echo -n "asdf" > /mnt/root.keys # cryptsetup luksAddKey /dev/md0 /mnt/root.key # umount /mnt Seting the following line in /etc/crypttab --- md0_crypt UUID=[...] /dev/disk/by-label/keys:/root.key:5 luks,initramfs,keyscript=/lib/cryptsetup/scripts/passdev,tries=2 --- # update-initramfs -tuck all # update-grub # reboot Now we are in a state that debian will boot automatically, unlocking the encrypted root filesystem on a raid without any user-interaction. Sweet? Somehow... systemd has a tendency to complain and slow down the boot process by 1:30 min because it fails to handle the new crypttab entry, but after the timeout, you get presented with a login. To silence systemd: # touch "/etc/systemd/system/systemd-cryptsetup@md0_crypt.service" # reboot This will override the generated systemd-cryptsetup services. After the reboot, we get booted instantly, without systemd complaining about anything. Now what happens, if you dont have your usb-stick connected to the machine when rebooting? Well... passdev will wait for 5 sec, then fail, retry, fail again, ... There doesnt seem to be any kind of fallback to console-pw-entry. This should not really matter, but what if for some reason the usb controller or usb-stick fails and renders the machine unbootable, because you dont have a replacement at hand? That would suck, wouldnt it? Therefore I extended/hacked the original /usr/share/initramfs-tools/scripts/local-top/cryptroot script a little bit. # cp /usr/share/initramfs-tools/scripts/local-top/cryptroot /etc/initramfs-tools/scripts/local-top/cryptroot *edit stuff* The patch is attached. The intention was to try to use the usb stick and if passdev times out, fall back to passphrase-entry in the console via askpass. I added the option "cryptaskpassfallback" that should have been settable via /etc/crypttab as another option, but for some reason it was not passed from /etc/crypttab and by that time, my motivation wasnt really that high anyomre to investigate it further, and i simply hardcoded the default to ="yes" instead of ="". The actual fallback feels a little hacky too and im not really happy about it, as Im not really sure, if I didnt create any unwanted side-effects, by calling setup_mapping() once more. It would be awesome, if some maintainer of the corresponding packages would consider including this functionality, maybe a little less hacky. :) Anyway. After applying the patch, all seems done. # update-initramfs -tuck all # reboot Will either insta-boot with the usb stick accessible, or ask for a passphrase, and systemd wont complain. Thats what I wanted for myself and it seems to work as expected... Just wanted to share this. Maybe this will help someone in the future. :) br, Jan -- I only read plaintext emails.
#!/bin/sh
PREREQ="cryptroot-prepare"
#
# Standard initramfs preamble
#
prereqs()
{
# Make sure that cryptroot is run last in local-top
for req in $(dirname $0)/*; do
script=${req##*/}
if [ $script != cryptroot ]; then
echo $script
fi
done
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
# source for log_*_msg() functions, see LP: #272301
. /scripts/functions
# define $askpass
askpass="/lib/cryptsetup/askpass"
#
# Helper functions
#
message()
{
if [ -x /bin/plymouth ] && plymouth --ping; then
plymouth message --text="$@"
else
echo "$@" >&2
fi
return 0
}
udev_settle()
{
# Wait for udev to be ready, see https://launchpad.net/bugs/85640
if command -v udevadm >/dev/null 2>&1; then
udevadm settle --timeout=30
elif command -v udevsettle >/dev/null 2>&1; then
udevsettle --timeout=30
fi
return 0
}
parse_options()
{
local cryptopts
cryptopts="$1"
if [ -z "$cryptopts" ]; then
return 1
fi
# Defaults
cryptcipher=aes-cbc-essiv:sha256
cryptsize=256
crypthash=ripemd160
crypttarget=cryptroot
cryptsource=""
cryptheader=""
cryptlvm=""
cryptkeyscript=""
cryptkey="" # This is only used as an argument to an eventual keyscript
cryptkeyslot=""
crypttries=3
crypttcrypt=""
cryptveracrypt=""
cryptrootdev=""
cryptdiscard=""
cryptaskpassfallback="yes"
CRYPTTAB_OPTIONS=""
local IFS=" ,"
for x in $cryptopts; do
case $x in
hash=*)
crypthash=${x#hash=}
;;
size=*)
cryptsize=${x#size=}
;;
cipher=*)
cryptcipher=${x#cipher=}
;;
target=*)
crypttarget=${x#target=}
export CRYPTTAB_NAME="$crypttarget"
;;
source=*)
cryptsource=${x#source=}
if [ ${cryptsource#UUID=} != $cryptsource ]; then
cryptsource="/dev/disk/by-uuid/${cryptsource#UUID=}"
elif [ ${cryptsource#LABEL=} != $cryptsource ]; then
cryptsource="/dev/disk/by-label/$(printf '%s' "${cryptsource#LABEL=}" | sed 's,/,\\x2f,g')"
fi
export CRYPTTAB_SOURCE="$cryptsource"
;;
header=*)
cryptheader=${x#header=}
if [ ! -e "$cryptheader" ] && [ -e "/conf/conf.d/cryptheader/$cryptheader" ]; then
cryptheader="/conf/conf.d/cryptheader/$cryptheader"
fi
export CRYPTTAB_HEADER="$cryptheader"
;;
lvm=*)
cryptlvm=${x#lvm=}
;;
keyscript=*)
cryptkeyscript=${x#keyscript=}
;;
key=*)
if [ "${x#key=}" != "none" ]; then
cryptkey=${x#key=}
fi
export CRYPTTAB_KEY="$cryptkey"
;;
keyslot=*)
cryptkeyslot=${x#keyslot=}
;;
tries=*)
crypttries="${x#tries=}"
case "$crypttries" in
*[![:digit:].]*)
crypttries=3
;;
esac
;;
tcrypt)
crypttcrypt="yes"
;;
veracrypt)
cryptveracrypt="--veracrypt"
;;
rootdev)
cryptrootdev="yes"
;;
discard)
cryptdiscard="yes"
;;
askpassfallback)
cryptaskpassfallback="yes"
;;
esac
PARAM="${x%=*}"
if [ "$PARAM" = "$x" ]; then
VALUE="yes"
else
VALUE="${x#*=}"
fi
CRYPTTAB_OPTIONS="$CRYPTTAB_OPTIONS $PARAM"
eval export CRYPTTAB_OPTION_$PARAM="\"$VALUE\""
done
export CRYPTTAB_OPTIONS
if [ -z "$cryptsource" ]; then
message "cryptsetup ($crypttarget): source parameter missing"
return 1
fi
return 0
}
activate_vg()
{
# Sanity checks
if [ ! -x /sbin/lvm ]; then
message "cryptsetup ($crypttarget): lvm is not available"
return 1
fi
# Detect and activate available volume groups
/sbin/lvm vgscan
/sbin/lvm vgchange -a y --sysinit
return $?
}
setup_mapping()
{
local opts count cryptopen cryptremove NEWROOT
opts="$1"
if [ -z "$opts" ]; then
return 0
fi
parse_options "$opts" || return 1
# disable cryptkeyscript - fall back to askpass.
if [ -n "$do_fallback" ]; then
cryptkeyscript=""
fi
if [ -z "$cryptkeyscript" ]; then
if [ ${cryptsource#/dev/disk/by-uuid/} != $cryptsource ]; then
# UUIDs are not very helpful
diskname="$crypttarget"
else
diskname="$cryptsource ($crypttarget)"
fi
cryptkeyscript=$askpass
cryptkey="Please unlock disk $diskname: "
elif ! type "$cryptkeyscript" >/dev/null; then
message "cryptsetup ($crypttarget): error - script \"$cryptkeyscript\" missing"
return 1
fi
if [ "$cryptkeyscript" = "cat" ] && [ "${cryptkey#/root/}" != "$cryptkey" ]; then
# skip the mapping if the root FS is not mounted yet
sed -rn 's/^\s*[^#]\S*\s+(\S+)\s.*/\1/p' /proc/mounts | grep -Fxq "$rootmnt" || return 1
# substitute the "/root" prefix by the real root FS mountpoint otherwise
cryptkey="${rootmnt}/${cryptkey#/root/}"
fi
if [ -n "$cryptheader" ] && ! type "$cryptheader" >/dev/null; then
message "cryptsetup ($crypttarget): error - LUKS header \"$cryptheader\" missing"
return 1
fi
# The same target can be specified multiple times
# e.g. root and resume lvs-on-lvm-on-crypto
if [ -e "/dev/mapper/$crypttarget" ]; then
return 0
fi
modprobe -q dm_crypt
# Make sure the cryptsource device is available
if [ ! -e $cryptsource ]; then
activate_vg
fi
# If the encrypted source device hasn't shown up yet, give it a
# little while to deal with removable devices
# the following lines below have been taken from
# /usr/share/initramfs-tools/scripts/local, as suggested per
# https://launchpad.net/bugs/164044
if [ ! -e "$cryptsource" ]; then
log_begin_msg "Waiting for encrypted source device..."
# Default delay is 180s
if [ -z "${ROOTDELAY}" ]; then
slumber=180
else
slumber=${ROOTDELAY}
fi
slumber=$(( ${slumber} * 10 ))
while [ ! -e "$cryptsource" ]; do
# retry for LVM devices every 10 seconds
if [ ${slumber} -eq $(( ${slumber}/100*100 )) ]; then
activate_vg
fi
/bin/sleep 0.1
slumber=$(( ${slumber} - 1 ))
[ ${slumber} -gt 0 ] || break
done
if [ ${slumber} -gt 0 ]; then
log_end_msg 0
else
log_end_msg 1 || true
fi
fi
udev_settle
# We've given up, but we'll let the user fix matters if they can
if [ ! -e "${cryptsource}" ]; then
echo " ALERT! ${cryptsource} does not exist."
echo " Check cryptopts=source= bootarg: cat /proc/cmdline"
echo " or missing modules, devices: cat /proc/modules; ls /dev"
panic -r "Dropping to a shell. Will skip ${cryptsource} if you can't fix."
fi
if [ ! -e "${cryptsource}" ]; then
return 1
fi
# Prepare commands
cryptopen="/sbin/cryptsetup -T 1"
if [ "$cryptdiscard" = "yes" ]; then
cryptopen="$cryptopen --allow-discards"
fi
if [ -n "$cryptheader" ]; then
cryptopen="$cryptopen --header=$cryptheader"
fi
if [ -n "$cryptkeyslot" ]; then
cryptopen="$cryptopen --key-slot=$cryptkeyslot"
fi
if /sbin/cryptsetup isLuks ${cryptheader:-$cryptsource} >/dev/null 2>&1; then
cryptopen="$cryptopen open --type luks $cryptsource $crypttarget --key-file=-"
elif [ "$crypttcrypt" = "yes" ]; then
cryptopen="$cryptopen open --type tcrypt $cryptveracrypt $cryptsource $crypttarget"
else
cryptopen="$cryptopen -c $cryptcipher -s $cryptsize -h $crypthash open --type plain $cryptsource $crypttarget --key-file=-"
fi
cryptremove="/sbin/cryptsetup remove $crypttarget"
NEWROOT="/dev/mapper/$crypttarget"
# Try to get a satisfactory password $crypttries times
count=0
while [ $crypttries -le 0 ] || [ $count -lt $crypttries ]; do
export CRYPTTAB_TRIED="$count"
count=$(( $count + 1 ))
if [ ! -e "$NEWROOT" ]; then
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
$cryptkeyscript "$cryptkey" | $cryptopen; then
message "cryptsetup ($crypttarget): cryptsetup failed, bad password or options?"
# if not askpass, fall back to askpass on fail.
if [ -z "$cryptaskpassfallback" ]; then
continue
elif [ "$cryptkeyscript" = "$askpass" ]; then
continue
else
export do_fallback="$cryptaskpassfallback"
setup_mapping "$1"
return
fi
fi
fi
if [ ! -e "$NEWROOT" ]; then
message "cryptsetup ($crypttarget): unknown error setting up device mapping"
return 1
fi
#FSTYPE=''
#eval $(fstype < "$NEWROOT")
FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")"
# See if we need to setup lvm on the crypto device
#if [ "$FSTYPE" = "lvm" ] || [ "$FSTYPE" = "lvm2" ]; then
if [ "$FSTYPE" = "LVM_member" ] || [ "$FSTYPE" = "LVM2_member" ]; then
if [ -z "$cryptlvm" ]; then
message "cryptsetup ($crypttarget): lvm fs found but no lvm configured"
return 1
elif ! activate_vg; then
# disable error message, LP: #151532
#message "cryptsetup ($crypttarget): failed to setup lvm device"
return 1
fi
# Apparently ROOT is already set in /conf/param.conf for
# flashed kernels at least. See bugreport #759720.
if [ -f /conf/param.conf ] && grep -q "^ROOT=" /conf/param.conf; then
NEWROOT=$(sed -n 's/^ROOT=//p' /conf/param.conf)
else
NEWROOT=${cmdline_root:-/dev/mapper/$cryptlvm}
if [ "$cryptrootdev" = "yes" ]; then
# required for lilo to find the root device
echo "ROOT=$NEWROOT" >>/conf/param.conf
fi
fi
#eval $(fstype < "$NEWROOT")
FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")"
fi
#if [ -z "$FSTYPE" ] || [ "$FSTYPE" = "unknown" ]; then
if [ -z "$FSTYPE" ]; then
message "cryptsetup ($crypttarget): unknown fstype, bad password or options?"
udev_settle
$cryptremove
continue
fi
# decrease $count by 1, apparently last try was successful.
count=$(( $count - 1 ))
message "cryptsetup ($crypttarget): set up successfully"
break
done
failsleep=60 # make configurable later?
if [ "$cryptrootdev" = "yes" ] && [ $crypttries -gt 0 ] && [ $count -ge $crypttries ]; then
message "cryptsetup ($crypttarget): maximum number of tries exceeded"
message "cryptsetup: going to sleep for $failsleep seconds..."
sleep $failsleep
exit 1
fi
udev_settle
return 0
}
#
# Begin real processing
#
# Do we have any kernel boot arguments?
cmdline_cryptopts=''
unset cmdline_root
for opt in $(cat /proc/cmdline); do
case $opt in
cryptopts=*)
opt="${opt#cryptopts=}"
if [ -n "$opt" ]; then
if [ -n "$cmdline_cryptopts" ]; then
cmdline_cryptopts="$cmdline_cryptopts $opt"
else
cmdline_cryptopts="$opt"
fi
fi
;;
root=*)
opt="${opt#root=}"
case $opt in
/*) # Absolute path given. Not lilo major/minor number.
cmdline_root=$opt
;;
*) # lilo major/minor number (See #398957). Ignore
esac
;;
esac
done
if [ -n "$cmdline_cryptopts" ]; then
# Call setup_mapping separately for each possible cryptopts= setting
for cryptopt in $cmdline_cryptopts; do
setup_mapping "$cryptopt"
done
exit 0
fi
# Do we have any settings from the /conf/conf.d/cryptroot file?
if [ -r /conf/conf.d/cryptroot ]; then
while read mapping <&3; do
setup_mapping "$mapping" 3<&-
done 3< /conf/conf.d/cryptroot
fi
exit 0
--- /usr/share/initramfs-tools/scripts/local-top/cryptroot 2017-05-09 13:50:59.000000000 +0200
+++ /mnt/cryptroot 2017-12-16 02:26:34.401812974 +0100
@@ -26,6 +26,9 @@
# source for log_*_msg() functions, see LP: #272301
. /scripts/functions
+# define $askpass
+askpass="/lib/cryptsetup/askpass"
+
#
# Helper functions
#
@@ -75,6 +78,7 @@
cryptveracrypt=""
cryptrootdev=""
cryptdiscard=""
+ cryptaskpassfallback="yes"
CRYPTTAB_OPTIONS=""
local IFS=" ,"
@@ -144,6 +148,9 @@
discard)
cryptdiscard="yes"
;;
+ askpassfallback)
+ cryptaskpassfallback="yes"
+ ;;
esac
PARAM="${x%=*}"
if [ "$PARAM" = "$x" ]; then
@@ -188,6 +195,11 @@
parse_options "$opts" || return 1
+ # disable cryptkeyscript - fall back to askpass.
+ if [ -n "$do_fallback" ]; then
+ cryptkeyscript=""
+ fi
+
if [ -z "$cryptkeyscript" ]; then
if [ ${cryptsource#/dev/disk/by-uuid/} != $cryptsource ]; then
# UUIDs are not very helpful
@@ -195,7 +207,7 @@
else
diskname="$cryptsource ($crypttarget)"
fi
- cryptkeyscript="/lib/cryptsetup/askpass"
+ cryptkeyscript=$askpass
cryptkey="Please unlock disk $diskname: "
elif ! type "$cryptkeyscript" >/dev/null; then
message "cryptsetup ($crypttarget): error - script \"$cryptkeyscript\" missing"
@@ -308,7 +320,17 @@
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
$cryptkeyscript "$cryptkey" | $cryptopen; then
message "cryptsetup ($crypttarget): cryptsetup failed, bad password or options?"
- continue
+
+ # if not askpass, fall back to askpass on fail.
+ if [ -z "$cryptaskpassfallback" ]; then
+ continue
+ elif [ "$cryptkeyscript" = "$askpass" ]; then
+ continue
+ else
+ export do_fallback="$cryptaskpassfallback"
+ setup_mapping "$1"
+ return
+ fi
fi
fi
Attachment:
signature.asc
Description: OpenPGP digital signature