Unlock encrypted root partition over SSH

This commit add a new feature for Debian-based distributions to unlock
encrypted root partition over SSH.  This feature is very handy on
headless NAS or VPS cloud servers.  To use this feature, you will need
to install the dropbear-initramfs package.

Reviewed-By: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-By: Tom Caputi <tcaputi@datto.com>
Signed-off-by: Andrey Prokopenko <job@terem.fr>
Signed-off-by: Richard Laager <rlaager@wiktel.com>
Closes #10027
This commit is contained in:
Andrey Prokopenko 2020-05-03 00:43:42 -05:00 committed by Brian Behlendorf
parent 746d22ee02
commit 1cc635a2dd
8 changed files with 92 additions and 3 deletions

View File

@ -1,5 +1,8 @@
initrddir = /usr/share/initramfs-tools initrddir = /usr/share/initramfs-tools
dist_initrd_SCRIPTS = \
zfsunlock
SUBDIRS = conf.d conf-hooks.d hooks scripts SUBDIRS = conf.d conf-hooks.d hooks scripts
EXTRA_DIST = \ EXTRA_DIST = \

View File

@ -72,3 +72,15 @@ The following kernel command line arguments are supported:
* `zfsdebug=(on,yes,1)`: Show extra debugging information * `zfsdebug=(on,yes,1)`: Show extra debugging information
* `zfsforce=(on,yes,1)`: Force import the pool * `zfsforce=(on,yes,1)`: Force import the pool
* `rollback=(on,yes,1)`: Rollback to (instead of clone) the snapshot * `rollback=(on,yes,1)`: Rollback to (instead of clone) the snapshot
### Unlocking a ZFS encrypted root over SSH
To use this feature:
1. Install the `dropbear-initramfs` package. You may wish to uninstall the
`cryptsetup-initramfs` package to avoid warnings.
2. Add your SSH key(s) to `/etc/dropbear-initramfs/authorized_keys`. Note
that Dropbear does not support ed25519 keys; use RSA (2048-bit or more)
instead.
3. Rebuild the initramfs with your keys: `update-initramfs -u`
4. During the system boot, login via SSH and run: `zfsunlock`

View File

@ -1 +1,2 @@
zfs zfs
zfsunlock

View File

@ -1,10 +1,12 @@
hooksdir = /usr/share/initramfs-tools/hooks hooksdir = /usr/share/initramfs-tools/hooks
hooks_SCRIPTS = \ hooks_SCRIPTS = \
zfs zfs \
zfsunlock
EXTRA_DIST = \ EXTRA_DIST = \
$(top_srcdir)/contrib/initramfs/hooks/zfs.in $(top_srcdir)/contrib/initramfs/hooks/zfs.in \
$(top_srcdir)/contrib/initramfs/hooks/zfsunlock.in
$(hooks_SCRIPTS):%:%.in Makefile $(hooks_SCRIPTS):%:%.in Makefile
-$(SED) -e 's,@sbindir\@,$(sbindir),g' \ -$(SED) -e 's,@sbindir\@,$(sbindir),g' \

View File

@ -21,6 +21,7 @@ COPY_FILE_LIST="$COPY_FILE_LIST @udevruledir@/69-vdev.rules"
# These prerequisites are provided by the base system. # These prerequisites are provided by the base system.
COPY_EXEC_LIST="$COPY_EXEC_LIST /usr/bin/dirname /bin/hostname /sbin/blkid" COPY_EXEC_LIST="$COPY_EXEC_LIST /usr/bin/dirname /bin/hostname /sbin/blkid"
COPY_EXEC_LIST="$COPY_EXEC_LIST /usr/bin/env" COPY_EXEC_LIST="$COPY_EXEC_LIST /usr/bin/env"
COPY_EXEC_LIST="$COPY_EXEC_LIST $(which systemd-ask-password)"
# Explicitly specify all kernel modules because automatic dependency resolution # Explicitly specify all kernel modules because automatic dependency resolution
# is unreliable on many systems. # is unreliable on many systems.

View File

@ -0,0 +1,18 @@
#!/bin/sh
PREREQ="dropbear"
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
copy_exec /usr/share/initramfs-tools/zfsunlock /usr/bin

View File

