Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaydub/fc380581b548057b157b6d0a5e3014e5 to your computer and use it in GitHub Desktop.
Save jaydub/fc380581b548057b157b6d0a5e3014e5 to your computer and use it in GitHub Desktop.
How to unlock just one LUKS volume on a btrfs RAID1 inside LUKS include /boot installation
prepare_grub_to_access_device ()
{
# If GRUB_BTRFS_RAID1 is set, then this function output fragments of config
# for each device surrounded by "if $root = $device", so eg only
# one cryptmount command is run, only one path is searched for root etc
# However it skips going to the trouble for insmod commands, as they're
# already duplicated all over the place and are O(1) if the module is already
# loaded.
#
# We use grub-probe --target=compatibility_hint to translate the devices
# handed to the function via $@ into something that's reasonably likely to map
# to $root
old_ifs="$IFS"
IFS='
'
partmap="$("${grub_probe}" --device "$@" --target=partmap)"
for module in ${partmap} ; do
case "${module}" in
netbsd | openbsd)
echo "insmod part_bsd";;
*)
echo "insmod part_${module}";;
esac
done
loop_file=
case $1 in
/dev/loop/*|/dev/loop[0-9])
grub_loop_device="${1#/dev/}"
loop_file=$(losetup "$1" | sed -e "s/^[^(]*(\([^)]\+\)).*/\1/")
case $loop_file in
/dev/*) ;;
*)
loop_device="$1"
shift
set -- "$("${grub_probe}" --target=device "${loop_file}")" "$@" || return 0
;;
esac
;;
esac
# Abstraction modules aren't auto-loaded.
abstraction="$("${grub_probe}" --device "$@" --target=abstraction)"
for module in ${abstraction} ; do
echo "insmod ${module}"
done
fs="$("${grub_probe}" --device "$@" --target=fs)"
for module in ${fs} ; do
echo "insmod ${module}"
done
if [ "x$GRUB_ENABLE_CRYPTODISK" = xy ]; then
if [ "x$GRUB_BTRFS_RAID1" = xy ]; then
for local_device in "$@"; do
compat_device="$("${grub_probe}" --device "$local_device" --target=compatibility_hint)"
echo "if [ x\$root = x$compat_device ]; then";
for uuid in $("${grub_probe}" --device "$local_device" --target=cryptodisk_uuid); do
echo " cryptomount -u $uuid"
done
echo "fi"
done
else
for uuid in $("${grub_probe}" --device "$@" --target=cryptodisk_uuid); do
echo "cryptomount -u $uuid"
done
fi
fi
# If there's a filesystem UUID that GRUB is capable of identifying, use it;
# otherwise set root as per value in device.map; in the GRUB_BTRFS_RAID1 case,
# we just trust that the existing root is correct
if [ "x$GRUB_BTRFS_RAID1" != xy ]; then
fs_hint="$("${grub_probe}" --device "$@" --target=compatibility_hint)"
if [ "x$fs_hint" != x ]; then
echo "set root='$fs_hint'"
fi
fi
# btrfs raid1 file systems have the same UUID across multiple devices, so the
# $@ invocation is fine here
if [ "x${GRUB_DISABLE_UUID}" != "xtrue" ] && fs_uuid="$("${grub_probe}" --device "$@" --target=fs_uuid 2> /dev/null)" ; then
if [ "x$GRUB_BTRFS_RAID1" = xy ]; then
echo "if [ x\$feature_platform_search_hint = xy ]; then"
# The specific device cases
for local_device in "$@"; do
compat_device="$("${grub_probe}" --device "$local_device" --target=compatibility_hint)"
hints="$("${grub_probe}" --device "$local_device" --target=hints_string 2> /dev/null)" || hints=
if [ "x$hints" != x ]; then
some_hints=true
echo " if [ x\$root = x$compat_device ]; then"
echo " search --no-floppy --fs-uuid --set=root ${hints} ${fs_uuid}"
echo " fi"
fi
done
# The case where we got this far but actually had no hints
if [ "x$some_hints" != xtrue ]; then
echo " search --no-floppy --fs-uuid --set=root ${fs_uuid}"
fi
# The platform doesn't support hints case
echo "else"
echo " search --no-floppy --fs-uuid --set=root ${fs_uuid}"
echo "fi"
else
hints="$("${grub_probe}" --device "$@" --target=hints_string 2> /dev/null)" || hints=
if [ "x$hints" != x ]; then
echo "if [ x\$feature_platform_search_hint = xy ]; then"
echo " search --no-floppy --fs-uuid --set=root ${hints} ${fs_uuid}"
echo "else"
echo " search --no-floppy --fs-uuid --set=root ${fs_uuid}"
echo "fi"
else
echo "search --no-floppy --fs-uuid --set=root ${fs_uuid}"
fi
fi
fi
IFS="$old_ifs"
if [ "x${loop_file}" != x ]; then
loop_mountpoint="$(awk '"'${loop_file}'" ~ "^"$2 && $2 != "/" { print $2 }' /proc/mounts | tail -n1)"
if [ "x${loop_mountpoint}" != x ]; then
echo "loopback ${grub_loop_device} ${loop_file#$loop_mountpoint}"
echo "set root=(${grub_loop_device})"
fi
fi
}
#!/usr/bin/env bash
# Install your own grub.cfg files into various EFI volumes after grub-install
# runs
#
# Set up with:
#
# dpkg-divert --local --rename --divert /usr/sbin/grub-install.real --add /usr/sbin/grub-install
#
# Then install the script is /usr/sbin/grub-install
if test "$BASH" = "" || "$BASH" -uc "a=();true \"\${a[@]}\"" 2>/dev/null; then
# Bash 4.4, Zsh
set -euo pipefail
else
# Bash 4.3 and older chokes on empty arrays with set -u.
set -eo pipefail
fi
shopt -s nullglob globstar
require(){ hash "$@" || exit 127; }
require grub-install.real
CONF_BASE=/etc/grub-efi
TARGET=/boot
/usr/sbin/grub-install.real "$@"
while read -r src; do
target="${TARGET}${src#$CONF_BASE}"
if [ -e "$target" ]; then
cp "$src" "$target"
printf 'copying %s to %s\n' "$src" "$target"
fi
done < <(find "$CONF_BASE" -name grub.cfg)
@jaydub
Copy link
Author

jaydub commented Oct 16, 2022

I have a btrfs RAID1 inside LUKS installation across two drives — with /boot inside root inside LUKS. This works, but grub does not know that it only needs to unlock and search for boot on one drive to bootstrap it's way into working kernel, so even if you embed LUKS keys in your initramfs so you only need to type passwords into grub, you're going to have to unlock both LUKS volumes, and endure two rounds of grub's slow PDKDF2 implementation and it's generally painful UX.

These two gists try to hack around that problem on Debian-like systems. One part is an override of the prepare_grub_to_access_device function from grub-mkconfig_lib which, in the presence of export GRUB_BTRFS_RAID1=true in your /etc/default/grub, it will produce output that will cryptomount and search only on the volume that matches the root that grub sets based on which volume UEFI has booted into. You can either shove that directly into /etc/grub.d/10_linux as I did or replace the same function in an override of /usr/lib/grub/grub-mkconfig_lib so it's used everywhere in the construction of a /boot/grub/grub.cfg by update-grub.

The second part is a dirty hack that overrides grub-install with a wrapper that runs it with whatever arguments it's called with, then just replaces the EFI grub.cfg stubs with your own versions. This presumes you've mounted all your relevant EFI System Partitions under /boot somewhere (I named them all after their fat32 ID), and have matching-path grub.cfg files under /etc/grub-efi eg:

/etc/grub-efi/efi_9D28_D486/EFI/ubuntu/grub.cfg/boot/efi_9D28_D486/EFI/ubuntu/grub.cfg

(You could hack on these files directly, but grub-install will be invoked during the next package update and will clobber your changes if you don't handle it in some fashion.)

Obviously the world would be a lot better if:

  • grub was generally more mirrored RAID aware, as I suspect this is a problem for standard software RAID, too
  • grub-install used a similar system to update-grub to build EPS grub.cfg stubs
  • cryptomount used an SSE optimised SHA-2 suite so PBKDF2 performance matched that of cryptsetup
  • cryptomount was willing to ask again for a password
  • grub knew enough about the starting video resolution state and included multiple font sizes to pick a sensible one
  • etc etc forever

But I doubt we'll see much movement here until distros decide that encrypted /boot is important enough to throw resources at grub development to address these problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment