Browse Source

Merge pull request #439 from kacf/grub.d-2

MEN-5255 test
change-dependabot-prefix
Kristian Amlie 3 years ago
committed by GitHub
parent
commit
5d9566c5f1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .gitlab-ci.yml
  2. 4
      Dockerfile
  3. 7
      configs/debian-qemux86-64_config
  4. 13
      configs/mender_grub_config
  5. 7
      configs/ubuntu-qemux86-64_config
  6. 18
      mender-convert-modify
  7. 99
      modules/grub.sh
  8. 29
      modules/probe.sh
  9. 169
      scripts/test/generate-image.sh
  10. 5
      scripts/test/mender-convert-qemu
  11. 25
      scripts/test/run-tests.sh
  12. 9
      scripts/test/test-utils.sh
  13. 4
      tests/conftest.py
  14. 2
      tests/mender-image-tests
  15. 199
      tests/test_grub_integration.py
  16. BIN
      tests/uefi-nvram/MicCorThiParMarRoo_2010-10-05.crt
  17. BIN
      tests/uefi-nvram/OVMF_VARS.fd
  18. 71
      tests/uefi-nvram/README.md

4
.gitlab-ci.yml

@ -223,12 +223,12 @@ test_acceptance_prebuilt_raspberrypi4:
test_acceptance_qemux86_64:
<<: *test_acceptance
script:
- ./scripts/test/run-tests.sh --config versions_override_config --only qemux86_64
- ./scripts/test/run-tests.sh --config versions_override_config --only ubuntu-qemux86-64
test_acceptance_debian_qemux86_64:
<<: *test_acceptance
script:
- ./scripts/test/run-tests.sh --config versions_override_config --only debian-qemux86_64
- ./scripts/test/run-tests.sh --config versions_override_config --only debian-qemux86-64
test_acceptance_raspberrypi:
<<: *test_acceptance

4
Dockerfile

@ -53,7 +53,9 @@ RUN apt-get update && env DEBIAN_FRONTEND=noninteractive apt-get install -y \
# manipulate binary and hex
xxd \
# JSON power tool
jq
jq \
# GRUB command line tools, primarily grub-probe
grub-common
COPY --from=build /root/pxz/pxz /usr/bin/pxz

7
configs/debian-qemux86-64_config

@ -2,8 +2,7 @@
#
# The image is generated with the following command:
#
# mkosi --root-size=2G --distribution=debian --release=bullseye --format=gpt_ext4 --bootable --checksum
# --password password --package=openssh-server,dhcpcd5 --output=Debian-11-x86-64.img build
# mender-convert/scripts/test/generate-image.sh debian
#
# Then manually uploaded to Mender's AWS S3 bucket for CI testing
#
@ -13,10 +12,10 @@
#
# and qemu is executed with the following command:
#
# qemu-system-x86_64 -enable-kvm -m 512 -smp 2 -bios /usr/share/OVMF/OVMF_CODE.fd -drive format=raw,file=deploy/Debian-11-x86-64-qemux86_64-mender.img
# qemu-system-x86_64 -enable-kvm -m 512 -smp 2 -bios /usr/share/OVMF/OVMF_CODE.fd -drive format=raw,file=deploy/Debian-11-x86-64-qemux86-64-mender.img
MENDER_STORAGE_DEVICE_BASE=/dev/sda
MENDER_DEVICE_TYPE="qemux86_64"
MENDER_DEVICE_TYPE="qemux86-64"
# Nothing to copy
MENDER_COPY_BOOT_GAP="n"

13
configs/mender_grub_config

@ -1,8 +1,17 @@
# Use U-boot -> GRUB -> EFI boot-loader integration
# Use UEFI -> GRUB boot-loader integration
#
# On ARM, U-boot is used as UEFI (EFI) provider
#
# Only disable this if you know what you are doing
MENDER_GRUB_EFI_INTEGRATION=y
# Integrate with /etc/grub.d boot scripts.
#
# This has no effect unless MENDER_GRUB_EFI_INTEGRATION is enabled. Default is
# auto, in which case it will be detected. This is only supported for x86_64
# platforms at the moment.
MENDER_GRUB_D_INTEGRATION=auto
# Version of GRUB to use. Note that there needs to be a precompiled version
# available at the MENDER_STORAGE_URL download source.
GRUB_VERSION=2.04
@ -17,7 +26,7 @@ GRUB_VERSION=2.04
MENDER_GRUB_KERNEL_BOOT_ARGS=""
# grub-mender-grubenv is the Mender integration for the GRUB bootloader
MENDER_GRUBENV_VERSION="1a7db967495bbe8be53b7a69dcb42822f39d9a74"
MENDER_GRUBENV_VERSION="b06a8e2cf13776b5cfc896fa8068006dd9992ebb"
MENDER_GRUBENV_URL="${MENDER_GITHUB_ORG}/grub-mender-grubenv/archive/${MENDER_GRUBENV_VERSION}.tar.gz"
# Name of the storage device containing root filesystem partitions in GRUB

