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

Reply via email to