Skip to content

Commit c2a1bee

Browse files
zdykstraahesford
andcommitted
Support early hooks, add example luks-unlock.sh
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 <[email protected]> Co-authored-by: Andrew J. Hesford <[email protected]>
1 parent 62f7315 commit c2a1bee

File tree

4 files changed

+115
-0
lines changed

4 files changed

+115
-0
lines changed

90zfsbootmenu/module-setup.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ install() {
159159
inst_hook cmdline 95 "${moddir}/zfsbootmenu-parse-commandline.sh" || _ret=$?
160160
inst_hook pre-mount 90 "${moddir}/zfsbootmenu-preinit.sh" || _ret=$?
161161

162+
# Install "early setup" hooks
163+
# shellcheck disable=SC2154
164+
if [ -n "${zfsbootmenu_early_setup}" ]; then
165+
for _exec in ${zfsbootmenu_early_setup}; do
166+
if [ -x "${_exec}" ]; then
167+
inst_simple "${_exec}" "/libexec/early-setup.d/$(basename "${_exec}")" || _ret=$?
168+
else
169+
dwarning "setup script (${_exec}) missing or not executable; cannot install"
170+
fi
171+
done
172+
fi
173+
162174
# Install "setup" hooks
163175
# shellcheck disable=SC2154
164176
if [ -n "${zfsbootmenu_setup}" ]; then

90zfsbootmenu/zfsbootmenu-init.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ elif [ ! -e /etc/hostid ]; then
7070
write_hostid "${default_hostid}"
7171
fi
7272

73+
# Run early setup hooks, if they exist
74+
if [ -d /libexec/early-setup.d ]; then
75+
tput clear
76+
for _hook in /libexec/early-setup.d/*; do
77+
zinfo "Processing hook: ${_hook}"
78+
[ -x "${_hook}" ] && "${_hook}"
79+
done
80+
unset _hook
81+
fi
82+
7383
# Prefer a specific pool when checking for a bootfs value
7484
# shellcheck disable=SC2154
7585
if [ "${root}" = "zfsbootmenu" ]; then

contrib/luks-unlock.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/bin/bash
2+
3+
## This early-setup hook finds a LUKS volume by looking for a partition with
4+
## label "KEYSTORE". (Partition labels are supported on GPT and a few obsolete
5+
## disklabel formats; see the "name" command in parted(8) for details.)
6+
##
7+
## If a KEYSTORE partition is found, the hook attempts repeatedly to unlock and
8+
## mount the encrypted volume (read-only) at /etc/zfs/keys. If successful, this
9+
## will allow ZFSBootMenu to automatically unlock any ZFS datasets that define
10+
## the ZFS property
11+
##
12+
## keylocation=file:///etc/zfs/keys
13+
##
14+
## If the partition cannot be found, does not appear to be a LUKS volume or has
15+
## already been activated, the hook will terminate and allow ZFSBootMenu to
16+
## proceed with its ordinary startup process. Once the hook begins the unlock
17+
## loop, it will not terminate until either the volume is successfully unlocked
18+
## or the user presses Ctrl-C to abandon the attempts. After every failed
19+
## unlock cycle, an emergency shell will be invoked to allow manual
20+
## intervention; type `exit` in the shell to continue the unlock loop.
21+
##
22+
## Because this script is intended to provide unlock keys *before* ZFSBootMenu
23+
## imports ZFS pools, it should be run as an early hook. To install, put this
24+
## script somewhere, make sure it is executable, and add the path to the
25+
## `zfsbootmenu_early_setup` space-separated list with, e.g.,
26+
##
27+
## zfsbootmenu_early_setup+=" <path to script> "
28+
##
29+
## in a dracut.conf(5) file inside the directory specified for the option
30+
## `Global.DracutConfDir` in the ZFSBootMenu `config.yaml`.
31+
32+
# shellcheck disable=SC1091
33+
[ -f /lib/zfsbootmenu-lib.sh ] && source /lib/zfsbootmenu-lib.sh
34+
35+
luks="/dev/disk/by-partlabel/KEYSTORE"
36+
dm="/dev/mapper/KEYSTORE"
37+
38+
if [ ! -b "${luks}" ] ; then
39+
zinfo "keystore device ${luks} does not exist"
40+
exit
41+
fi
42+
43+
if ! cryptsetup isLuks ${luks} >/dev/null 2>&1 ; then
44+
zwarn "keystore device ${luks} missing LUKS partition header"
45+
exit
46+
fi
47+
48+
if cryptsetup status "${dm}" >/dev/null 2>&1 ; then
49+
zinfo "${dm} already active, not continuing"
50+
exit
51+
fi
52+
53+
header="$( center_string "[CTRL-C] cancel luksOpen attempts" )"
54+
55+
while true; do
56+
tput clear
57+
colorize red "${header}\n\n"
58+
59+
cryptsetup --tries=5 luksOpen "${luks}" KEYSTORE
60+
ret=$?
61+
62+
# successfully entered a passphrase
63+
if [ "${ret}" -eq 0 ] ; then
64+
mkdir -p /etc/zfs/keys
65+
mount -r "${dm}" /etc/zfs/keys
66+
zdebug "$(
67+
cryptsetup status "${dm}"
68+
mount | grep KEYSTORE
69+
)"
70+
exit
71+
fi
72+
73+
# ctrl-c'd the process
74+
if [ "${ret}" -eq 1 ] ; then
75+
zdebug "canceled luksOpen attempts via SIGINT"
76+
exit
77+
fi
78+
79+
# failed all password attempts
80+
if [ "${ret}" -eq 2 ] ; then
81+
if prompt="Continuing in %0.2d seconds" timed_prompt "[RETURN] continue unlock attempts" "[ESCAPE] emergency shell" "" ; then
82+
continue
83+
else
84+
emergency_shell "Unable to unlock LUKS partition"
85+
fi
86+
fi
87+
done

pod/zfsbootmenu.7.pod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ In addition to standard dracut configuration options, the ZFSBootMenu dracut mod
184184

185185
=over 4
186186

187+
=item B<zfsbootmenu_early_setup=E<lt>executable-listE<gt>>
188+
189+
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.
190+
191+
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.
192+
187193
=item B<zfsbootmenu_setup=E<lt>executable-listE<gt>>
188194

189195
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.

0 commit comments

Comments
 (0)