Initial commit
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
#!/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=)
|
||||
# shellcheck disable=SC2010 # /sys/class/net names are kernel ifnames; ls|grep is safe here
|
||||
[ -n "$NIC" ] || NIC=$(ls /sys/class/net | grep -vE '^(lo|macsec)' | head -1)
|
||||
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,
|
||||
# rootovl) is unchanged because it is driven by the kernel cmdline, not by this
|
||||
# hook. SECURITY: no link authentication 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"
|
||||
ip link set "$NIC" up
|
||||
# Plain DHCP on the physical NIC via network-legacy (mirrors how the secure
|
||||
# path addresses macsec0); satisfies rd.neednet for the nvmf module.
|
||||
ifup "$NIC"
|
||||
# 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
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# 95dot1x-macsec: authenticate the port with 802.1X EAP-TLS (TPM-held key,
|
||||
# client cert in TPM NV) and bring up MACsec before netroot is mounted.
|
||||
# The supplicant is launched as a systemd "root storage daemon"
|
||||
# (argv[0] = @wpa_supplicant) so it survives switch-root and keeps the
|
||||
# MKA session — and therefore the link carrying the root fs — alive.
|
||||
|
||||
check() {
|
||||
require_binaries wpa_supplicant wpa_cli tpm2_nvread openssl || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
depends() {
|
||||
echo "network-legacy bash"
|
||||
return 0
|
||||
}
|
||||
|
||||
installkernel() {
|
||||
instmods macsec
|
||||
# tpm_crb/tpm_tis are usually built in; assert anyway
|
||||
instmods tpm_crb tpm_tis
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple wpa_supplicant wpa_cli tpm2_nvread openssl ip setsid
|
||||
|
||||
# TSS libraries that tpm2_nvread dlopens
|
||||
inst_libdir_file "libtss2-*"
|
||||
|
||||
# Fleet-generic supplicant config + RADIUS CA
|
||||
inst_simple /etc/dot1x/wpa_supplicant-dot1x.conf /etc/dot1x/wpa_supplicant-dot1x.conf
|
||||
inst_simple /etc/dot1x/radius-ca.pem /etc/dot1x/radius-ca.pem
|
||||
|
||||
# Hook: runs from the initqueue once the NIC exists, before netroot
|
||||
inst_hook initqueue/settled 30 "$moddir/dot1x-macsec-up.sh"
|
||||
|
||||
dracut_need_initqueue
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# The physical NIC and macsec0 are owned by the initramfs-launched
|
||||
# @wpa_supplicant (root storage daemon). NetworkManager must not touch
|
||||
# them, or it will spawn a competing supplicant and drop the MKA session
|
||||
# carrying the root filesystem.
|
||||
[keyfile]
|
||||
unmanaged-devices=interface-name:macsec0;interface-name:en*
|
||||
@@ -0,0 +1,2 @@
|
||||
# PLACEHOLDER - replace with your RADIUS server CA chain (PEM) before building.
|
||||
# The build will succeed with this placeholder but authentication will not.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Fleet-generic wired 802.1X EAP-TLS + MACsec configuration.
|
||||
# Identical on every host: per-host identity comes from the TPM
|
||||
# (key at persistent handle, cert extracted from NV at boot).
|
||||
ctrl_interface=/run/wpa_supplicant
|
||||
eapol_version=3
|
||||
ap_scan=0
|
||||
|
||||
network={
|
||||
key_mgmt=IEEE8021X
|
||||
eap=TLS
|
||||
# RADIUS derives the real identity from the certificate CN/SAN
|
||||
identity="fleet-host"
|
||||
ca_cert="/etc/dot1x/radius-ca.pem"
|
||||
client_cert="/run/dot1x/client.pem"
|
||||
|
||||
# BENCH-VERIFY: TPM key reference method on your wpa_supplicant build.
|
||||
# Option A (OpenSSL tpm2 provider, direct persistent handle):
|
||||
private_key="handle:0x81010001"
|
||||
# Option B (tpm2-pkcs11 with a store regenerated into /run at boot):
|
||||
# private_key="pkcs11:token=fleet;object=dot1x-key;type=private;pin-value=..."
|
||||
|
||||
macsec_policy=1 # require MACsec
|
||||
macsec_integ_only=0 # confidentiality (encrypt), not integrity-only
|
||||
macsec_replay_protect=1
|
||||
macsec_replay_window=0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Fleet-generic initramfs for diskless 802.1X/MACsec hosts.
|
||||
# Root = erofs image on a shared read-only NVMe/TCP namespace,
|
||||
# integrity-verified by dm-verity, tmpfs overlay for runtime state.
|
||||
# Built with --no-hostonly; assert everything the real hardware needs,
|
||||
# because the build VM only has virtio devices.
|
||||
hostonly="no"
|
||||
add_dracutmodules+=" 95dot1x-macsec network-legacy nvmf systemd-veritysetup overlayfs "
|
||||
filesystems+=" erofs overlay "
|
||||
add_drivers+=" macsec nvme-tcp nvme-fabrics dm-verity @NIC_DRIVERS@ "
|
||||
install_items+=" /etc/dot1x/radius-ca.pem /etc/dot1x/wpa_supplicant-dot1x.conf "
|
||||
compress="zstd"
|
||||
Reference in New Issue
Block a user