From b4307cc4843fc43c5380ebd8f612088ac5b0b71e Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Sun, 20 Oct 2013 10:58:50 +0200 Subject: [PATCH] initramfs: add support for multiple crypted root and swap devices It is now possible to pass multiple crypt_roots= and crypt_swaps= parameters (mind the "s") and have multiple devices concurring to building the final root device and swap areas activated. The change is backward compatible and crypt_root=, crypt_swap= is still fully supported. This change makes possible to support multiple device mapper block devices grouped together into aggregated software raid devices. For instance, two individual luks devices can be now used to build a single raid0 or raid1 md device. Moreover, genkernel-next initramfs now supports multiple encrypted swap devices. --- defaults/initrd.d/00-crypt.sh | 350 ++++++++++++++++++--------------- defaults/initrd.d/00-livecd.sh | 6 +- defaults/initrd.d/00-zfs.sh | 2 +- defaults/linuxrc | 19 +- doc/genkernel.8.txt | 27 ++- 5 files changed, 232 insertions(+), 172 deletions(-) diff --git a/defaults/initrd.d/00-crypt.sh b/defaults/initrd.d/00-crypt.sh index 745b9c1..dfebeb7 100755 --- a/defaults/initrd.d/00-crypt.sh +++ b/defaults/initrd.d/00-crypt.sh @@ -14,15 +14,17 @@ _bootstrap_key() { } _crypt_exec() { + local luks_dev="${1}" + local cmd="${2}" # TODO(lxnay): this fugly crypt_silent should really go away if [ "${CRYPT_SILENT}" = "1" ]; then - eval ${1} >/dev/null 2>/dev/null + eval ${cmd} >/dev/null 2>/dev/null else ask_for_password --ply-tries 5 \ - --ply-cmd "${1}" \ - --ply-prompt "Encryption password (${LUKS_DEVICE}): " \ + --ply-cmd "${cmd}" \ + --ply-prompt "Encryption password (${luks_dev}): " \ --tty-tries 5 \ - --tty-cmd "${1}" || return 1 + --tty-cmd "${cmd}" || return 1 return 0 fi } @@ -30,19 +32,22 @@ _crypt_exec() { _open_luks() { case ${1} in root) + local ltypes=ROOTS local ltype=ROOT ;; swap) + local ltypes=SWAPS local ltype=SWAP ;; esac - eval local LUKS_DEVICE='"${CRYPT_'${ltype}'}"' - eval local LUKS_KEY='"${CRYPT_'${ltype}'_KEY}"' - eval local LUKS_KEYDEV='"${CRYPT_'${ltype}'_KEYDEV}"' - eval local LUKS_TRIM='"${CRYPT_'${ltype}'_TRIM}"' + eval local luks_devices='"${CRYPT_'${ltypes}'}"' + eval local luks_key='"${CRYPT_'${ltype}'_KEY}"' + eval local luks_keydev='"${CRYPT_'${ltype}'_KEYDEV}"' + eval local luks_trim='"${CRYPT_'${ltype}'_TRIM}"' + + local luks_name="${1}" - local LUKS_NAME="${1}" local dev_error=0 key_error=0 keydev_error=0 local mntkey="/mnt/key/" cryptsetup_opts="" @@ -51,187 +56,204 @@ _open_luks() { return 1 fi - local exit_st= - - while true; do - local gpg_cmd="" - exit_st=1 - - # do not force the link to /dev/mapper/root - # but rather use the value from root=, which is - # in ${REAL_ROOT} - local luks_dev_name=$(basename "${LUKS_DEVICE}") - local real_dev= - local luks_name_prefix= - if [ "${ltype}" = "ROOT" ]; then - real_dev="${REAL_ROOT}" - elif [ "${ltype}" = "SWAP" ]; then - real_dev="${REAL_RESUME}" - fi - if echo "${real_dev}" | grep -q "^/dev/mapper/"; then - # If we use LVM + cryptsetup, we may have collisions between - # the two inside /dev/mapper. So, make up a way to avoid them. - LUKS_NAME="${LUKS_NAME}_${luks_dev_name}-$(basename ${real_dev})" - fi + local real_dev= + if [ "${ltype}" = "ROOT" ]; then + real_dev="${REAL_ROOT}" + elif [ "${ltype}" = "SWAP" ]; then + real_dev="${REAL_RESUME}" + fi - # if crypt_silent=1 and some error occurs, bail out. - local any_error= - [ "${dev_error}" = "1" ] && any_error=1 - [ "${key_error}" = "1" ] && any_error=1 - [ "${keydev_error}" = "1" ] && any_error=1 - if [ "${CRYPT_SILENT}" = "1" ] && [ -n "${any_error}" ]; then - bad_msg "Failed to setup the LUKS device" - exit_st=1 - break - fi + local exit_st= luks_device= + for luks_device in ${luks_devices}; do - if [ "${dev_error}" = "1" ]; then - prompt_user "LUKS_DEVICE" "${LUKS_NAME}" - dev_error=0 - continue - fi + good_msg "Working on device ${luks_device}..." - if [ "${key_error}" = "1" ]; then - prompt_user "LUKS_KEY" "${LUKS_NAME} key" - key_error=0 - continue - fi + while true; do - if [ "${keydev_error}" = "1" ]; then - prompt_user "LUKS_KEYDEV" "${LUKS_NAME} key device" - keydev_error=0 - continue - fi + local gpg_cmd="" + exit_st=1 - local luks_dev=$(find_real_device "${LUKS_DEVICE}") - [ -n "${luks_dev}" ] && LUKS_DEVICE="${luks_dev}" # otherwise hope... + # do not force the link to /dev/mapper/root + # but rather use the value from root=, which is + # in ${REAL_ROOT} + local luks_dev_name=$(basename "${luks_device}") + local luks_name_prefix= - setup_md_device "${LUKS_DEVICE}" - cryptsetup isLuks "${LUKS_DEVICE}" || { - bad_msg "${LUKS_DEVICE} does not contain a LUKS header" - dev_error=1 - continue; - } + if echo "${real_dev}" | grep -q "^/dev/mapper/"; then + local real_dev_bn=$(basename "${real_dev}") + # If we use LVM + cryptsetup, we may have collisions between + # the two inside /dev/mapper. So, make up a way to avoid them. + luks_dev_name="${luks_name}_${luks_dev_name}-${real_dev_bn}" + fi - # Handle keys - if [ "${LUKS_TRIM}" = "yes" ]; then - good_msg "Enabling TRIM support for ${LUKS_NAME}." - cryptsetup_opts="${cryptsetup_opts} --allow-discards" - fi + # if crypt_silent=1 and some error occurs, bail out. + local any_error= + [ "${dev_error}" = "1" ] && any_error=1 + [ "${key_error}" = "1" ] && any_error=1 + [ "${keydev_error}" = "1" ] && any_error=1 + if [ "${CRYPT_SILENT}" = "1" ] && [ -n "${any_error}" ]; then + bad_msg "Failed to setup the LUKS device" + exit_st=1 + break + fi + + if [ "${dev_error}" = "1" ]; then + prompt_user "luks_device" "${luks_dev_name}" + dev_error=0 + continue + fi - if [ -n "${LUKS_KEY}" ]; then - local real_luks_keydev="${LUKS_KEYDEV}" + if [ "${key_error}" = "1" ]; then + prompt_user "luks_key" "${luks_dev_name} key" + key_error=0 + continue + fi - if [ ! -e "${mntkey}${LUKS_KEY}" ]; then - real_luks_keydev=$(find_real_device "${LUKS_KEYDEV}") - good_msg "Using key device ${real_luks_keydev}." + if [ "${keydev_error}" = "1" ]; then + prompt_user "luks_keydev" "${luks_dev_name} key device" + keydev_error=0 + continue + fi - if [ ! -b "${real_luks_keydev}" ]; then - bad_msg "Insert device ${LUKS_KEYDEV} for ${LUKS_NAME}" - bad_msg "You have 10 seconds..." - local count=10 - while [ ${count} -gt 0 ]; do - count=$((count-1)) - sleep 1 + local luks_dev=$(find_real_device "${luks_device}") + [ -n "${luks_dev}" ] && \ + luks_device="${luks_dev}" # otherwise hope... + + setup_md_device "${luks_device}" + cryptsetup isLuks "${luks_device}" || { + bad_msg "${luks_device} does not contain a LUKS header" + dev_error=1 + continue; + } + + # Handle keys + if [ "${luks_trim}" = "yes" ]; then + good_msg "Enabling TRIM support for ${luks_dev_name}." + cryptsetup_opts="${cryptsetup_opts} --allow-discards" + fi - real_luks_keydev=$(find_real_device "${LUKS_KEYDEV}") - [ ! -b "${real_luks_keydev}" ] || { - good_msg "Device ${real_luks_keydev} detected." - break; - } - done + if [ -n "${luks_key}" ]; then + local real_luks_keydev="${luks_keydev}" + + if [ ! -e "${mntkey}${luks_key}" ]; then + real_luks_keydev=$(find_real_device "${luks_keydev}") + good_msg "Using key device ${real_luks_keydev}." if [ ! -b "${real_luks_keydev}" ]; then - eval CRYPT_${ltype}_KEY=${LUKS_KEY} - _bootstrap_key ${ltype} - eval LUKS_KEYDEV='"${CRYPT_'${ltype}'_KEYDEV}"' + bad_msg "Insert device ${luks_keydev} for ${luks_dev_name}" + bad_msg "You have 10 seconds..." + local count=10 + while [ ${count} -gt 0 ]; do + count=$((count-1)) + sleep 1 + + real_luks_keydev=$(find_real_device "${luks_keydev}") + [ ! -b "${real_luks_keydev}" ] || { + good_msg "Device ${real_luks_keydev} detected." + break; + } + done - real_luks_keydev=$(find_real_device "${LUKS_KEYDEV}") if [ ! -b "${real_luks_keydev}" ]; then - keydev_error=1 - bad_msg "Removable device ${LUKS_KEYDEV} not found." + eval CRYPT_${ltype}_KEY=${luks_key} + _bootstrap_key ${ltype} + eval luks_keydev='"${CRYPT_'${ltype}'_KEYDEV}"' + + real_luks_keydev=$(find_real_device "${luks_keydev}") + if [ ! -b "${real_luks_keydev}" ]; then + keydev_error=1 + bad_msg "Device ${luks_keydev} not found." + continue + fi + + # continue otherwise will mount keydev which is + # mounted by bootstrap continue fi + fi - # continue otherwise will mount keydev which is - # mounted by bootstrap + # At this point a device was recognized, now let's see + # if the key is there + mkdir -p "${mntkey}" # ignore + + mount -n -o ro "${real_luks_keydev}" \ + "${mntkey}" || { + keydev_error=1 + bad_msg "Mounting of device ${real_luks_keydev} failed." + continue; + } + + good_msg "Removable device ${real_luks_keydev} mounted." + + if [ ! -e "${mntkey}${luks_key}" ]; then + umount -n "${mntkey}" + key_error=1 + keydev_error=1 + bad_msg "{luks_key} on ${real_luks_keydev} not found." continue fi fi - # At this point a device was recognized, now let's see - # if the key is there - mkdir -p "${mntkey}" # ignore - - mount -n -o ro "${real_luks_keydev}" \ - "${mntkey}" || { - keydev_error=1 - bad_msg "Mounting of device ${real_luks_keydev} failed." - continue; - } - - good_msg "Removable device ${real_luks_keydev} mounted." - - if [ ! -e "${mntkey}${LUKS_KEY}" ]; then - umount -n "${mntkey}" - key_error=1 - keydev_error=1 - bad_msg "{LUKS_KEY} on ${real_luks_keydev} not found." - continue + # At this point a candidate key exists + # (either mounted before or not) + good_msg "${luks_key} on device ${real_luks_keydev} found" + if [ "$(echo ${luks_key} | grep -o '.gpg$')" = ".gpg" ] && \ + [ -e /usr/bin/gpg ]; then + + # TODO(lxnay): WTF is this? + [ -e /dev/tty ] && mv /dev/tty /dev/tty.org + mknod /dev/tty c 5 1 + + cryptsetup_opts="${cryptsetup_opts} -d -" + gpg_cmd="/usr/bin/gpg --logger-file /dev/null" + gpg_cmd="${gpg_cmd} --quiet --decrypt ${mntkey}${luks_key} | " + else + cryptsetup_opts="${cryptsetup_opts} -d ${mntkey}${luks_key}" fi fi - # At this point a candidate key exists - # (either mounted before or not) - good_msg "${LUKS_KEY} on device ${real_luks_keydev} found" - if [ "$(echo ${LUKS_KEY} | grep -o '.gpg$')" = ".gpg" ] && \ - [ -e /usr/bin/gpg ]; then - - # TODO(lxnay): WTF is this? - [ -e /dev/tty ] && mv /dev/tty /dev/tty.org - mknod /dev/tty c 5 1 - - cryptsetup_opts="${cryptsetup_opts} -d -" - gpg_cmd="/usr/bin/gpg --logger-file /dev/null" - gpg_cmd="${gpg_cmd} --quiet --decrypt ${mntkey}${LUKS_KEY} | " - else - cryptsetup_opts="${cryptsetup_opts} -d ${mntkey}${LUKS_KEY}" - fi - fi - - # At this point, keyfile or not, we're ready! - local cmd="${gpg_cmd}/sbin/cryptsetup" - cmd="${cmd} ${cryptsetup_opts} luksOpen ${LUKS_DEVICE} ${LUKS_NAME}" - _crypt_exec "${cmd}" - local ret="${?}" - - # TODO(lxnay): WTF is this? - [ -e /dev/tty.org ] \ - && rm -f /dev/tty \ - && mv /dev/tty.org /dev/tty - - if [ "${ret}" = "0" ]; then - exit_st=0 - good_msg "LUKS device ${LUKS_DEVICE} opened" - - # This is fine if the crypt device is a physical device - # like /dev/sdaX, however, if we have cryptsetup inside - # LVM, we must tweak REAL_ROOT if there is no device node. - start_volumes # this should create /dev/mapper links - if echo "${real_dev}" | grep -q "^/dev/mapper/"; then - if [ ! -e "${real_dev}" ]; then - good_msg "Creating symlink for ${LUKS_NAME} to ${real_dev}" - ln -s "${LUKS_NAME}" "${real_dev}" || exit_st=1 + # At this point, keyfile or not, we're ready! + local cmd="${gpg_cmd}/sbin/cryptsetup" + cmd="${cmd} ${cryptsetup_opts} open ${luks_device} ${luks_dev_name}" + _crypt_exec "${luks_device}" "${cmd}" + local ret="${?}" + + # TODO(lxnay): WTF is this? + [ -e /dev/tty.org ] \ + && rm -f /dev/tty \ + && mv /dev/tty.org /dev/tty + + if [ "${ret}" = "0" ]; then + exit_st=0 + good_msg "LUKS device ${luks_device} opened" + + # This is fine if the crypt device is a physical device + # like /dev/sdaX, however, if we have cryptsetup inside + # LVM, we must tweak REAL_ROOT if there is no device node. + start_volumes # this should create /dev/mapper links + if echo "${real_dev}" | grep -q "^/dev/mapper/"; then + if [ ! -e "${real_dev}" ]; then + # WARN: while for ltype=SWAP this may not be a problem, + # for ltype=ROOT this may render the system unbootable + # because lvm can get angry to see a symlink where it's + # not supposed to be or we may fail to create the proper + # link (due to the if above), however, reordering the + # cmdline entries may solve this. + good_msg "Creating symlink ${luks_dev_name} -> ${real_dev}" + ln -s "${luks_dev_name}" "${real_dev}" || exit_st=1 + fi fi + + break fi - break - fi + bad_msg "Failed to open LUKS device ${luks_device}" + dev_error=1 + key_error=1 + keydev_error=1 + + done - bad_msg "Failed to open LUKS device ${LUKS_DEVICE}" - dev_error=1 - key_error=1 - keydev_error=1 done umount -l "${mntkey}" 2>/dev/null >/dev/null @@ -246,7 +268,7 @@ start_luks() { [ -n "${CRYPT_ROOT_KEY}" ] && [ -z "${CRYPT_ROOT_KEYDEV}" ] \ && sleep 6 && _bootstrap_key "ROOT" - if [ -n "${CRYPT_ROOT}" ]; then + if [ -n "${CRYPT_ROOTS}" ]; then if _open_luks "root"; then # force REAL_ROOT= to some value if not set # this is mainly for backward compatibility, @@ -259,9 +281,9 @@ start_luks() { # TODO(lxnay): this sleep 6 thing is hurting my eyes sooooo much. # same for swap, but no need to sleep if root was unencrypted [ -n "${CRYPT_SWAP_KEY}" ] && [ -z "${CRYPT_SWAP_KEYDEV}" ] \ - && { [ -z "${CRYPT_ROOT}" ] && sleep 6; _bootstrap_key "SWAP"; } + && { [ -z "${CRYPT_ROOTS}" ] && sleep 6; _bootstrap_key "SWAP"; } - if [ -n "${CRYPT_SWAP}" ]; then + if [ -n "${CRYPT_SWAPS}" ]; then if _open_luks "swap"; then # force REAL_RESUME= to some value if not set [ -z "${REAL_RESUME}" ] && REAL_RESUME="/dev/mapper/swap" diff --git a/defaults/initrd.d/00-livecd.sh b/defaults/initrd.d/00-livecd.sh index f483071..350267e 100755 --- a/defaults/initrd.d/00-livecd.sh +++ b/defaults/initrd.d/00-livecd.sh @@ -360,12 +360,12 @@ livecd_mount() { _cache_cd_contents # If encrypted, find key and mount, otherwise mount as usual - if [ -n "${CRYPT_ROOT}" ]; then + if [ -n "${CRYPT_ROOTS}" ]; then CRYPT_ROOT_KEY=$(head -n 1 "${CDROOT_PATH}/${CDROOT_MARKER}") - CRYPT_ROOT="$(losetup -f)" + CRYPT_ROOTS="$(losetup -f)" # support only one value for livecd good_msg "You booted an encrypted livecd" - losetup "${CRYPT_ROOT}" "${CDROOT_PATH}/${LOOPEXT}${LOOP}" + losetup "${CRYPT_ROOTS}" "${CDROOT_PATH}/${LOOPEXT}${LOOP}" test_success "Preparing loop filesystem" start_luks diff --git a/defaults/initrd.d/00-zfs.sh b/defaults/initrd.d/00-zfs.sh index 7df7056..7c9acc4 100755 --- a/defaults/initrd.d/00-zfs.sh +++ b/defaults/initrd.d/00-zfs.sh @@ -86,7 +86,7 @@ zfs_start_volumes() { if [ "${pools}" = "${ZFS_POOL}" ]; then good_msg "ZFS pool ${ZFS_POOL} already imported." - if [ -n "${CRYPT_ROOT}" ] || [ -n "${CRYPT_SWAP}" ]; then + if [ -n "${CRYPT_ROOTS}" ] || [ -n "${CRYPT_SWAPS}" ]; then good_msg "LUKS detected. Reimporting ${ZFS_POOL}" zpool export -f "${ZFS_POOL}" zpool import -N ${ZPOOL_FORCE} "${ZFS_POOL}" diff --git a/defaults/linuxrc b/defaults/linuxrc index eb7e14b..d690114 100755 --- a/defaults/linuxrc +++ b/defaults/linuxrc @@ -195,11 +195,26 @@ for x in ${CMDLINE}; do ;; crypt_root=*) - CRYPT_ROOT=${x#*=} + # kept for backward compatibility + CRYPT_ROOTS=${x#*=} ;; crypt_swap=*) - CRYPT_SWAP=${x#*=} + # kept for backward compatibility + CRYPT_SWAPS=${x#*=} ;; + + crypt_roots=*) + # The first entry will be the one that + # is going to be mapped to ${REAL_ROOT}. + # Multiple "roots" devices are needed + # in order to support multiple dmcrypts + # aggregated through software raid arrays. + CRYPT_ROOTS="${CRYPT_ROOTS} ${x#*=}" + ;; + crypt_swaps=*) + CRYPT_SWAPS="${CRYPT_SWAPS} ${x#*=}" + ;; + root_key=*) CRYPT_ROOT_KEY=${x#*=} ;; diff --git a/doc/genkernel.8.txt b/doc/genkernel.8.txt index 5fbfff0..2d6b988 100644 --- a/doc/genkernel.8.txt +++ b/doc/genkernel.8.txt @@ -398,10 +398,33 @@ which the ramdisk scripts would recognize. *crypt_root*=<...>:: This specifies the device encrypted by Luks, which contains the - root filesystem to mount. + root filesystem to mount. If you intend to use multiple devices + cooperating in the building of a single root device node (like + for instance in the case of dmcrypts -> software raid), please + use crypt_roots= instead. + +*crypt_roots*=<...>:: + This specifies the devices encrypted by Luks, which concur in + the availability of a device node used for the root filesystem. + For instance, you could have two separately encrypted partitions + on separate disks that are part of a software raid setup. You just + need to declare multiple crypt_roots= statements each one containing + one device path. Please keep in mind that the entries will be processed + in the order they are declared. If you use crypt_roots=, remember to + remove any reference to crypt_root=. They all share the same root_key= + and root_keydev=. *crypt_swap*=<...>:: - This specifies the swap device encrypted by Luks. + This specifies the swap device encrypted by Luks. If you have more than + one encrypted swap, please use crypt_swaps= instead. + +*crypt_swaps*=<...>:: + This specifies the swap devices encrypted by Luks. You just + need to declare multiple crypt_swaps= statements each one containing + one device path. Please keep in mind that the entries will be processed + in the order they are declared. If you use crypt_swaps=, remember to + remove any reference to crypt_swap=. They all share the same root_key= + and root_keydev=. *root_key*=<...>:: In case your root is encrypted with a key, you can use a device