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