commit: 9e7b41cda3df648b509a0f8a2af29a683100a46f Author: Thomas Deutschmann <whissi <AT> gentoo <DOT> org> AuthorDate: Fri Jan 10 16:07:20 2020 +0000 Commit: Thomas Deutschmann <whissi <AT> gentoo <DOT> org> CommitDate: Sat Jan 11 19:54:40 2020 +0000 URL: https://gitweb.gentoo.org/proj/genkernel.git/commit/?id=9e7b41cd
Rework ZFS unlock - Prompt for key when key is unavailable, not when key is available. - Check ZFS' keystatus property instead of return value to allow remote unlock. - Add unlock-zfs command to remote rescue shell. Closes: https://bugs.gentoo.org/705032 Signed-off-by: Thomas Deutschmann <whissi <AT> gentoo.org> defaults/initrd.defaults | 3 ++ defaults/initrd.scripts | 38 +++++++++++++++----- defaults/linuxrc | 42 +++++++++++++++------- defaults/login-remote.sh | 5 +++ defaults/unlock-zfs.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ gen_initramfs.sh | 6 ++++ 6 files changed, 165 insertions(+), 20 deletions(-) diff --git a/defaults/initrd.defaults b/defaults/initrd.defaults index 198800b..15326dd 100644 --- a/defaults/initrd.defaults +++ b/defaults/initrd.defaults @@ -103,6 +103,9 @@ CRYPT_KEYFILE_SWAP='/tmp/swap.key' CRYPT_ROOT_OPENED_LOCKFILE='/tmp/ROOT.opened' CRYPT_SWAP_OPENED_LOCKFILE='/tmp/SWAP.opened' +ZFS_ENC_ENV_FILE='/etc/ZFS_ENC_ENV.conf' +ZFS_ENC_OPENED_LOCKFILE='/tmp/ZFS.opened' + # Flag for if ok when using CDROOT got_good_root='0' # if LOOP found on root before mount, trigger Unpacking additional packages diff --git a/defaults/initrd.scripts b/defaults/initrd.scripts index 73cd94c..6c7d72b 100644 --- a/defaults/initrd.scripts +++ b/defaults/initrd.scripts @@ -1087,6 +1087,20 @@ warn_msg() { [ "$2" != '1' ] && printf "%b\n" "${WARN}**${NORMAL}${BOLD} ${msg_string} ${NORMAL}" } +write_env_file() { + local env_file=${1} + shift + + run touch "${env_file}" + + local varname= varvalue= + for varname in $* + do + eval varvalue=\$${varname} + echo "${varname}=${varvalue}" >> "${env_file}" + done +} + crypt_filter() { if [ "${CRYPT_SILENT}" = '1' ] then @@ -2265,14 +2279,15 @@ start_sshd() { return fi - # setup environment variables for the ssh login shell - local varname= varvalue= - run touch "${CRYPT_ENV_FILE}" - for varname in CRYPT_ROOT CRYPT_ROOT_TRIM CRYPT_SILENT CRYPT_SWAP - do - eval varvalue=\$${varname} - echo "${varname}=${varvalue}" >> "${CRYPT_ENV_FILE}" - done + # setup environment variables for the remote rescue shell + # ZFS will use a different file because $REAL_ROOT for ZFS + # isn't known yet. + write_env_file \ + "${CRYPT_ENV_FILE}" \ + CRYPT_ROOT \ + CRYPT_ROOT_TRIM \ + CRYPT_SILENT \ + CRYPT_SWAP run touch /var/log/lastlog @@ -2679,6 +2694,13 @@ get_mount_device() { ' ${NEW_ROOT}/etc/fstab } +get_zfs_property() { + local device=${1} + local propertyname=${2} + + echo "$(zfs get -H -o value ${propertyname} "${device}" 2>/dev/null)" +} + # If the kernel is handed a mount option is does not recognize, it WILL fail to # mount. util-linux handles auto/noauto, but busybox passes it straight to the kernel # which then rejects the mount. diff --git a/defaults/linuxrc b/defaults/linuxrc index f585017..7fbd0ad 100644 --- a/defaults/linuxrc +++ b/defaults/linuxrc @@ -491,7 +491,7 @@ then if [ ! -x ${i} ] then USE_ZFS=0 - bad_msg 'Aborting use of zfs because ${i} not found!' + bad_msg "Aborting use of ZFS because ${i} not found!" break fi done @@ -740,7 +740,7 @@ do ROOT_DEV="${REAL_ROOT#*=}" if [ "${ROOT_DEV}" != 'ZFS' ] then - if [ "$(zfs get type -o value -H ${ROOT_DEV} 2>/dev/null)" = 'filesystem' ] + if [ "$(get_zfs_property "${ROOT_DEV}" type)" = 'filesystem' ] then got_good_root=1 REAL_ROOT=${ROOT_DEV} @@ -753,7 +753,7 @@ do continue fi else - BOOTFS=$(/sbin/zpool list -H -o bootfs 2>/dev/null) + BOOTFS=$(zpool list -H -o bootfs 2>/dev/null) if [ "${BOOTFS}" != '-' ] then for i in ${BOOTFS} @@ -801,6 +801,14 @@ do echo fi + if [ "${USE_ZFS}" = '1' ] + then + write_env_file \ + "${ZFS_ENC_ENV_FILE}" \ + REAL_ROOT \ + ROOTFSTYPE + fi + # Check for a block device or /dev/nfs or zfs encryption if [ -n "${REAL_ROOT}" ] && [ "${REAL_ROOT}" = "/dev/nfs" ] || [ "${ROOTFSTYPE}" = "zfs" ] || [ -b "${REAL_ROOT}" ] then @@ -810,20 +818,30 @@ do # let's check if this dataset is encrypted and ask for passphrase if [ "$(zpool list -H -o feature@encryption "${REAL_ROOT%%/*}" 2>/dev/null)" = 'active' ] then - ZFS_KEYSTATUS="$(zfs get -H -o value keystatus "${REAL_ROOT}" 2>/dev/null)" - ZFS_ENCRYPTIONROOT="$(zfs get -H -o value encryptionroot "${REAL_ROOT}" 2>/dev/null)" - if ! [ "${ZFS_ENCRYPTIONROOT}" = '-' ] || [ "${ZFS_KEYSTATUS}" = 'available' ] + ZFS_KEYSTATUS="$(get_zfs_property "${REAL_ROOT}" keystatus)" + ZFS_ENCRYPTIONROOT="$(get_zfs_property "${REAL_ROOT}" encryptionroot)" + if [ "${ZFS_ENCRYPTIONROOT}" != '-' ] && [ "${ZFS_KEYSTATUS}" = 'unavailable' ] then good_msg "Detected ZFS encryption, asking for key" - zfs load-key "${ZFS_ENCRYPTIONROOT}" - retval=$? - # if the key loaded fine, confirm got_good_root to exit second while loop - if [ ${retval} -ne 0 ] + run zfs load-key "${ZFS_ENCRYPTIONROOT}" + + # Get new key status to check if load-key was successful + # or dataset has been opened by someone else in the meantime (through SSH for instance) + ZFS_KEYSTATUS="$(get_zfs_property "${REAL_ROOT}" keystatus)" + + if [ "${ZFS_KEYSTATUS}" != 'available' ] then - bad_msg "${ROOT_DEV} is encrypted and not mountable without key" + bad_msg "${REAL_ROOT} is encrypted and not mountable without key" got_good_root=0 break fi + + if [ -f "${ZFS_ENC_OPENED_LOCKFILE}" ] + then + good_msg "${REAL_ROOT} device meanwhile was opened by someone else." + else + run touch "${ZFS_ENC_OPENED_LOCKFILE}" + fi fi fi else @@ -849,7 +867,7 @@ do if [ "${ROOTFSTYPE}" = 'zfs' ] then - if [ "$(zfs get -H -o value mountpoint "${REAL_ROOT}")" = 'legacy' ] + if [ "$(get_zfs_property "${REAL_ROOT}" mountpoint)" = 'legacy' ] then MOUNT_STATE=rw else diff --git a/defaults/login-remote.sh b/defaults/login-remote.sh index 588504f..94ee014 100644 --- a/defaults/login-remote.sh +++ b/defaults/login-remote.sh @@ -105,6 +105,11 @@ else good_msg "${NORMAL}To remote unlock LUKS-encrypted swap device, run '${BOLD}unlock-luks swap${NORMAL}'." fi + if [ -e "${ZFS_ENC_ENV_FILE}" ] && [ ! -f "${ZFS_ENC_OPENED_LOCKFILE}" ] + then + good_msg "${NORMAL}To remote unlock ZFS root device, run '${BOLD}unlock-zfs${NORMAL}'." + fi + echo [ -x /bin/sh ] && SH=/bin/sh || SH=/bin/ash diff --git a/defaults/unlock-zfs.sh b/defaults/unlock-zfs.sh new file mode 100644 index 0000000..c22a214 --- /dev/null +++ b/defaults/unlock-zfs.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +. /etc/initrd.defaults +. /etc/initrd.scripts + +GK_INIT_LOG_PREFIX=${0} +if [ -n "${SSH_CLIENT_IP}" ] && [ -n "${SSH_CLIENT_PORT}" ] +then + GK_INIT_LOG_PREFIX="${0}[${SSH_CLIENT_IP}:${SSH_CLIENT_PORT}]" +fi + +if [ -f "${ZFS_ENC_ENV_FILE}" ] +then + . "${ZFS_ENC_ENV_FILE}" +else + bad_msg "${ZFS_ENC_ENV_FILE} does not exist! Did you boot without 'dozfs' kernel command-line parameter?" + exit 1 +fi + +main() { + if ! hash zfs >/dev/null 2>&1 + then + bad_msg "zfs program is missing. Was initramfs built without --zfs parameter?" + exit 1 + elif ! hash zpool >/dev/null 2>&1 + then + bad_msg "zpool program is missing. Was initramfs built without --zfs parameter?" + exit 1 + elif [ -z "${ROOTFSTYPE}" ] + then + bad_msg "Something went wrong. ROOTFSTYPE is not set!" + exit 1 + elif [ "${ROOTFSTYPE}" != "zfs" ] + then + bad_msg "ROOTFSTYPE of 'zfs' required but '${ROOTFSTYPE}' detected!" + exit 1 + elif [ -z "${REAL_ROOT}" ] + then + bad_msg "Something went wrong. REAL_ROOT is not set!" + exit 1 + fi + + if [ "$(zpool list -H -o feature@encryption "${REAL_ROOT%%/*}" 2>/dev/null)" != 'active' ] + then + bad_msg "Root device ${REAL_ROOT} is not encrypted!" + exit 1 + fi + + local ZFS_ENCRYPTIONROOT="$(get_zfs_property "${REAL_ROOT}" encryptionroot)" + if [ "${ZFS_ENCRYPTIONROOT}" = '-' ] + then + bad_msg "Failed to determine encryptionroot for ${REAL_ROOT}!" + exit 1 + fi + + local ZFS_KEYSTATUS= + while true + do + if [ -e "${ZFS_ENC_OPENED_LOCKFILE}" ] + then + good_msg "${REAL_ROOT} device meanwhile was opened by someone else." + break + fi + + zfs load-key "${ZFS_ENCRYPTIONROOT}" + + ZFS_KEYSTATUS="$(get_zfs_property "${REAL_ROOT}" keystatus)" + if [ "${ZFS_KEYSTATUS}" = 'available' ] + then + run touch "${ZFS_ENC_OPENED_LOCKFILE}" + good_msg "ZFS device ${REAL_ROOT} opened" + break + else + bad_msg "Failed to open ZFS device ${REAL_ROOT}" + + # We need to stop here with a non-zero exit code to prevent + # a loop when invalid keyfile was sent. + exit 1 + fi + done + + if [ "${ZFS_KEYSTATUS}" = 'available' ] + then + # Kill any running load-key prompt. + run pkill -f "load-key" >/dev/null 2>&1 + fi +} + +main + +exit 0 diff --git a/gen_initramfs.sh b/gen_initramfs.sh index 676b86d..8620414 100755 --- a/gen_initramfs.sh +++ b/gen_initramfs.sh @@ -1342,6 +1342,9 @@ append_dropbear() { cp -a "${GK_SHARE}"/defaults/unlock-luks.sh "${TDIR}"/usr/sbin/unlock-luks \ || gen_die "Failed to copy '${GK_SHARE}/defaults/unlock-luks.sh' to '${TDIR}/usr/sbin/unlock-luks'" + cp -a "${GK_SHARE}"/defaults/unlock-zfs.sh "${TDIR}"/usr/sbin/unlock-zfs \ + || gen_die "Failed to copy '${GK_SHARE}/defaults/unlock-zfs.sh' to '${TDIR}/usr/sbin/unlock-zfs'" + cp -aL "${DROPBEAR_AUTHORIZED_KEYS_FILE}" "${TDIR}"/root/.ssh/ \ || gen_die "Failed to copy '${DROPBEAR_AUTHORIZED_KEYS_FILE}'!" @@ -1369,6 +1372,9 @@ append_dropbear() { chmod 0755 "${TDIR}"/usr/sbin/unlock-luks \ || gen_die "Failed to chmod of '${TDIR}/usr/sbin/unlock-luks'!" + chmod 0755 "${TDIR}"/usr/sbin/unlock-zfs \ + || gen_die "Failed to chmod of '${TDIR}/usr/sbin/unlock-zfs'!" + chmod 0640 "${TDIR}"/etc/shadow \ || gen_die "Failed to chmod of '${TDIR}/etc/shadow'!"