7
configs/qemux86-64_config → configs/ubuntu-qemux86-64_config

@ -2,8 +2,7 @@
#
# The image is generated with the following command:
#
# mkosi --root-size=2G --distribution=ubuntu --release=focal --format=gpt_ext4 --bootable --checksum
# --password password --package=openssh-server,dhcpcd5 --output=Ubuntu-Focal-x86-64.img build
# mender-convert/scripts/test/generate-image.sh ubuntu
#
# Then manually uploaded to Mender's AWS S3 bucket for CI testing
#
@ -13,10 +12,10 @@
#
# and qemu is executed with the following command:
#
# qemu-system-x86_64 -enable-kvm -m 512 -smp 2 -bios /usr/share/OVMF/OVMF_CODE.fd -drive format=raw,file=deploy/Ubuntu-Focal-x86-64-qemux86_64-mender.img
# qemu-system-x86_64 -enable-kvm -m 512 -smp 2 -bios /usr/share/OVMF/OVMF_CODE.fd -drive format=raw,file=deploy/Ubuntu-Focal-x86-64-qemux86-64-mender.img
MENDER_STORAGE_DEVICE_BASE=/dev/sda
MENDER_DEVICE_TYPE="qemux86_64"
MENDER_DEVICE_TYPE="qemux86-64"
# Nothing to copy
MENDER_COPY_BOOT_GAP="n"

18
mender-convert-modify

@ -140,14 +140,26 @@ if [ "${MENDER_GRUB_EFI_INTEGRATION}" == "y" ]; then
# Check for known U-Boot problems in all files on the boot partition.
check_for_broken_uboot_uefi_support work/boot
if has_secureboot_shim "work/boot"; then
if has_grub_efi "work/boot"; then
# No need to install Grub, use the one already present, and only install
# our grub.cfg
grub_install_with_shim_present
# our tools.
log_info "GRUB EFI bootloader already present, not installing one."
grub_install_grub_editenv_binary
else
log_info "Installing GRUB EFI bootloader..."
grub_install_mender_grub
fi
if [ "$MENDER_GRUB_D_INTEGRATION" = y ] || ( [ "$MENDER_GRUB_D_INTEGRATION" = auto ] && supports_grub_d "work/rootfs" ); then
log_info "Generating grub config using update-grub..."
grub_create_grub_config
grub_install_grub_d_config
else
log_info "Generating the mender-grub config..."
grub_create_grub_config
grub_install_standalone_grub_config
fi
fi
run_and_log_cmd "sudo mkdir -p work/rootfs/data/mender"

99
modules/grub.sh

@ -45,7 +45,12 @@ EOF
mender_kernel_root_base=${MENDER_STORAGE_DEVICE_BASE}
EOF
fi
}
# grub_install_standalone_grub_config
#
#
function grub_install_standalone_grub_config() {
if [ -n "${MENDER_GRUB_KERNEL_BOOT_ARGS}" ]; then
cat <<- EOF > work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}/11_bootargs_grub.cfg
set bootargs="${MENDER_GRUB_KERNEL_BOOT_ARGS}"
@ -55,10 +60,83 @@ EOF
(
cd work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}
run_and_log_cmd "make 2>&1"
run_and_log_cmd "sudo make DESTDIR=../ BOOT_DIR=boot install-boot-files"
run_and_log_cmd "sudo make DESTDIR=../rootfs install-tools"
run_and_log_cmd "sudo make DESTDIR=$PWD/../ BOOT_DIR=boot install-standalone-boot-files"
run_and_log_cmd "sudo make DESTDIR=$PWD/../rootfs install-tools"
)
}
# grub_install_grub_d_config
#
#
function grub_install_grub_d_config() {
if [ -n "${MENDER_GRUB_KERNEL_BOOT_ARGS}" ]; then
log_warn "MENDER_GRUB_KERNEL_BOOT_ARGS is ignored when MENDER_GRUB_D_INTEGRATION is enabled. Set it in the GRUB configuration instead."
fi
# When using grub.d integration, /boot/efi must point to the boot partition,
# and /boot/grub must point to grub-mender-grubenv on the boot partition.
if [ ! -d work/rootfs/boot/efi ]; then
run_and_log_cmd "sudo mkdir work/rootfs/boot/efi"
fi
run_and_log_cmd "sudo mkdir work/boot/grub-mender-grubenv"
run_and_log_cmd "sudo mv work/rootfs/boot/grub/* work/boot/grub-mender-grubenv/"
run_and_log_cmd "sudo rmdir work/rootfs/boot/grub"
run_and_log_cmd "sudo ln -s efi/grub-mender-grubenv work/rootfs/boot/grub"
(
cd work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}
run_and_log_cmd "make 2>&1"
run_and_log_cmd "sudo make DESTDIR=$PWD/../ BOOT_DIR=boot install-boot-env"
run_and_log_cmd "sudo make DESTDIR=$PWD/../rootfs install-grub.d-boot-scripts"
run_and_log_cmd "sudo make DESTDIR=$PWD/../rootfs install-tools"
# We need this for running the scripts once.
run_and_log_cmd "sudo make DESTDIR=$PWD/../rootfs install-offline-files"
)
# Mender-convert usually runs in a container. It's difficult to launch
# additional containers from within an existing one, but we need to run
# `update-grub` on a simulated device using some sort of container. Use good
# old `chroot`, which doesn't provide perfect containment, but it is good
# enough for our purposes, and doesn't require special containment
# capabilities. This will not work for foreign architectures, but we could
# probably use something like qemu-aarch64-static to get around that.
run_and_log_cmd "sudo mount work/boot work/rootfs/boot/efi -o bind"
run_and_log_cmd "sudo mount /dev work/rootfs/dev -o bind,ro"
run_and_log_cmd "sudo mount /proc work/rootfs/proc -o bind,ro"
run_and_log_cmd "sudo mount /sys work/rootfs/sys -o bind,ro"
local ret=0
# Use `--no-nvram`, since we cannot update firmware memory in an offline
# build. Instead, use `--removable`, which creates entries that automate
# booting if you put the image into a new device, which you almost certainly
# will after using mender-convert.
run_and_log_cmd_noexit "sudo chroot work/rootfs grub-install --removable --no-nvram" || ret=$?
if [ $ret -eq 0 ]; then
run_and_log_cmd_noexit "sudo chroot work/rootfs grub-install --no-nvram" || ret=$?
fi
if [ $ret -eq 0 ]; then
run_and_log_cmd_noexit "sudo chroot work/rootfs update-grub" || ret=$?
fi
# Very important that these are unmounted, otherwise Docker may start to
# remove files inside them while tearing down the container. You can guess
# how I found that out... We run without the logger because otherwise the
# message from the previous command, which is the important one, is lost.
sudo umount -l work/rootfs/boot/efi || true
sudo umount -l work/rootfs/dev || true
sudo umount -l work/rootfs/proc || true
sudo umount -l work/rootfs/sys || true
[ $ret -ne 0 ] && exit $ret
(
cd work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}
# Should be removed after running.
run_and_log_cmd "sudo make DESTDIR=$PWD/../rootfs uninstall-offline-files"
)
}
# grub_install_grub_editenv_binary
@ -74,18 +152,6 @@ function grub_install_grub_editenv_binary() {
}
# grub_install_with_shim_present
#
# Keep the existing boot shim, and bootloader, and only install the mender-grub
# config
function grub_install_with_shim_present() {
grub_create_grub_config
grub_install_grub_editenv_binary
}
# grub_install_mender_grub
#
# Install mender-grub on the converted boot partition
@ -98,9 +164,6 @@ function grub_install_mender_grub() {
run_and_log_cmd "sudo ln -s ${initrd_imagetype} work/rootfs/boot/initrd"
fi
log_info "Generating the mender-grub config..."
grub_create_grub_config
# Remove conflicting boot files. These files do not necessarily effect the
# functionality, but lets get rid of them to avoid confusion.
#
@ -110,7 +173,7 @@ function grub_install_mender_grub() {
sudo rm -rf work/boot/EFI/systemd
sudo rm -rf work/boot/NvVars
for empty_dir in $(
cd work/boot && find . -maxdepth 1 -type d -empty
cd work/boot && find . -maxdepth 1 -type d -empty -not -name .
); do
sudo rmdir work/boot/$empty_dir
done

29
modules/probe.sh

@ -38,7 +38,7 @@ probe_arch() {
target_arch="unknown"
if grep -q x86-64 <<< "${file_info}"; then
target_arch="x86-64"
target_arch="x86_64"
elif grep -Eq "ELF 32-bit.*ARM" <<< "${file_info}"; then
target_arch="arm"
elif grep -Eq "ELF 64-bit.*aarch64" <<< "${file_info}"; then
@ -57,7 +57,7 @@ probe_grub_efi_name() {
local efi_name=""
local -r arch=$(probe_arch)
case "${arch}" in
"x86-64")
"x86_64")
efi_name="grub-efi-bootx64.efi"
;;
"arm")
@ -81,7 +81,7 @@ probe_debian_arch_name() {
deb_arch=""
arch=$(probe_arch)
case "${arch}" in
"x86-64")
"x86_64")
deb_arch="amd64"
;;
"arm")
@ -130,7 +130,7 @@ probe_grub_efi_target_name() {
local efi_target_name=""
local -r arch=$(probe_arch)
case "$arch" in
"x86-64")
"x86_64")
efi_target_name="bootx64.efi"
;;
"arm")
@ -345,11 +345,22 @@ is_efi_compatible_kernel() {
return 0
}
# has_secureboot_shim
# has_grub_efi
#
# $1 - the boot partition to search for a secureboot shim
# $1 - the boot partition to search for a grub*.efi
#
# Checks the EFI/* filesystem for the presence of a signed boot shim
has_secureboot_shim() {
find "${1}" -type f -name 'shim*.efi' -print0 | grep -qz shim
# Checks the EFI/* filesystem for the presence of a GRUB bootloader
has_grub_efi() {
find "${1}" -type f -name 'grub*.efi' -print0 | grep -qz grub
}
supports_grub_d() {
test -d "$1"/etc/grub.d || return 1
# Because we are executing programs inside a chroot in the image, we cannot
# currently convert non-native architectures to use grub.d integration. See
# relevant section on chroot inside grub_install_grub_d_config.
[ "$(probe_arch)" = "$(uname -m)" ] || return 1
return 0
}

169
scripts/test/generate-image.sh

@ -0,0 +1,169 @@
#!/bin/bash
set -e
usage() {
cat 1>&2 <<EOF
Please run with either "debian" or "ubuntu" as argument.
EOF
}
if [ "$UID" -ne 0 ]; then
echo "We'll need root for this..." 1>&2
exec sudo "$0" "$@"
fi
while [ -n "$1" ]; do
case "$1" in
"ubuntu")
GENERATE_VARIANT=generate_ubuntu
;;
"debian")
GENERATE_VARIANT=generate_debian
;;
*)
usage
exit 1
;;
esac
shift
done
cleanup_losetup() {
set +e
for dev in ${LO_DEVICE}p*; do
umount $dev
done
losetup -d $LO_DEVICE
rmdir tmp-p1
rmdir tmp-p2
}
generate_debian() {
local -r image="Debian-11-x86-64.img"
mkosi --root-size=2G --distribution=debian --release=bullseye --format=gpt_ext4 --bootable --checksum \
--password password --package=openssh-server,dhcpcd5 --package grub-efi-amd64-signed \
--package shim-signed --package lsb-release --output="$image" build
post_process_image "$image"
echo "Image successfully generated!" 1>&2
}
generate_ubuntu() {
local -r image="Ubuntu-Focal-x86-64.img"
mkosi --root-size=2G --distribution=ubuntu --release=focal --format=gpt_ext4 --bootable --checksum \
--password password --package=openssh-server,dhcpcd5 --package grub-efi-amd64-signed \
--package shim-signed --package lsb-release --output="$image" build
post_process_image "$image"
echo "Image successfully generated!" 1>&2
}
post_process_image() {
local -r image="$1"
mkdir -p tmp-p1
mkdir -p tmp-p2
LO_DEVICE=$(losetup --find --show --partscan "$image")
trap cleanup_losetup EXIT
mount ${LO_DEVICE}p1 tmp-p1
mount ${LO_DEVICE}p2 tmp-p2
pre_tweaks tmp-p1 tmp-p2
create_grub_regeneration_service tmp-p1 tmp-p2
umount tmp-p1
umount tmp-p2
regenerate_grub_live "$image"
mount ${LO_DEVICE}p1 tmp-p1
mount ${LO_DEVICE}p2 tmp-p2
post_tweaks tmp-p1 tmp-p2
}
pre_tweaks() {
local -r boot="$1"
local -r root="$2"
# Fstab is missing for some reason. I'm not exactly sure why systemd-boot
# works without this, and GRUB doesn't.
cat > "$root/etc/fstab" <<EOF
/dev/root / auto defaults 0 0
/dev/sda1 /boot/efi auto defaults 0 0
EOF
# Real installers create a /etc/default/grub file with a distributor in
# them.
if [ ! -e "$root/etc/default/grub" ]; then
mkdir -p $root/etc/default
echo 'GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null`' > $root/etc/default/grub
fi
sed -E -i -e 's/^#? *PermitRootLogin .*/PermitRootLogin yes/' $root/etc/ssh/sshd_config
}
post_tweaks() {
local -r boot="$1"
local -r root="$2"
# Delete systemd-boot, which isn't normally present in images that were
# installed with OS installers, at least not at the time of writing.
rm -rf "$boot/EFI/systemd"
# Also replace bootx64.efi, which is the default bootloader. Mkosi installs
# systemd-bootx86.efi, but we want the shim.
rm -f "$boot/EFI/BOOT/*"
mkdir -p "$boot/EFI/BOOT"
cp "$root/usr/lib/shim/shimx64.efi.signed" "$boot/EFI/BOOT/BOOTX64.EFI"
}
# Unfortunately installing grub scripts is something which is not really
# possible when offline. This is something which is easier with systemd-boot, so
# longterm GRUB will probably follow, or systemd-boot will take over. Anyway,
# let's do it by using a systemd service to perform the job, and then shut down.
create_grub_regeneration_service() {
local -r boot="$1"
local -r root="$2"
cat > "$root/etc/systemd/system/mender-regenerate-grub-and-shutdown.service" <<EOF
[Unit]
Description=Regenerate grub scripts, disable itself and then shut down.
[Service]
Type=oneshot
ExecStart=sh -c "grub-install && update-grub && systemctl disable mender-regenerate-grub-and-shutdown.service && poweroff"
EOF
ln -sf "$root/etc/systemd/system/mender-regenerate-grub-and-shutdown.service" "$root/etc/systemd/system/multi-user.target.wants/"
}
regenerate_grub_live() {
local -r image="$1"
local -r nvvars=$(mktemp)
dd if=/dev/zero of="$nvvars" bs=1M count=1
local ret=0
for maybe_kvm in -enable-kvm ""; do
ret=0
echo "Generating GRUB boot files live..." 1>&2
qemu-system-x86_64 \
$maybe_kvm \
-drive file="$image",if=ide,format=raw \
-drive file=/usr/share/OVMF/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on \
-drive file="$nvvars",if=pflash,format=raw,unit=1 \
-display vnc=:23 \
-m 512 \
|| ret=$?
[ $ret -eq 0 ] && break
done
return $ret
}
$GENERATE_VARIANT

