Added UKI Secure Boot installation support.
This commit is contained in:
+12
-71
@@ -17,86 +17,25 @@
|
||||
# bootloader.sh - systemd-boot configuration
|
||||
#
|
||||
# Installs and configures systemd-boot as the bootloader:
|
||||
# - Signs systemd-boot before bootctl copies it to the ESP
|
||||
# - Runs bootctl install to set up EFI boot manager
|
||||
# - Creates boot entry with LUKS unlock parameters
|
||||
# - Supports both single-disk and RAID1 configurations
|
||||
# - Relies on systemd-boot Type #2 UKI auto-discovery
|
||||
# - Configures loader.conf timeout and editor settings
|
||||
|
||||
# Install systemd-boot bootloader
|
||||
install_bootloader() {
|
||||
print "Installing bootloader..."
|
||||
run_visible_cmd_in_chroot bootctl install
|
||||
}
|
||||
|
||||
# Create boot entry for single-disk installation
|
||||
# Arguments:
|
||||
# $1 - LUKS UUID
|
||||
create_boot_entry_single() {
|
||||
local luks_uuid="$1"
|
||||
|
||||
run_cmd_in_chroot sh -c "cat > /boot/loader/entries/arch.conf" <<EOF
|
||||
title Arch Linux
|
||||
linux /vmlinuz-linux
|
||||
initrd /initramfs-linux.img
|
||||
options lockdown=confidentiality intel_iommu=on amd_iommu=on iommu=force iommu.passthrough=0 rd.luks.name=${luks_uuid}=cryptroot rd.luks.options=discard root=/dev/mapper/cryptroot
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create boot entry for RAID1 installation
|
||||
# Arguments:
|
||||
# $1 - first LUKS UUID
|
||||
# $2 - second LUKS UUID
|
||||
create_boot_entry_raid1() {
|
||||
local luks_uuid_1="$1"
|
||||
local luks_uuid_2="$2"
|
||||
|
||||
run_cmd_in_chroot sh -c "cat > /boot/loader/entries/arch.conf" <<EOF
|
||||
title Arch Linux
|
||||
linux /vmlinuz-linux
|
||||
initrd /initramfs-linux.img
|
||||
options lockdown=confidentiality intel_iommu=on amd_iommu=on iommu=force iommu.passthrough=0 rd.luks.name=${luks_uuid_1}=cryptroot-1 rd.luks.name=${luks_uuid_2}=cryptroot-2 rd.luks.options=${luks_uuid_1}=discard rd.luks.options=${luks_uuid_2}=discard root=/dev/mapper/cryptroot-1
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create boot entry for RAID1 3-disk installation
|
||||
# Arguments:
|
||||
# $1 - first LUKS UUID
|
||||
# $2 - second LUKS UUID
|
||||
# $3 - third LUKS UUID
|
||||
create_boot_entry_raid1_3disk() {
|
||||
local luks_uuid_1="$1"
|
||||
local luks_uuid_2="$2"
|
||||
local luks_uuid_3="$3"
|
||||
|
||||
run_cmd_in_chroot sh -c "cat > /boot/loader/entries/arch.conf" <<EOF
|
||||
title Arch Linux
|
||||
linux /vmlinuz-linux
|
||||
initrd /initramfs-linux.img
|
||||
options lockdown=confidentiality intel_iommu=on amd_iommu=on iommu=force iommu.passthrough=0 rd.luks.name=${luks_uuid_1}=cryptroot-1 rd.luks.name=${luks_uuid_2}=cryptroot-2 rd.luks.name=${luks_uuid_3}=cryptroot-3 rd.luks.options=${luks_uuid_1}=discard rd.luks.options=${luks_uuid_2}=discard rd.luks.options=${luks_uuid_3}=discard root=/dev/mapper/cryptroot-1
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create appropriate boot entry based on storage mode
|
||||
# Arguments:
|
||||
# $1 - storage mode (single, raid1, raid1-3disk)
|
||||
create_boot_entry() {
|
||||
local storage_mode="$1"
|
||||
|
||||
if [ "$storage_mode" = "raid1" ]; then
|
||||
# shellcheck disable=SC2153
|
||||
create_boot_entry_raid1 "$LUKS_UUID" "$LUKS_UUID_2"
|
||||
elif [ "$storage_mode" = "raid1-3disk" ]; then
|
||||
# shellcheck disable=SC2153
|
||||
create_boot_entry_raid1_3disk "$LUKS_UUID" "$LUKS_UUID_2" "$LUKS_UUID_3"
|
||||
else
|
||||
create_boot_entry_single "$LUKS_UUID"
|
||||
fi
|
||||
run_visible_cmd_in_chroot bootctl --esp-path=/boot install
|
||||
}
|
||||
|
||||
# Configure loader.conf
|
||||
configure_loader() {
|
||||
run_cmd_in_chroot sed -i 's/^#timeout 3/timeout menu-hidden/' /boot/loader/loader.conf
|
||||
run_cmd_in_chroot sh -c 'printf "\neditor no\n" >> /boot/loader/loader.conf'
|
||||
run_cmd_in_chroot sh -c 'cat > /boot/loader/loader.conf <<'"'"'EOF'"'"'
|
||||
timeout menu-hidden
|
||||
#console-mode keep
|
||||
|
||||
editor no
|
||||
EOF'
|
||||
}
|
||||
|
||||
# Full bootloader setup
|
||||
@@ -105,7 +44,9 @@ configure_loader() {
|
||||
setup_bootloader() {
|
||||
local storage_mode="$1"
|
||||
|
||||
sign_systemd_boot_source
|
||||
install_bootloader
|
||||
create_boot_entry "$storage_mode"
|
||||
configure_loader
|
||||
chroot_systemd_enable systemd-boot-update.service
|
||||
warn_raid_esp_limitations "$storage_mode"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2026 Logan Fick
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
# secureboot.sh - UKI and Secure Boot signing configuration
|
||||
#
|
||||
# Builds a UKI-only boot path:
|
||||
# - Creates file-backed sbctl keys in the target system
|
||||
# - Configures mkinitcpio/ukify to emit a UKI
|
||||
# - Signs and tracks the UKI with sbctl
|
||||
# - Signs other EFI binaries that systemd-boot/fwupd need
|
||||
# - Leaves firmware key enrollment to the user
|
||||
|
||||
readonly UKI_OUTPUT_PATH="/boot/EFI/Linux/arch-linux.efi"
|
||||
readonly CMDLINE_DIR="/etc/cmdline.d"
|
||||
readonly SECURITY_CMDLINE="lockdown=confidentiality intel_iommu=on amd_iommu=on iommu=force iommu.passthrough=0"
|
||||
|
||||
# Create sbctl keys in the target system if they do not already exist.
|
||||
create_secure_boot_keys() {
|
||||
print "Creating Secure Boot signing keys..."
|
||||
|
||||
run_visible_cmd_in_chroot sbctl create-keys
|
||||
run_cmd_in_chroot test -r /var/lib/sbctl/keys/db/db.key
|
||||
run_cmd_in_chroot test -r /var/lib/sbctl/keys/db/db.pem
|
||||
}
|
||||
|
||||
# Return the storage-specific root command line for the UKI.
|
||||
# Arguments:
|
||||
# $1 - storage mode (single, raid1, raid1-3disk)
|
||||
get_root_cmdline() {
|
||||
local storage_mode="$1"
|
||||
|
||||
if [ "$storage_mode" = "raid1" ]; then
|
||||
printf 'rd.luks.name=%s=cryptroot-1 rd.luks.name=%s=cryptroot-2 rd.luks.options=%s=discard rd.luks.options=%s=discard root=/dev/mapper/cryptroot-1\n' \
|
||||
"$LUKS_UUID" "$LUKS_UUID_2" "$LUKS_UUID" "$LUKS_UUID_2"
|
||||
elif [ "$storage_mode" = "raid1-3disk" ]; then
|
||||
printf 'rd.luks.name=%s=cryptroot-1 rd.luks.name=%s=cryptroot-2 rd.luks.name=%s=cryptroot-3 rd.luks.options=%s=discard rd.luks.options=%s=discard rd.luks.options=%s=discard root=/dev/mapper/cryptroot-1\n' \
|
||||
"$LUKS_UUID" "$LUKS_UUID_2" "$LUKS_UUID_3" "$LUKS_UUID" "$LUKS_UUID_2" "$LUKS_UUID_3"
|
||||
else
|
||||
printf 'rd.luks.name=%s=cryptroot rd.luks.options=discard root=/dev/mapper/cryptroot\n' "$LUKS_UUID"
|
||||
fi
|
||||
}
|
||||
|
||||
# Write UKI command-line drop-ins.
|
||||
# Arguments:
|
||||
# $1 - storage mode (single, raid1, raid1-3disk)
|
||||
write_cmdline_dropins() {
|
||||
local storage_mode="$1"
|
||||
local root_cmdline
|
||||
|
||||
root_cmdline="$(get_root_cmdline "$storage_mode")"
|
||||
|
||||
print "Writing UKI command line drop-ins..."
|
||||
|
||||
run_cmd_in_chroot install -d -m 0755 "$CMDLINE_DIR"
|
||||
|
||||
run_cmd_in_chroot sh -c "cat > ${CMDLINE_DIR}/10-security.conf" <<EOF
|
||||
${SECURITY_CMDLINE}
|
||||
EOF
|
||||
|
||||
run_cmd_in_chroot sh -c "cat > ${CMDLINE_DIR}/90-root.conf" <<EOF
|
||||
${root_cmdline}
|
||||
EOF
|
||||
|
||||
run_cmd_in_chroot sh -c "test -s ${CMDLINE_DIR}/10-security.conf"
|
||||
run_cmd_in_chroot sh -c "test -s ${CMDLINE_DIR}/90-root.conf"
|
||||
run_cmd_in_chroot sh -c "find ${CMDLINE_DIR} -type f -name '*.conf' -size +0c | grep -q ."
|
||||
}
|
||||
|
||||
# Patch the stock linux preset in place for UKI-only output.
|
||||
write_uki_mkinitcpio_preset() {
|
||||
print "Configuring mkinitcpio for UKI-only output..."
|
||||
|
||||
run_cmd_in_chroot test -f /etc/mkinitcpio.d/linux.preset
|
||||
run_cmd_in_chroot sed -i \
|
||||
-e '/^ALL_cmdline=/d' \
|
||||
-e '/^#ALL_cmdline=/d' \
|
||||
-e "/^ALL_kver=/a ALL_cmdline=\"${CMDLINE_DIR}\"" \
|
||||
-e 's|^default_image=|#default_image=|' \
|
||||
-e "s|^#default_uki=.*|default_uki=\"${UKI_OUTPUT_PATH}\"|" \
|
||||
-e "s|^default_uki=.*|default_uki=\"${UKI_OUTPUT_PATH}\"|" \
|
||||
/etc/mkinitcpio.d/linux.preset
|
||||
|
||||
run_cmd_in_chroot grep -qx "ALL_cmdline=\"${CMDLINE_DIR}\"" /etc/mkinitcpio.d/linux.preset
|
||||
run_cmd_in_chroot grep -qx "default_uki=\"${UKI_OUTPUT_PATH}\"" /etc/mkinitcpio.d/linux.preset
|
||||
run_cmd_in_chroot sh -c "! grep -q '^default_image=' /etc/mkinitcpio.d/linux.preset"
|
||||
}
|
||||
|
||||
# Create the UKI output directory before mkinitcpio validates the preset path.
|
||||
prepare_uki_output_directory() {
|
||||
run_cmd_in_chroot install -d -m 0755 "$(dirname "$UKI_OUTPUT_PATH")"
|
||||
}
|
||||
|
||||
# Remove standalone initramfs images created by the stock package hook before the
|
||||
# installer replaces the preset. The unsigned kernel remains as mkinitcpio input.
|
||||
remove_standalone_initramfs_images() {
|
||||
run_cmd_in_chroot rm -f /boot/initramfs-linux.img /boot/initramfs-linux-fallback.img
|
||||
}
|
||||
|
||||
# Configure all files required before mkinitcpio -P creates the signed UKI.
|
||||
# Arguments:
|
||||
# $1 - storage mode (single, raid1, raid1-3disk)
|
||||
prepare_uki_secure_boot() {
|
||||
local storage_mode="$1"
|
||||
|
||||
create_secure_boot_keys
|
||||
write_cmdline_dropins "$storage_mode"
|
||||
prepare_uki_output_directory
|
||||
write_uki_mkinitcpio_preset
|
||||
}
|
||||
|
||||
# Sign and save the generated UKI in the sbctl file database.
|
||||
sign_uki() {
|
||||
print "Signing UKI..."
|
||||
|
||||
run_cmd_in_chroot test -f "$UKI_OUTPUT_PATH"
|
||||
run_visible_cmd_in_chroot sbctl sign -s "$UKI_OUTPUT_PATH"
|
||||
}
|
||||
|
||||
# Sign the source systemd-boot binary before bootctl copies it to the ESP.
|
||||
sign_systemd_boot_source() {
|
||||
print "Signing systemd-boot source binary..."
|
||||
|
||||
run_cmd_in_chroot rm -f /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed
|
||||
run_visible_cmd_in_chroot sbctl sign -s \
|
||||
-o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed \
|
||||
/usr/lib/systemd/boot/efi/systemd-bootx64.efi
|
||||
}
|
||||
|
||||
# Configure fwupd for direct Secure Boot signing without shim.
|
||||
configure_fwupd_secure_boot() {
|
||||
print "Configuring fwupd Secure Boot support..."
|
||||
|
||||
run_cmd_in_chroot install -d -m 0755 /var/etc/fwupd
|
||||
run_cmd_in_chroot sh -c "cat > /var/etc/fwupd/fwupd.conf" <<'EOF'
|
||||
[uefi_capsule]
|
||||
DisableShimForSecureBoot=true
|
||||
EOF
|
||||
run_cmd_in_chroot chmod 0640 /var/etc/fwupd/fwupd.conf
|
||||
|
||||
if [ -f "${MOUNT_POINT}/usr/lib/fwupd/efi/fwupdx64.efi" ]; then
|
||||
run_cmd_in_chroot rm -f /usr/lib/fwupd/efi/fwupdx64.efi.signed
|
||||
run_visible_cmd_in_chroot sbctl sign -s \
|
||||
-o /usr/lib/fwupd/efi/fwupdx64.efi.signed \
|
||||
/usr/lib/fwupd/efi/fwupdx64.efi
|
||||
else
|
||||
print_warning "fwupd EFI helper not found; skipping fwupd EFI signing."
|
||||
fi
|
||||
}
|
||||
|
||||
# Verify the specific signed boot artifacts.
|
||||
verify_signed_artifacts_in_chroot() {
|
||||
local artifact_path
|
||||
|
||||
for artifact_path in "$@"; do
|
||||
run_cmd_in_chroot sh -c '
|
||||
verify_output="$(env SYSTEMD_ESP_PATH=/boot sbctl verify "$1")" || {
|
||||
printf "%s\n" "$verify_output" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
printf "%s\n" "$verify_output"
|
||||
|
||||
if ! printf "%s\n" "$verify_output" | grep -F -- "$1" | grep -Fq "is signed"; then
|
||||
printf "Expected signed Secure Boot artifact was not reported as signed: %s\n" "$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
' sh "$artifact_path"
|
||||
done
|
||||
}
|
||||
|
||||
verify_secure_boot_artifacts() {
|
||||
print "Verifying signed UKI and EFI boot artifacts..."
|
||||
|
||||
run_cmd_in_chroot test -f "$UKI_OUTPUT_PATH"
|
||||
verify_signed_artifacts_in_chroot \
|
||||
"$UKI_OUTPUT_PATH" \
|
||||
/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed \
|
||||
/boot/EFI/systemd/systemd-bootx64.efi \
|
||||
/boot/EFI/BOOT/BOOTX64.EFI
|
||||
|
||||
if [ -f "${MOUNT_POINT}/usr/lib/fwupd/efi/fwupdx64.efi.signed" ]; then
|
||||
verify_signed_artifacts_in_chroot /usr/lib/fwupd/efi/fwupdx64.efi.signed
|
||||
fi
|
||||
}
|
||||
|
||||
# Display post-install firmware enrollment instructions.
|
||||
show_secure_boot_enrollment_instructions() {
|
||||
print_warning "Secure Boot keys have not been enrolled in firmware."
|
||||
print "To enable Secure Boot after the first boot:"
|
||||
print " 1. Put firmware Secure Boot keys into Setup Mode, or otherwise prepare custom key enrollment."
|
||||
print " 2. Boot this installation with Secure Boot disabled or in Setup Mode."
|
||||
print " 3. Run: sudo sbctl enroll-keys --microsoft"
|
||||
print " 4. Enable Secure Boot in firmware."
|
||||
print "The installer does not enroll Secure Boot keys automatically."
|
||||
}
|
||||
|
||||
# Warn when RAID installs still only have the primary mounted ESP populated.
|
||||
# Arguments:
|
||||
# $1 - storage mode (single, raid1, raid1-3disk)
|
||||
warn_raid_esp_limitations() {
|
||||
local storage_mode="$1"
|
||||
|
||||
if [ "$storage_mode" = "raid1" ] || [ "$storage_mode" = "raid1-3disk" ]; then
|
||||
print_warning "Only the primary mounted ESP has been populated. Redundant Secure Boot bootability across secondary ESPs is not implemented yet."
|
||||
fi
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
# security.sh - Security hardening functions
|
||||
#
|
||||
# Applies security hardening to the installed system:
|
||||
# - Configures mkinitcpio with sd-encrypt hook for LUKS
|
||||
# - Runs mkinitcpio after boot artifact configuration
|
||||
# - Enables sudo access for wheel group
|
||||
# - Disables root account login
|
||||
# - Enables nftables firewall, smartd, and fstrim timer
|
||||
@@ -25,11 +25,11 @@
|
||||
# - Installs custom CA certificates from certs directory to system trust store
|
||||
# - Sets up USBGuard to whitelist connected devices
|
||||
|
||||
# Configure mkinitcpio hooks for encrypted root
|
||||
# Generate configured mkinitcpio artifacts.
|
||||
configure_initramfs() {
|
||||
print "Configuring initramfs..."
|
||||
print "Generating UKI..."
|
||||
|
||||
run_visible_cmd_in_chroot mkinitcpio -P
|
||||
run_visible_cmd_in_chroot mkinitcpio -P -- --nopost
|
||||
}
|
||||
|
||||
# Enable BTRFS scrub timer if using BTRFS filesystem
|
||||
|
||||
Reference in New Issue
Block a user