Files
diskless-workstation/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh
T
2026-06-14 12:24:35 +00:00

128 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
# dot1x-macsec-up.sh — initqueue/settled hook.
# Order of operations is load-bearing:
# 1. pull client cert from TPM NV into /run
# 2. start wpa_supplicant (argv[0]=@wpa_supplicant => survives switch-root)
# 3. wait for EAP success + macsec0
# 4. DHCP on macsec0 (the pre-auth lease, if any, is invalid post-auth/VLAN)
# 5. derive hostnqn/hostid from the cert so dracut's nvmf module can
# connect the NVMe/TCP root namespace over macsec0
# Idempotent: guarded by a stamp file because initqueue may re-run hooks.
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
STAMP=/run/dot1x/.done
[ -e "$STAMP" ] && exit 0
mkdir -p /run/dot1x /run/wpa_supplicant
# --- knobs (override on the kernel cmdline) ---------------------------------
NIC=$(getarg rd.dot1x.if=)
if [ -z "$NIC" ]; then
# First non-loopback, non-macsec interface. Pure shell — ls/grep/head are
# not guaranteed to exist in the initramfs.
for _dev in /sys/class/net/*; do
_name=${_dev##*/}
case "$_name" in lo|macsec*) continue ;; esac
NIC=$_name; break
done
fi
NVINDEX=$(getarg rd.dot1x.nvindex=); [ -n "$NVINDEX" ] || NVINDEX=0x01800001
TIMEOUT=$(getarg rd.dot1x.timeout=); [ -n "$TIMEOUT" ] || TIMEOUT=60
info "dot1x-macsec: using interface $NIC, NV index $NVINDEX"
# --- 0. TEMPORARY test bypass (rd.dot1x=0) ----------------------------------
# Disables 802.1X + MACsec so the UKI / rootfs / NVMe-oF boot path can be
# validated on an OPEN switch port before the auth layer is brought online.
# The physical NIC is brought up with DHCP directly and dracut's cmdline-driven
# nvmf module connects the root namespace over it — there is no macsec0, no
# supplicant, no TPM cert read. The rest of the boot (rd.nvmf.discover, verity,
# systemd.volatile=overlay) is unchanged because it is driven by the kernel
# cmdline, not by this hook. SECURITY: no link auth or encryption in this mode — lab/test
# networks only. Remove rd.dot1x=0 from the cmdline to restore full security.
if [ "$(getarg rd.dot1x=)" = "0" ]; then
warn "dot1x-macsec: 802.1X/MACsec DISABLED (rd.dot1x=0) — TEST MODE, no link security"
# Networking in test mode is owned ENTIRELY by `ip=dhcp` on the cmdline:
# dracut's network module brings the NIC up, gets a DHCP lease, writes
# resolv.conf, and — crucially — emits the "online" event that dracut's
# nvmf module's initqueue/online hook waits on to run `nvme connect-all`.
# Do NOT `ifup`/`ip link set up` the NIC here: it races dracut's own
# bring-up ("Error: ipv4: Address already assigned") and, because it isn't
# dracut's network path, never fires the nvmf online hook — so the NVMe
# namespace never connects and the boot spins on the root device forever.
# (The secure path below is different: there the hook owns macsec0, which
# dracut knows nothing about, so it must address it itself.)
#
# Static test NVMe identity (the secure path derives this from the TPM
# cert). Match these on the nvmet target if it filters by host NQN.
if [ ! -s /etc/nvme/hostnqn ]; then
mkdir -p /etc/nvme
echo "nqn.2026-06.org.example.fleet:test-host" > /etc/nvme/hostnqn
echo "11111111-1111-1111-1111-111111111111" > /etc/nvme/hostid
info "dot1x-macsec: test hostnqn $(cat /etc/nvme/hostnqn)"
fi
: > "$STAMP"
exit 0
fi
# --- 1. client certificate out of TPM NV ------------------------------------
if [ ! -s /run/dot1x/client.pem ]; then
if ! tpm2_nvread -T device:/dev/tpmrm0 "$NVINDEX" -o /run/dot1x/client.der; then
warn "dot1x-macsec: tpm2_nvread failed — cannot authenticate"
exit 1
fi
openssl x509 -inform DER -in /run/dot1x/client.der -out /run/dot1x/client.pem
fi
# --- 2. supplicant as a root storage daemon ---------------------------------
ip link set "$NIC" up
if [ ! -e /run/dot1x/wpa.pid ]; then
# bash exec -a sets argv[0]; '@' prefix tells systemd's switch-root
# killing spree to spare this process (root storage daemon convention).
setsid bash -c "exec -a @wpa_supplicant /usr/sbin/wpa_supplicant \
-Dmacsec_linux -i $NIC \
-c /etc/dot1x/wpa_supplicant-dot1x.conf \
-P /run/dot1x/wpa.pid" &
fi
# --- 3. wait for authentication + macsec0 -----------------------------------
i=0
while [ "$i" -lt "$TIMEOUT" ]; do
if wpa_cli -p /run/wpa_supplicant -i "$NIC" status 2>/dev/null \
| grep -q '^suppPortStatus=Authorized' \
&& ip link show macsec0 >/dev/null 2>&1; then
break
fi
sleep 1; i=$((i + 1))
done
if [ "$i" -ge "$TIMEOUT" ]; then
warn "dot1x-macsec: authentication / MACsec timeout after ${TIMEOUT}s"
exit 1
fi
info "dot1x-macsec: port authorized, macsec0 up after ${i}s"
# --- 4. address the secure interface; netroot rides on macsec0 --------------
ip link set macsec0 up
# Hand macsec0 to dracut's network stack for DHCP + netroot resolution
ifup macsec0
# --- 5. derive per-host NVMe identity from the TPM certificate --------------
# Keeps the image fleet-generic: the host NQN is a function of the machine
# identity already proven via 802.1X, not a baked-in value.
if [ ! -s /etc/nvme/hostnqn ]; then
mkdir -p /etc/nvme
SERIAL=$(openssl x509 -in /run/dot1x/client.pem -serial -noout \
| cut -d= -f2 | tr 'A-F' 'a-f')
# Adjust the NQN domain/date to your org's convention (must match the
# allow-list on the nvmet target if host filtering is enabled).
echo "nqn.2026-06.org.example.fleet:host-${SERIAL}" > /etc/nvme/hostnqn
# Stable hostid derived from the same serial (UUID-shaped)
printf '%s' "$SERIAL" | sha256sum | cut -c1-32 \
| sed 's/\(.\{8\}\)\(.\{4\}\)\(.\{4\}\)\(.\{4\}\)\(.\{12\}\).*/\1-\2-\3-\4-\5/' \
> /etc/nvme/hostid
info "dot1x-macsec: hostnqn $(cat /etc/nvme/hostnqn)"
fi
: > "$STAMP"
exit 0