5
scripts/test/mender-convert-qemu

@ -17,10 +17,11 @@ fi
qemu-system-x86_64 \
-enable-kvm \
-nographic \
-m 256 \
-m 512 \
-net user,hostfwd=tcp::8822-:22 \
-net nic,macaddr=52:54:00$(od -txC -An -N3 /dev/urandom|tr \ :) \
-bios ${ovmf_file} \
-drive file=${ovmf_file},if=pflash,format=raw,unit=0,readonly=on \
-drive file=./uefi-nvram/OVMF_VARS.fd,if=pflash,format=raw,unit=1,readonly=on \
-drive format=raw,file=${DISK_IMG} &
qemu_pid=$!

25
scripts/test/run-tests.sh

@ -83,18 +83,17 @@ test_result=0
if [ -n "$PREBUILT_IMAGE" ]; then
run_tests $PREBUILT_IMAGE \
"-k" "'not test_update'" \
|| test_result=$?
exit $test_result
else
if [ "$TEST_ALL" == "1" -o "$TEST_PLATFORM" == "qemux86_64" ]; then
if [ "$TEST_ALL" == "1" -o "$TEST_PLATFORM" == "ubuntu-qemux86-64" ]; then
wget --progress=dot:giga -N ${UBUNTU_IMAGE_URL} -P input/
convert_and_test "qemux86_64" \
convert_and_test "qemux86-64" \
"release-1" \
"input/Ubuntu-Focal-x86-64.img.gz" \
"--overlay tests/ssh-public-key-overlay" \
"--config configs/qemux86-64_config $EXTRA_CONFIG" \
"--config configs/ubuntu-qemux86-64_config $EXTRA_CONFIG" \
|| test_result=$?
echo >&2 "----------------------------------------"
@ -104,10 +103,10 @@ else
gunzip --force "input/Ubuntu-Focal-x86-64.img.gz"
run_convert "release-2" \
"input/Ubuntu-Focal-x86-64.img" \
"--config configs/qemux86-64_config $EXTRA_CONFIG" || test_result=$?
"--config configs/ubuntu-qemux86-64_config $EXTRA_CONFIG" || test_result=$?
ret=0
test -f deploy/Ubuntu-Focal-x86-64-qemux86_64-mender.img || ret=$?
assert "${ret}" "0" "Expected uncompressed file deploy/Ubuntu-Focal-x86-64-qemux86_64-mender.img"
test -f deploy/Ubuntu-Focal-x86-64-qemux86-64-mender.img || ret=$?
assert "${ret}" "0" "Expected uncompressed file deploy/Ubuntu-Focal-x86-64-qemux86-64-mender.img"
fi
if [ "$TEST_ALL" == "1" -o "$TEST_PLATFORM" == "raspberrypi3" ]; then
@ -117,8 +116,6 @@ else
"release-1" \
"input/${RASPBIAN_IMAGE}" \
"--config configs/raspberrypi3_config $EXTRA_CONFIG" \
-- \
"-k" "'not test_update'" \
|| test_result=$?
fi
@ -132,8 +129,6 @@ else
"release-1" \
"input/${BBB_DEBIAN_SDCARD_IMAGE_UNCOMPRESSED}" \
"--config configs/beaglebone_black_debian_sdcard_config $EXTRA_CONFIG" \
-- \
"-k" "'not test_update'" \
|| test_result=$?
rm -rf deploy
@ -145,8 +140,6 @@ else
"release-1" \
"input/${BBB_DEBIAN_EMMC_IMAGE_UNCOMPRESSED}" \
"--config configs/beaglebone_black_debian_emmc_config $EXTRA_CONFIG" \
-- \
"-k" "'not test_update'" \
|| test_result=$?
fi
@ -157,14 +150,12 @@ else
"release-1" \
"input/${UBUNTU_SERVER_RPI_IMAGE_COMPRESSED}" \
"--config configs/raspberrypi3_config $EXTRA_CONFIG" \
-- \
"-k" "'not test_update'" \
|| test_result=$?
fi
if [ "$TEST_ALL" == "1" -o "$TEST_PLATFORM" == "debian-qemux86_64" ]; then
if [ "$TEST_ALL" == "1" -o "$TEST_PLATFORM" == "debian-qemux86-64" ]; then
wget --progress=dot:giga -N ${DEBIAN_IMAGE_URL} -P input/
convert_and_test "qemux86_64" \
convert_and_test "qemux86-64" \
"release-1" \
"input/Debian-11-x86-64.img.gz" \
"--overlay tests/ssh-public-key-overlay" \

