Skip to content

Instantly share code, notes, and snippets.

@arrjay
Created September 24, 2024 17:08
Show Gist options
  • Select an option

  • Save arrjay/7f4209337062fb4db577887c3ff84c6c to your computer and use it in GitHub Desktop.

Select an option

Save arrjay/7f4209337062fb4db577887c3ff84c6c to your computer and use it in GitHub Desktop.
dracut module to load ZFS keys from a LUKS volume (specified via a ZFS property)
#!/usr/bin/env bash
# internal to get if the zfs root has a luks property
get_zfs_luksholder () {
# we need to find the zfs dataset for root (if any)
# this is somewhat duplicated from zfsexpandknowledge, but we want
# abstract datasets, not devices.
local mp
local fstype
local _
local dataset
local numfields="$(awk '{print NF; exit}' /proc/self/mountinfo)"
if [[ "${numfields}" = "10" ]] ; then
fields="_ _ _ _ mp _ _ fstype dev _"
else
fields="_ _ _ _ mp _ _ _ fstype dev _"
fi
# shellcheck disable=SC2086
while read -r ${fields?} ; do
[ "$fstype" = "zfs" ] || continue
[ "$mp" = "/" ] && dataset="${dev}"
done < /proc/self/mountinfo
[[ "${dataset}" ]] || return 255
# see if we have a luksholder property on that
local luksholder="$(zfs get -H -o value io.github.arrjay:luksholder "${dataset}")"
[[ "${luksholder}" ]] || return 255
[[ "${luksholder}" == "-" ]] && return 255
printf '%s' "${luksholder}"
}
check () {
require_binaries mtype zfs cryptsetup "$systemdutildir"/systemd-cryptsetup
[[ $hostonly ]] || [[ $mount_needs ]] && {
case " ${host_fs_types[*]} " in
*" zfs_member "*) : ;;
*) return 255 ;;
esac
get_zfs_luksholder >/dev/null || return 255
}
return 0
}
# called by dracut
depends () {
local deps
echo "crypt zfs"
return 0
}
# called by dracut
installkernel () {
instmods dm_crypt
# pry the kernel modules out of the luks header regardless of the mount status
[[ $hostonly ]] && {
local holder="$(get_zfs_luksholder)"
local tdev="/dev/disk/by-uuid/${holder}"
[[ -e "${tdev}" ]] || return 0
local crypttype="$(cryptsetup luksDump "${tdev}"|awk '$1 ~ "cipher:" { split($2,o,"-") ; print o[1] ; }')"
[[ "${crypttype}" ]] && instmods "crypto-${crypttype}"
}
}
# called by dracut
install () {
[[ "${hostonly}" ]] || return 0
local holder="$(get_zfs_luksholder)"
[[ "${holder}" ]] || return 0
local escaped_holder="$(systemd-escape "luks-${holder}")"
# wire cryptsetup...
printf 'luks-%s UUID=%s none luks,nofail\n' "${holder}" "${holder}" >> "${initdir}/etc/crypttab"
# create new target to run *after* zfs-import.target
if dracut_module_included "systemd" ; then
inst_simple "${moddir}/zfs-luksunwrap.target" "${systemdsystemunitdir}/zfs-luksunwrap.target"
systemctl -q --root "${initdir}" add-wants initrd.target zfs-luksunwrap.target
# add cryptsetup as a wants to our new target in a kinda clunky way
mkdir -p "${initdir}/etc/systemd/system/systemd-cryptsetup@${escaped_holder}.service.d"
{
printf '[%s]\n' 'Unit'
printf 'WantedBy=%s\n' 'zfs-luksunwrap.target'
} > "${initdir}/etc/systemd/system/systemd-cryptsetup@${escaped_holder}.service.d/target.conf"
mkdir -p "${initdir}/etc/systemd/system/zfs-luksunwrap.target.wants"
# you can't do the below as it doesn't exist. but you can have drop-ins for units that are generated, as abpve...
#systemctl -q --root "${initdir}" add-wants zfs-luksunwrap.target "systemd-cryptsetup@${escaped_holder}"
fi
# all the stuff needed for mktemp, sed, tr, grep, mtype, mdir - which our hook will call.
local libtuple="$(dpkg-architecture -qDEB_BUILD_MULTIARCH)"
inst_binary mktemp
inst_binary sed
inst_binary tr
inst_binary grep
inst_binary mtype
inst_binary mdir
inst_library "/usr/lib/${libtuple}/gconv/IBM850.so"
inst_library "/usr/lib/${libtuple}/gconv/gconv-modules"
[[ -e "/usr/lib/${libtuple}/gconv/gconv-modules.d/gconv-modules-extra.conf" ]] && inst_library "/usr/lib/${libtuple}/gconv/gconv-modules.d/gconv-modules-extra.conf"
inst_hook pre-mount 89 "${moddir}/zfs-unwrap-luks.sh"
}
# probably unneeded now but I wanted to upload a version that got close to what I wanted first.
[Install]
WantedBy=zfs.target
[Unit]
Description=ZFS-LUKS unwrap target
After=zfs-import.target
Before=dracut-mount.service
#!/bin/sh
. /lib/dracut-zfs-lib.sh
# only activate if we're on zfs
# this should also set root to the dataset of note, I think?
decode_root_args || return 0
# see if we can get the prop off zfs now
get_zfsl_prop () {
zfs_luksholder="$(zfs get -H -o value io.github.arrjay:luksholder "${root}")"
[ "${zfs_luksholder}" ] || return 1
[ "${zfs_luksholder}" = "-" ] && return 1
zfs_luksholder_unit="systemd-cryptsetup@$(systemd-escape "luks-${zfs_luksholder}").service"
zfs_luksholder="/dev/mapper/luks-${zfs_luksholder}"
}
get_zfsl_prop || return 0
# spin until we complete cryptsetup for our unit - which we should know here..
systemctl start "${zfs_luksholder_unit}" || return 0
while ! systemctl is-active --quiet "${zfs_luksholder_unit}" ; do
# TODO: timeout?
sleep 0.1s
done
zfsl_unlock () {
# see if we have a blocky boy
[ -e "${zfs_luksholder}" ] || return 0
# get a list of the key files on the partition
zfslklist="$(mktemp)"
# deal with normalized key files (using dashes instead of /) and dataset components.
zfsnorname="$(echo "${root}" | sed -e 's@/@-@g')"
zfspooltarg="${root}"
# grab all the keyfiles - we are expeting one level with dashes instead of slashes (if nested)
mdir -f -b -i "${zfs_luksholder}" '*.key' | sed -e 's@::/@@g' -e '[email protected]$@@g' > "${zfslklist}"
# go looking, strip off datasets as we go up
zfsfoundkey=0
while [ "$(echo ${zfsnorname} | tr -cd '-')" != "" ] && [ "${zfsfoundkey}" -eq 0 ] ; do
grep -q "${zfsnorname}" "${zfslklist}" && { zfsfoundkey=1 ; break ; }
zfsnorname="${zfsnorname%-*}"
zfspooltarg="${zfspooltarg%/*}"
done
# we need to do the test one last time in case we exhausted the loop
[ "${zfsfoundkey}" -eq 0 ] && grep -q "${zfsnorname}" "${zfslklist}" && zfsfoundkey=1
# try to load a key if we got one
[ "${zfsfoundkey}" -eq 1 ] && mtype -i "${zfs_luksholder}" "::/${zfsnorname}.key" | zfs load-key "${zfspooltarg}"
}
zfsl_unlock
# unwind our crypt device while we're here but don't error
systemctl stop "${zfs_luksholder_unit}" || return 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment