Skip to content

Commit

Permalink
Support early hooks, add example luks-unlock.sh
Browse files Browse the repository at this point in the history
Add an early hook loop just after SPL and ZFS kernel modules have been
loaded, but before any pools have been imported. This allows for making
keys available from sources other than keyboard input.

Included is an example 'keystore' script; luks-unlock.sh. Using this
script, you can store one or more pool keys on a LUKS volume with
multiple key slots (which ZFS does not support), and make them available
prior to pool imports.

Co-authored-by: Zach Dykstra <dykstra.zachary@gmail.com>
Co-authored-by: Andrew J. Hesford <ajh@sideband.org>
  • Loading branch information
zdykstra and ahesford committed Sep 8, 2021
1 parent 62f7315 commit c2a1bee
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
12 changes: 12 additions & 0 deletions 90zfsbootmenu/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ install() {
inst_hook cmdline 95 "${moddir}/zfsbootmenu-parse-commandline.sh" || _ret=$?
inst_hook pre-mount 90 "${moddir}/zfsbootmenu-preinit.sh" || _ret=$?

# Install "early setup" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_early_setup}" ]; then
for _exec in ${zfsbootmenu_early_setup}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/early-setup.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "setup script (${_exec}) missing or not executable; cannot install"
fi
done
fi

# Install "setup" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_setup}" ]; then
Expand Down
10 changes: 10 additions & 0 deletions 90zfsbootmenu/zfsbootmenu-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ elif [ ! -e /etc/hostid ]; then
write_hostid "${default_hostid}"
fi

# Run early setup hooks, if they exist
if [ -d /libexec/early-setup.d ]; then
tput clear
for _hook in /libexec/early-setup.d/*; do
zinfo "Processing hook: ${_hook}"
[ -x "${_hook}" ] && "${_hook}"
done
unset _hook
fi

# Prefer a specific pool when checking for a bootfs value
# shellcheck disable=SC2154
if [ "${root}" = "zfsbootmenu" ]; then
Expand Down
87 changes: 87 additions & 0 deletions contrib/luks-unlock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

## This early-setup hook finds a LUKS volume by looking for a partition with
## label "KEYSTORE". (Partition labels are supported on GPT and a few obsolete
## disklabel formats; see the "name" command in parted(8) for details.)
##
## If a KEYSTORE partition is found, the hook attempts repeatedly to unlock and
## mount the encrypted volume (read-only) at /etc/zfs/keys. If successful, this
## will allow ZFSBootMenu to automatically unlock any ZFS datasets that define
## the ZFS property
##
## keylocation=file:///etc/zfs/keys
##
## If the partition cannot be found, does not appear to be a LUKS volume or has
## already been activated, the hook will terminate and allow ZFSBootMenu to
## proceed with its ordinary startup process. Once the hook begins the unlock
## loop, it will not terminate until either the volume is successfully unlocked
## or the user presses Ctrl-C to abandon the attempts. After every failed
## unlock cycle, an emergency shell will be invoked to allow manual
## intervention; type `exit` in the shell to continue the unlock loop.
##
## Because this script is intended to provide unlock keys *before* ZFSBootMenu
## imports ZFS pools, it should be run as an early hook. To install, put this
## script somewhere, make sure it is executable, and add the path to the
## `zfsbootmenu_early_setup` space-separated list with, e.g.,
##
## zfsbootmenu_early_setup+=" <path to script> "
##
## in a dracut.conf(5) file inside the directory specified for the option
## `Global.DracutConfDir` in the ZFSBootMenu `config.yaml`.

# shellcheck disable=SC1091
[ -f /lib/zfsbootmenu-lib.sh ] && source /lib/zfsbootmenu-lib.sh

luks="/dev/disk/by-partlabel/KEYSTORE"
dm="/dev/mapper/KEYSTORE"

if [ ! -b "${luks}" ] ; then
zinfo "keystore device ${luks} does not exist"
exit
fi

if ! cryptsetup isLuks ${luks} >/dev/null 2>&1 ; then
zwarn "keystore device ${luks} missing LUKS partition header"
exit
fi

if cryptsetup status "${dm}" >/dev/null 2>&1 ; then
zinfo "${dm} already active, not continuing"
exit
fi

header="$( center_string "[CTRL-C] cancel luksOpen attempts" )"

while true; do
tput clear
colorize red "${header}\n\n"

cryptsetup --tries=5 luksOpen "${luks}" KEYSTORE
ret=$?

# successfully entered a passphrase
if [ "${ret}" -eq 0 ] ; then
mkdir -p /etc/zfs/keys
mount -r "${dm}" /etc/zfs/keys
zdebug "$(
cryptsetup status "${dm}"
mount | grep KEYSTORE
)"
exit
fi

# ctrl-c'd the process
if [ "${ret}" -eq 1 ] ; then
zdebug "canceled luksOpen attempts via SIGINT"
exit
fi

# failed all password attempts
if [ "${ret}" -eq 2 ] ; then
if prompt="Continuing in %0.2d seconds" timed_prompt "[RETURN] continue unlock attempts" "[ESCAPE] emergency shell" "" ; then
continue
else
emergency_shell "Unable to unlock LUKS partition"
fi
fi
done
6 changes: 6 additions & 0 deletions pod/zfsbootmenu.7.pod
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ In addition to standard dracut configuration options, the ZFSBootMenu dracut mod

=over 4

=item B<zfsbootmenu_early_setup=E<lt>executable-listE<gt>>

An optional variable specifying a space-separated list of paths to setup hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list B<E<lt>executable-listE<gt>> that exists and is executable will be installed.

Any installed early hooks are run after SPL and ZFS kernel modules are loaded and a hostid is configured in I</etc/hostid>, but before any zpools have been imported.

=item B<zfsbootmenu_setup=E<lt>executable-listE<gt>>

An optional variable specifying a space-separated list of paths to setup hooks that will be installed in the ZFSBootMenu initramfs. Any path in the list B<E<lt>executable-listE<gt>> that exists and is executable will be installed.
Expand Down

0 comments on commit c2a1bee

Please sign in to comment.