@ -405,6 +405,8 @@ decrypt_fs()
ENCRYPTIONROOT="$(get_fs_value "${fs}" encryptionroot)" ENCRYPTIONROOT="$(get_fs_value "${fs}" encryptionroot)"
KEYLOCATION="$(get_fs_value "${ENCRYPTIONROOT}" keylocation)" KEYLOCATION="$(get_fs_value "${ENCRYPTIONROOT}" keylocation)"
echo "${ENCRYPTIONROOT}" > /run/zfs_fs_name
# If root dataset is encrypted... # If root dataset is encrypted...
if ! [ "${ENCRYPTIONROOT}" = "-" ]; then if ! [ "${ENCRYPTIONROOT}" = "-" ]; then
KEYSTATUS="$(get_fs_value "${ENCRYPTIONROOT}" keystatus)" KEYSTATUS="$(get_fs_value "${ENCRYPTIONROOT}" keystatus)"
@ -418,6 +420,7 @@ decrypt_fs()
# Prompt with plymouth, if active # Prompt with plymouth, if active
elif [ -e /bin/plymouth ] && /bin/plymouth --ping 2>/dev/null; then elif [ -e /bin/plymouth ] && /bin/plymouth --ping 2>/dev/null; then
echo "plymouth" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do while [ $TRY_COUNT -gt 0 ]; do
plymouth ask-for-password --prompt "Encrypted ZFS password for ${ENCRYPTIONROOT}" | \ plymouth ask-for-password --prompt "Encrypted ZFS password for ${ENCRYPTIONROOT}" | \
$ZFS load-key "${ENCRYPTIONROOT}" && break $ZFS load-key "${ENCRYPTIONROOT}" && break
@ -426,6 +429,7 @@ decrypt_fs()
# Prompt with systemd, if active # Prompt with systemd, if active
elif [ -e /run/systemd/system ]; then elif [ -e /run/systemd/system ]; then
echo "systemd-ask-password" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do while [ $TRY_COUNT -gt 0 ]; do
systemd-ask-password "Encrypted ZFS password for ${ENCRYPTIONROOT}" --no-tty | \ systemd-ask-password "Encrypted ZFS password for ${ENCRYPTIONROOT}" --no-tty | \
$ZFS load-key "${ENCRYPTIONROOT}" && break $ZFS load-key "${ENCRYPTIONROOT}" && break
@ -434,7 +438,8 @@ decrypt_fs()
# Prompt with ZFS tty, otherwise # Prompt with ZFS tty, otherwise
else else
# Setting "printk" temporarily to "7" will allow prompt even if kernel option "quiet" # Temporarily setting "printk" to "7" allows the prompt to appear even when the "quiet" kernel option has been used
echo "load-key" > /run/zfs_console_askpwd_cmd
storeprintk="$(awk '{print $1}' /proc/sys/kernel/printk)" storeprintk="$(awk '{print $1}' /proc/sys/kernel/printk)"
echo 7 > /proc/sys/kernel/printk echo 7 > /proc/sys/kernel/printk
$ZFS load-key "${ENCRYPTIONROOT}" $ZFS load-key "${ENCRYPTIONROOT}"
@ -964,6 +969,11 @@ mountroot()
mount_fs "$fs" mount_fs "$fs"
done done
touch /run/zfs_unlock_complete
if [ -e /run/zfs_unlock_complete_notify ]; then
read zfs_unlock_complete_notify < /run/zfs_unlock_complete_notify
fi
# ------------ # ------------
# Debugging information # Debugging information
if [ -n "${ZFS_DEBUG}" ] if [ -n "${ZFS_DEBUG}" ]

42
contrib/initramfs/zfsunlock Executable file
View File

@ -0,0 +1,42 @@
#!/bin/sh
set -eu
if [ ! -e /run/zfs_fs_name ]; then
echo "Wait for the root pool to be imported or press Ctrl-C to exit."
fi
while [ ! -e /run/zfs_fs_name ]; do
if [ -e /run/zfs_unlock_complete ]; then
exit 0
fi
sleep 0.5
done
echo
echo "Unlocking encrypted ZFS filesystems..."
echo "Enter the password or press Ctrl-C to exit."
echo
zfs_fs_name=""
if [ ! -e /run/zfs_unlock_complete_notify ]; then
mkfifo /run/zfs_unlock_complete_notify
fi
while [ ! -e /run/zfs_unlock_complete ]; do
zfs_fs_name=$(cat /run/zfs_fs_name)
zfs_console_askpwd_cmd=$(cat /run/zfs_console_askpwd_cmd)
systemd-ask-password "Encrypted ZFS password for ${zfs_fs_name}:" | \
/sbin/zfs load-key "$zfs_fs_name" || true
if [ "$(/sbin/zfs get -H -ovalue keystatus "$zfs_fs_name" 2> /dev/null)" = "available" ]; then
echo "Password for $zfs_fs_name accepted."
zfs_console_askpwd_pid=$(ps a -o pid= -o args | grep -v grep | grep "$zfs_console_askpwd_cmd" | cut -d ' ' -f3 | sort -n | head -n1)
if [ -n "$zfs_console_askpwd_pid" ]; then
kill "$zfs_console_askpwd_pid"
fi
# Wait for another filesystem to unlock.
while [ "$(cat /run/zfs_fs_name)" = "$zfs_fs_name" ] && [ ! -e /run/zfs_unlock_complete ]; do
sleep 0.5
done
else
echo "Wrong password. Try again."
fi
done
echo "Unlocking complete. Resuming boot sequence..."
echo "Please reconnect in a while."
echo "ok" > /run/zfs_unlock_complete_notify