Skip to content

Commit

Permalink
Unlock encrypted root partition over SSH
Browse files Browse the repository at this point in the history
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
  • Loading branch information
terem42 authored and behlendorf committed May 7, 2020
1 parent 746d22e commit 1cc635a
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 3 deletions.
3 changes: 3 additions & 0 deletions contrib/initramfs/Makefile.am
@@ -1,5 +1,8 @@
initrddir = /usr/share/initramfs-tools

dist_initrd_SCRIPTS = \
zfsunlock

SUBDIRS = conf.d conf-hooks.d hooks scripts

EXTRA_DIST = \
Expand Down
12 changes: 12 additions & 0 deletions contrib/initramfs/README.initramfs.markdown
Expand Up @@ -72,3 +72,15 @@ The following kernel command line arguments are supported:
* `zfsdebug=(on,yes,1)`: Show extra debugging information
* `zfsforce=(on,yes,1)`: Force import the pool
* `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`
1 change: 1 addition & 0 deletions contrib/initramfs/hooks/.gitignore
@@ -1 +1,2 @@
zfs
zfsunlock
6 changes: 4 additions & 2 deletions contrib/initramfs/hooks/Makefile.am
@@ -1,10 +1,12 @@
hooksdir = /usr/share/initramfs-tools/hooks

hooks_SCRIPTS = \
zfs
zfs \
zfsunlock

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
-$(SED) -e 's,@sbindir\@,$(sbindir),g' \
Expand Down
1 change: 1 addition & 0 deletions contrib/initramfs/hooks/zfs.in
Expand Up @@ -21,6 +21,7 @@ COPY_FILE_LIST="$COPY_FILE_LIST @udevruledir@/69-vdev.rules"
# 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/env"
COPY_EXEC_LIST="$COPY_EXEC_LIST $(which systemd-ask-password)"

# Explicitly specify all kernel modules because automatic dependency resolution
# is unreliable on many systems.
Expand Down
18 changes: 18 additions & 0 deletions contrib/initramfs/hooks/zfsunlock.in
@@ -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
12 changes: 11 additions & 1 deletion contrib/initramfs/scripts/zfs
Expand Up @@ -405,6 +405,8 @@ decrypt_fs()
ENCRYPTIONROOT="$(get_fs_value "${fs}" encryptionroot)"
KEYLOCATION="$(get_fs_value "${ENCRYPTIONROOT}" keylocation)"

echo "${ENCRYPTIONROOT}" > /run/zfs_fs_name

# If root dataset is encrypted...
if ! [ "${ENCRYPTIONROOT}" = "-" ]; then
KEYSTATUS="$(get_fs_value "${ENCRYPTIONROOT}" keystatus)"
Expand All @@ -418,6 +420,7 @@ decrypt_fs()

# Prompt with plymouth, if active
elif [ -e /bin/plymouth ] && /bin/plymouth --ping 2>/dev/null; then
echo "plymouth" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do
plymouth ask-for-password --prompt "Encrypted ZFS password for ${ENCRYPTIONROOT}" | \
$ZFS load-key "${ENCRYPTIONROOT}" && break
Expand All @@ -426,6 +429,7 @@ decrypt_fs()

# Prompt with systemd, if active
elif [ -e /run/systemd/system ]; then
echo "systemd-ask-password" > /run/zfs_console_askpwd_cmd
while [ $TRY_COUNT -gt 0 ]; do
systemd-ask-password "Encrypted ZFS password for ${ENCRYPTIONROOT}" --no-tty | \
$ZFS load-key "${ENCRYPTIONROOT}" && break
Expand All @@ -434,7 +438,8 @@ decrypt_fs()

# Prompt with ZFS tty, otherwise
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)"
echo 7 > /proc/sys/kernel/printk
$ZFS load-key "${ENCRYPTIONROOT}"
Expand Down Expand Up @@ -964,6 +969,11 @@ mountroot()
mount_fs "$fs"
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
if [ -n "${ZFS_DEBUG}" ]
Expand Down
42 changes: 42 additions & 0 deletions contrib/initramfs/zfsunlock
@@ -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

0 comments on commit 1cc635a

Please sign in to comment.