Files
diskless-workstation/files/secureboot/fleet-sb-enroll
T
2026-06-20 06:04:04 +00:00

95 lines
3.9 KiB
Bash

#!/usr/bin/env bash
# fleet-sb-enroll — enrol the fleet's custom Secure Boot keys (PK/KEK/db) from the
# baked PUBLIC .auth files into firmware, via efivarfs.
#
# EL9 ships no efitools (no efi-updatevar), so this is the OS-side equivalent: it
# prepends the EFI authenticated-variable attribute mask to each signed .auth
# payload and writes it to /sys/firmware/efi/efivars in a single write(), in the
# order db -> KEK -> PK (PK last leaves Setup Mode and turns Secure Boot on).
#
# Run as root over SSH on a workstation that is in Secure Boot SETUP MODE. Only
# PUBLIC material is used (the .auth files carry certs + signatures, never the
# PK/KEK/db private keys, which stay offline/HSM — see docs/secure-boot-keys.md).
#
# Usage:
# fleet-sb-enroll --check # print SB state + which keys are baked; do nothing
# fleet-sb-enroll # enrol db, KEK, PK (PK last) — requires Setup Mode
# fleet-sb-enroll --force # attempt even if not in Setup Mode (User-Mode
# # writes only succeed if each .auth is signed by
# # the in-place parent key)
#
# NOTE: this is a deliberate trust-bootstrap action. In Setup Mode the firmware
# accepts these writes unauthenticated, so only run it on a host you control, on a
# trusted/isolated network. See docs/TODO.md #10 for the auto-enrol risk model.
set -euo pipefail
KEYDIR=/usr/share/fleet-secureboot
EFIVARS=/sys/firmware/efi/efivars
GUID_GLOBAL=8be4df61-93ca-11d2-aa0d-00e098032b8c # PK, KEK, SetupMode
GUID_DB=d719b2cb-3d3a-4596-a3bc-dad00e67656f # db, dbx
# EFI var attributes: NON_VOLATILE|BOOTSERVICE_ACCESS|RUNTIME_ACCESS|
# TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x27, little-endian 4 bytes.
ATTR=$'\x27\x00\x00\x00'
die() { echo "fleet-sb-enroll: $*" >&2; exit 1; }
[ "$(id -u)" = 0 ] || die "must run as root (use sudo)"
[ -d "$EFIVARS" ] || die "efivarfs not present ($EFIVARS) — not a UEFI system / not booted via UEFI?"
setup_mode() { # echoes yes|no|unknown
local f="$EFIVARS/SetupMode-$GUID_GLOBAL"
[ -e "$f" ] || { echo unknown; return; }
# 4 attribute bytes then 1 data byte: 1 = Setup Mode, 0 = User Mode.
if [ "$(od -An -tu1 -j4 -N1 "$f" | tr -d ' ')" = 1 ]; then echo yes; else echo no; fi
}
show_state() {
echo "Secure Boot: $(mokutil --sb-state 2>/dev/null | tr '\n' ' ' || echo '?')"
echo "Setup Mode: $(setup_mode)"
echo "Baked keys ($KEYDIR):"
local k
for k in db KEK PK; do
if [ -f "$KEYDIR/$k.auth" ]; then echo " $k.auth present"; else echo " $k.auth MISSING"; fi
done
}
enrol_one() { # $1=var name $2=guid $3=.auth path
local f="$EFIVARS/$1-$2" tmp
[ -f "$3" ] || die "missing $3"
tmp=$(mktemp)
printf '%s' "$ATTR" > "$tmp"
cat "$3" >> "$tmp"
# Existing EFI vars carry the immutable flag; clear it before overwriting.
[ -e "$f" ] && chattr -i "$f" 2>/dev/null || true
# efivarfs requires the whole variable in ONE write() — dd, single block.
dd if="$tmp" of="$f" bs="$(stat -c%s "$tmp")" count=1 status=none \
|| { rm -f "$tmp"; die "write to '$1' failed (in Setup Mode? .auth signed by the right parent key?)"; }
rm -f "$tmp"
echo " enrolled $1"
}
FORCE=0
case "${1:-}" in
--check) show_state; exit 0 ;;
--force) FORCE=1 ;;
"") FORCE=0 ;;
*) die "usage: fleet-sb-enroll [--check|--force]" ;;
esac
# Require all three baked keys.
for k in db KEK PK; do
[ -f "$KEYDIR/$k.auth" ] || die "no baked keys at $KEYDIR/$k.auth — supply public PK/KEK/db .auth via the build (files/secureboot/keys/)"
done
if [ "$(setup_mode)" != yes ] && [ "$FORCE" != 1 ]; then
show_state
die "not in Setup Mode — clear/reset Secure Boot keys in firmware first, or pass --force"
fi
echo "Enrolling fleet Secure Boot keys (order: db -> KEK -> PK)..."
enrol_one db "$GUID_DB" "$KEYDIR/db.auth"
enrol_one KEK "$GUID_GLOBAL" "$KEYDIR/KEK.auth"
enrol_one PK "$GUID_GLOBAL" "$KEYDIR/PK.auth" # PK last -> exits Setup Mode
echo "Done."
echo "Verify with: mokutil --db and mokutil --sb-state"