Web lists-archives.com

[stretch] unlock with keyfile LVM encrypted rootfs -- hung at boot




Hello everyone!

I'm currently trying to find a method to decrypt my rootfs at boot time with a keyfile on debian stretch.

I've successfully implemented a method ([1] and [2]) which uses a custom script to read the first 2048bit
from the usbkey memory to decrypt the disk, but I actually need to store the keyfile on a filesystem.

Someone [3] told me about an old post on debian-user mailing list: https://lists.debian.org/debian-user/2017/12/msg00523.html
That's exactly what I need. I don't mind patching cryptroot script (appended [4]) to do it, so I decided to test it.
I've create a VM (kvm/qemu), partitioned the disks this way (a classic crypted debian LVM partitioning):


   root@debian:~ $ lsblk
   NAME                    MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
   sda                       8:0    0   1.2G  0 disk 
   vdb                       8:16   0  20.8G  0 disk
   ├─vdb1                    8:17   0   243M  0 part  /boot
   ├─vdb2                    8:18   0     1K  0 part 
   └─vdb5                    8:21   0  20.5G  0 part 
        └─vdb5_crypt         254:0    0 20.5G  0 crypt
    ├─debian--vg-root   254:1    0   18G  0 lvm   /
         └─debian--vg-swap_1 254:2    0  2.3G  0 lvm   [SWAP]


Configured as in the mailing list tutorial, updated initramfs, updated grub, rebooted.
I've followed the tutorial step by step, but still at boot time the system hung:


    Reading all physical volumes. This may take a while...
   WARNING: Failed to connect to lvmetad. Falling back to device scanning.
   WARNING: Failed to connect to lvmetad. Falling back to device scanning.
   Reading all physical volumes. This may take a while...
   WARNING: Failed to connect to lvmetad. Falling back to device scanning.
   ...
   ALERT! /dev/vdb5 does not exist.
                Check cryptopts=source bootarg: cat /proc/cmdline
                or missing modules. devices: cat /proc/modules: ls /dev
   -r Dropping to a shell. Will skip /dev/vdb5 if you can't fix.
   Busybox v1.22.1 (Debian 1:1.22.0-19+b3) built-in shell (ash)
   Enter 'help' fro a list of built-int commands.
   (initramfs)
   (initramfs) _


It seems unable to find the disk to decrypt (/dev/vdb5).
I was so sad, I've thought I've found the right way...
So I digged more and suddenly I've thought maybe it could be my LVM partition scheme.
With no real expectations I've created another VM, with the following partition scheme:


NAME      MAJ:MIN      RM      SIZE     RO     TYPE     MOUNTPOINT
sr0                     11:0           1    1024M      0        rom
vda                  254:0           0         20G     0         disk
|_vda1             254:0           0      953M      0        part       /boot
|_vda2             254:0           0           1K      0        part
|_vda5             254:0           0         17G      0       part
  |_vda5_crypt   254:0           0         17G      0       crypt       /
|_vda6             254:0           0         2G        0       part
   |_vda6_crypt   254:0           0         2G        0      crypt        [SWAP]


Configured, updated, rebooted.
And it works!

So I'm a bit stuck here and I've decided to write to this mailing list hoping someone may illuminate me,
why does it hung with LVM partitioning?
Could there it be some tricks to make it work?

Now to my point of view I think it could be a missing or erroneous cmdline parameter, or/and the cryptroot script in the tutorial (appended [4]) maybe is causing this. But I don't know yet how to fix this.

The cmdline I used with LVM partitioning is:
    BOOT_IMAGE=/vmlinuz-4.9.0-7-amd64  root=/dev/mapper/debian--vg-root ro cryptopts=target=vda5_crypt,source=/dev/disk/by-uuid/b59bb85d-abec-4562-90c3-1f3d0ab34036

The cmdline used without LVM:
     BOOT_IMAGE=/vmlinuz-4.9.0-7-amd64  root=UUID=b59bb85d-abec-4562-90c3-1f3d0ab34036 ro quiet

I also tried to decrypt LVM partitioned device from initramfs panic shell, but I succeeded only on an actual machine, on a virtual machine the devices didn't appear (no /dev/vda).

A partitioning scheme without LVM may be ok to my usecase, but I would like to understand why it does not work and if it can be fixed.
Thank you for your time,

Lorenzo

REF:
[1]  https://www.oxygenimpaired.com/debian-lenny-luks-encrypted-root-hidden-usb-keyfile
[2]  https://www.oxygenimpaired.com/ubuntu-with-grub2-luks-encrypted-lvm-root-hidden-usb-keyfile
[3]  https://unix.stackexchange.com/questions/471854/decrypt-root-device-at-boot-with-keyfile-on-usb-debian-stretch
[4]  cryptroot script:


#!/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

Attachment: signature.asc
Description: OpenPGP digital signature