Created
September 24, 2024 17:08
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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