9
scripts/test/test-utils.sh

@ -140,7 +140,6 @@ run_tests() {
--sdimg-location="${MENDER_CONVERT_DIR}/deploy" \
--ssh-priv-key="./ssh-priv-key/key" \
--qemu-wrapper="../scripts/test/mender-convert-qemu" \
mender-image-tests \
${pytest_extra_args}
exitcode=$?
@ -163,15 +162,15 @@ prepare_ssh_keys() {
sudo chown -R root:root tests/ssh-public-key-overlay/root
fi
if [ "$(stat -c %a tests/ssh-public-key-overlay/root)" != "755" ]; then
chmod 755 tests/ssh-public-key-overlay/root
sudo chmod 755 tests/ssh-public-key-overlay/root
fi
if [ "$(stat -c %a tests/ssh-public-key-overlay/root/.ssh)" != "755" ]; then
chmod 700 tests/ssh-public-key-overlay/root/.ssh
sudo chmod 700 tests/ssh-public-key-overlay/root/.ssh
fi
if [ "$(stat -c %a tests/ssh-public-key-overlay/root/.ssh/authorized_keys)" != "755" ]; then
chmod 600 tests/ssh-public-key-overlay/root/.ssh/authorized_keys
sudo chmod 600 tests/ssh-public-key-overlay/root/.ssh/authorized_keys
fi
if [ "$(stat -c %a tests/ssh-priv-key/key)" != "600" ]; then
chmod 600 tests/ssh-priv-key/key
sudo chmod 600 tests/ssh-priv-key/key
fi
}

4
tests/conftest.py

@ -1,4 +1,4 @@
# Copyright 2021 Northern.tech AS
# Copyright 2022 Northern.tech AS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -22,3 +22,5 @@ sys.path.append(
# Load the parser for our custom option flags
pytest_plugins = "utils.parseropts.parseropts"
from utils.fixtures import *

2
tests/mender-image-tests

@ -1 +1 @@
Subproject commit ee1266afe8ec58efce9b4223e2489ac023b0582f
Subproject commit 7c081c042f0024e87e9e15144b18d991fb378bcd

199
tests/test_grub_integration.py

@ -0,0 +1,199 @@
#!/usr/bin/python
# Copyright 2022 Northern.tech AS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
import re
import os
import subprocess
from utils.common import (
extract_partition,
get_no_sftp,
)
@pytest.fixture(scope="function")
def cleanup_boot_scripts(request, connection):
"""Take a backup of the various grub.cfg files and restore them after the
test. This is recommended for tests that call `grub-install` and/or
`update-grub`, so that other tests can also run them from a pristine
state."""
connection.run(
"cp $(find /boot/efi/EFI/ -name grub.cfg -not -path '*/EFI/BOOT/*') /data/grub-efi.cfg"
)
connection.run("cp /boot/grub/grub.cfg /data/grub-main.cfg")
connection.run("cp /boot/grub-mender-grubenv.cfg /data/grub-mender-grubenv.cfg")
def cleanup():
connection.run(
"mv /data/grub-efi.cfg $(find /boot/efi/EFI/ -name grub.cfg -not -path '*/EFI/BOOT/*')"
)
connection.run("mv /data/grub-main.cfg /boot/grub/grub.cfg")
connection.run("mv /data/grub-mender-grubenv.cfg /boot/grub-mender-grubenv.cfg")
request.addfinalizer(cleanup)
def check_all_root_occurrences_valid(grub_cfg):
found_expected = False
inside_10_header = False
# One of the functions we define and use.
expected = "mender_check_and_restore_env"
with open(grub_cfg) as fd:
lineno = 0
for line in fd.readlines():
lineno += 1
if re.match(r'^\s*root="\$\{mender_grub_storage_device\}', line):
continue
if line.strip() == "### BEGIN /etc/grub.d/00_header ###":
# We allow root references inside the 00_header, because they
# are overriden by Mender later.
inside_10_header = True
elif line.strip() == "### END /etc/grub.d/00_header ###":
inside_10_header = False
if not inside_10_header and re.match(r"^\s*(set +)?root=", line):
pytest.fail(
"Found unexpected occurrence of `root=` in grub boot script\n"
"%d:%s" % (lineno, line)
)
if line.find(expected) >= 0:
found_expected = True
assert found_expected, "Expected content (%s) not found" % expected
@pytest.mark.usefixtures("setup_board", "cleanup_boot_scripts")
class TestGrubIntegration:
@pytest.mark.min_mender_version("1.0.0")
def test_no_root_occurrences(self, connection, latest_part_image):
"""Test that the generated grub scripts do not contain any occurrences of
`root=<something>` except for known instances that we control. This is
important because Mender needs to keep tight control of when this
variable is set, in order to boot from, and mount, the correct root
partition."""
# First, check that the offline generated scripts don't have any.
extract_partition(latest_part_image, 1)
try:
subprocess.check_call(
["mcopy", "-i", "img1.fs", "::/grub-mender-grubenv/grub.cfg", "."]
)
check_all_root_occurrences_valid("grub.cfg")
finally:
os.remove("img1.fs")
os.remove("grub.cfg")
extract_partition(latest_part_image, 2)
try:
subprocess.check_call(
[
"debugfs",
"-R",
"dump -p /boot/grub-mender-grubenv.cfg grub-mender-grubenv.cfg",
"img2.fs",
]
)
check_all_root_occurrences_valid("grub-mender-grubenv.cfg")
finally:
os.remove("img2.fs")
os.remove("grub-mender-grubenv.cfg")
# Then, check that the runtime generated scripts don't have any.
get_no_sftp("/boot/grub/grub.cfg", connection)
try:
check_all_root_occurrences_valid("grub.cfg")
finally:
os.remove("grub.cfg")
get_no_sftp("/boot/grub-mender-grubenv.cfg", connection)
try:
check_all_root_occurrences_valid("grub-mender-grubenv.cfg")
finally:
os.remove("grub-mender-grubenv.cfg")
# Check again after running `update-grub`.
connection.run("grub-install && update-grub")
get_no_sftp("/boot/grub/grub.cfg", connection)
try:
check_all_root_occurrences_valid("grub.cfg")
finally:
os.remove("grub.cfg")
get_no_sftp("/boot/grub-mender-grubenv.cfg", connection)
try:
check_all_root_occurrences_valid("grub-mender-grubenv.cfg")
finally:
os.remove("grub-mender-grubenv.cfg")
@pytest.mark.min_mender_version("1.0.0")
def test_offline_and_runtime_boot_scripts_identical(self, connection):
# Update scripts at runtime.
connection.run("grub-install && update-grub")
# Take advantage of the copies already made by cleanup_boot_scripts
# fixture above, and use the copies in /data.
# Take into account some known, but harmless differences. The "hd0,gpt1"
# style location is missing from the offline generated grub-efi.cfg
# file, but it is harmless because the filesystem UUID is being used
# instead.
connection.run(
r"sed -Ee 's/ *hd[0-9]+,gpt[0-9]+//' "
"$(find /boot/efi/EFI/ -name grub.cfg -not -path '*/EFI/BOOT/*') "
"> /data/new-grub-efi-modified.cfg"
)
try:
connection.run("diff -u /data/grub-efi.cfg /data/new-grub-efi-modified.cfg")
finally:
connection.run("rm -f /data/new-grub-efi-modified.cfg")
# Another few differences we work around in the main grub files:
# * `--hint` parameters are not generated in offline copy.
# * `root` variable is not set in offline copy.
# * `fwsetup` is added somewhat randomly depending on availability both
# on build host and device.
try:
connection.run("cp /data/grub-main.cfg /data/old-grub-modified.cfg")
connection.run("cp /boot/grub/grub.cfg /data/new-grub-modified.cfg")
connection.run(
r"sed -i -En -e '/\bsearch\b/{s/ --hint[^ ]*//g;}' "
"-e \"/^set root='hd0,gpt1'$/d\" "
r"-e '\,### BEGIN /etc/grub.d/30_uefi-firmware ###,{p; n; :loop; \,### END /etc/grub.d/30_uefi-firmware ###,b end; n; b loop; :end;}' "
"-e p "
"/data/old-grub-modified.cfg /data/new-grub-modified.cfg"
)
connection.run("diff -u /data/old-grub-modified.cfg /data/new-grub-modified.cfg")
finally:
connection.run("rm -f /data/old-grub-modified.cfg /data/new-grub-modified.cfg")
# Same differences as in previous check.
try:
connection.run("cp /data/grub-mender-grubenv.cfg /data/old-grub-mender-grubenv-modified.cfg")
connection.run("cp /boot/grub-mender-grubenv.cfg /data/new-grub-mender-grubenv-modified.cfg")
connection.run(
r"sed -i -En -e '/\bsearch\b/{s/ --hint[^ ]*//g;}' "
"-e \"/^set root='hd0,gpt1'$/d\" "
r"-e '\,### BEGIN /etc/grub.d/30_uefi-firmware ###,{p; n; :loop; \,### END /etc/grub.d/30_uefi-firmware ###,b end; n; b loop; :end;}' "
"-e p "
"/data/old-grub-mender-grubenv-modified.cfg /data/new-grub-mender-grubenv-modified.cfg"
)
connection.run(
"diff -u /data/old-grub-mender-grubenv-modified.cfg /data/new-grub-mender-grubenv-modified.cfg"
)
finally:
connection.run("rm -f /data/old-grub-mender-grubenv-modified.cfg /data/new-grub-mender-grubenv-modified.cfg")

BIN
tests/uefi-nvram/MicCorThiParMarRoo_2010-10-05.crt

Binary file not shown.

BIN
tests/uefi-nvram/OVMF_VARS.fd

Binary file not shown.

71
tests/uefi-nvram/README.md

@ -0,0 +1,71 @@
UEFI NVRAM
==========
This directory holds the NVRAM file which is used as the firmware memory of the UEFI software which
runs under QEMU. It's main purpose is to start the UEFI software with certificates pre-loaded into
the firmware memory, and Secure Boot enabled.
How to recreate the `OVMF_VARS.fd` file
--------------------------------
1. Create the `OVMF_VARS.fd` file:
```bash
cp /usr/share/OVMF/OVMF_VARS.fd OVMF_VARS.fd
```
2. Create a filesystem which contains the UEFI certificates:
```bash
dd if=/dev/zero of=/tmp/cert-filesystem.fs bs=1M count=10; \
mkfs.vfat /tmp/cert-filesystem.fs; \
mkdir cert-filesystem; \
sudo mount /tmp/cert-filesystem.fs cert-filesystem -o loop,uid=$UID; \
cp *.crt cert-filesystem; \
sudo umount cert-filesystem; \
rmdir cert-filesystem
```
Tip: If you ever need to re-fetch the certificate files, run `mokutil --db` on your own
computer. This lists your installed certificates, and they come with URLs which say where you can
download them. Make sure you download the `.crt`, not the `.crl`.
3. Launch QEMU with the NVRAM and the filesystem containing the certificates. *Make sure to press F2
quickly after the window appears to enter the firmware menu*:
```bash
qemu-system-x86_64 \
-drive file=/usr/share/OVMF/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on \
-drive file=./OVMF_VARS.fd,if=pflash,format=raw,unit=1 \
-drive file=/tmp/cert-filesystem.fs,if=ide,format=raw
```
4. After having entered the firmware menu, perform the following steps:
1. Enter "Device Manager".
2. Enter "Secure Boot Configuration".
3. Switch "Secure Boot Mode" to "Custom Mode".
4. Enter "Custom Secure Boot Options".
5. Enter "PK Options".
6. Enter "Enroll OK".
7. Enter "Enroll PK Using File".
8. Locate the certificate file in the filesystem that you created in main step 2, and add it.
9. Make sure to select "Commit Changes".
10. Repeat the same process starting from sub step 5, except for "DB Options" instead.
11. Go back to the "Secure Boot Configuration" screen. "Attempt Secure Boot" should now have
been auto-selected. If it's not, enable it and save the change.
12. Go back to the main manu and select "Reset". After the setup has been exited, you can kill
QEMU.
5. Clean up:
```bash
rm -f /tmp/cert-filesystem.fs
```
Loading…
Cancel
Save