Merge pull request 'Network Manager refactor' (#1) from network-manager into main
Reviewed-on: https://git.bitasylum.org/audiobuzz/diskless-workstation/pulls/1
This commit is contained in:
@@ -24,13 +24,19 @@
|
||||
#
|
||||
|
||||
name: Build Diskless UKI
|
||||
run-name: ${{ gitea.actor }} builds the diskless fleet UKI
|
||||
run-name: ${{ gitea.actor }} builds the diskless fleet UKI (${{ gitea.ref_name }})
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "**" # every branch — feature branches get a branch-name+sha tag
|
||||
tags:
|
||||
- "v*"
|
||||
- "v*" # tag push -> clean semver tag (v1.2.3 -> 1.2.3)
|
||||
|
||||
# One build per ref at a time; a newer push to the same branch cancels the
|
||||
# in-flight build (each build is a ~17-min VM run, so don't stack them).
|
||||
concurrency:
|
||||
group: uki-build-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PRODUCT_VERSION: "1.15.4" # Packer version
|
||||
@@ -68,13 +74,26 @@ jobs:
|
||||
-
|
||||
name: Derive image version
|
||||
id: vars
|
||||
# Tag push (v1.2.3 -> 1.2.3); otherwise 0.0.0-<short-sha>.
|
||||
# This becomes the OCI tag (build-oci.sh tags ${oci_image}:${image_version})
|
||||
# and the artifact dir name. Two cases:
|
||||
# - tag push (refs/tags/v1.2.3) -> "1.2.3" (release)
|
||||
# - branch push (refs/heads/<name>) -> "<name>-<sha7>" (dev image)
|
||||
# The branch name is sanitized to the OCI tag charset
|
||||
# ([A-Za-z0-9._-], <=128, no leading . or -): e.g. feature/foo -> feature-foo,
|
||||
# giving feature-foo-ab12cd3. So every branch yields a uniquely tagged,
|
||||
# pushed image you can boot/test without clobbering main or releases.
|
||||
run: |
|
||||
ref="${GITHUB_REF#refs/*/}"
|
||||
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
||||
if [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then
|
||||
version="${ref#v}"
|
||||
version="${GITHUB_REF#refs/tags/}"
|
||||
version="${version#v}"
|
||||
else
|
||||
version="0.0.0-$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
||||
branch="${GITHUB_REF#refs/heads/}"
|
||||
# invalid chars -> '-'; trim leading '.'/'-'; cap length headroom for -sha7
|
||||
safe_branch="$(printf '%s' "$branch" \
|
||||
| sed -e 's#[^A-Za-z0-9._-]#-#g' -e 's#^[.-]*##' | cut -c1-100)"
|
||||
[ -n "$safe_branch" ] || safe_branch="branch"
|
||||
version="${safe_branch}-${short_sha}"
|
||||
fi
|
||||
echo "image_version=${version}" >> "$GITHUB_OUTPUT"
|
||||
echo "Building image_version=${version}"
|
||||
|
||||
@@ -26,7 +26,8 @@ http/ks.cfg Kickstart for the throwaway build VM
|
||||
files/dracut/95dot1x-macsec/ Custom dracut module (the heart of the system)
|
||||
files/etc/dot1x/ Supplicant config + RADIUS CA (placeholder)
|
||||
files/etc/dracut.conf.d/90-diskless.conf no-hostonly + drivers + modules
|
||||
files/etc/NetworkManager/ NM unmanaged-device config
|
||||
(NM unmanaged split is now runtime:
|
||||
95dot1x-macsec/nm-unmanaged.sh, secure-mode only)
|
||||
scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline
|
||||
scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image
|
||||
serving/ Lab serving stack: dnsmasq + nginx + SPDK
|
||||
@@ -75,10 +76,27 @@ instead of working around it.
|
||||
1. **The supplicant must survive switch-root.** It is launched with
|
||||
`exec -a @wpa_supplicant` (systemd root-storage-daemon convention) and
|
||||
its state lives under `/run`. Do not “clean up” the `@`, move state off
|
||||
`/run`, or hand the interface to NetworkManager — MKA times out in ~6 s
|
||||
without keepalives and the root filesystem link dies. The NM
|
||||
unmanaged-devices config exists for this reason; keep it in sync with
|
||||
`/run`, or hand the **physical NIC** to NetworkManager — MKA times out in
|
||||
~6 s without keepalives and the root filesystem link dies. The NM
|
||||
unmanaged split exists for this reason (see below); keep it in sync with
|
||||
interface naming.
|
||||
1. **NM owns IP/DHCP; wpa_supplicant owns the physical NIC.** The initramfs
|
||||
uses the `network-manager` dracut module (not `network-legacy`) so NM
|
||||
**renews the DHCP lease** in the running system after switch-root — without
|
||||
this the one-shot lease expired, the address was dropped, and the NVMe/TCP
|
||||
root died (“keep alive timeout” / “no route to host”). The managed/unmanaged
|
||||
split is therefore load-bearing and **mode-aware**:
|
||||
- **Secure path:** IP rides on **`macsec0`**, which must stay **MANAGED** by
|
||||
NM. The **physical NIC (`en*`)** must stay **UNMANAGED** (wpa_supplicant
|
||||
owns EAPOL+MKA on it). This is enforced at runtime by the cmdline-stage
|
||||
hook `nm-unmanaged.sh`, which writes
|
||||
`/run/NetworkManager/conf.d/10-dot1x-unmanaged.conf` **before NM starts**.
|
||||
- **Test path (`rd.dot1x=0`):** no macsec0; the physical NIC carries IP and
|
||||
must be **MANAGED** so NM renews it — so the hook writes nothing.
|
||||
There is intentionally **no static `99-dot1x-unmanaged.conf`** (it couldn't
|
||||
be mode-aware — it wrongly unmanaged `en*` in test mode, blocking renewal).
|
||||
Never add macsec0 back to an unmanaged list, and never statically unmanage
|
||||
`en*` for all modes.
|
||||
1. **roothash coupling is a hard gate.** `build-oci.sh` refuses to build if
|
||||
the `roothash=` in the UKI cmdline ≠ `verity.json`. Never weaken, skip,
|
||||
or warn-instead-of-fail this check: a mismatched UKI/rootfs pair is an
|
||||
@@ -137,10 +155,15 @@ instead of working around it.
|
||||
**two separate NVMe namespaces** (`rootfs.erofs` + `rootfs.hash`, distinct
|
||||
pinned UUIDs), so the cmdline uses plain `systemd.verity_root_data=` /
|
||||
`systemd.verity_root_hash=` by-id devices with no offset.
|
||||
- `network-legacy` dracut module needs `dhclient`; RHEL 9 omits it from the
|
||||
minimal install, so the provisioner installs `dhcp-client` (without it dracut
|
||||
fails "module network-legacy can't be installed"). dhclient is deprecated —
|
||||
the durable fix is porting the hook to the `network-manager` dracut module.
|
||||
- **Network module: `network-manager` (ported 2026-06-17 from `network-legacy`).**
|
||||
network-legacy's one-shot DHCP lease was never renewed after switch-root, so
|
||||
at lease expiry the address was dropped and the NVMe/TCP root died. The
|
||||
`network-manager` module hands its initramfs connections to the real-root NM
|
||||
(state under `/run/NetworkManager`), which renews the lease. No more
|
||||
`dhcp-client`/`dhclient` (deprecated). The hook drives macsec0 DHCP via `nmcli`
|
||||
(not `ifup`). See invariant on the managed/unmanaged split. BENCH-VERIFY: lease
|
||||
actually renews past expiry (test path); macsec0 managed + `en*` unmanaged with
|
||||
MKA surviving switch-root (secure path).
|
||||
- Writable root overlay is `systemd.volatile=overlay`, **not** `rootovl`:
|
||||
RHEL/Rocky dracut ships no `overlayfs`/`rootovl` module. Needs
|
||||
`systemd-volatile-root.service` in the initramfs (bundled by recent dracut;
|
||||
@@ -164,8 +187,8 @@ These bit us bringing the build up on Rocky 9.8; they are easy to regress.
|
||||
the dest to exist or the tree lands elsewhere and later `install` steps fail.
|
||||
- **dracut module name** in `add_dracutmodules` omits the `NN` dir-prefix: the
|
||||
directory is `95dot1x-macsec` but the module is `dot1x-macsec` (cf. `nvmf`).
|
||||
- **`network-legacy` → `dhcp-client`** and **overlay → `systemd.volatile=overlay`**:
|
||||
see the bench-verify items above.
|
||||
- **`network-manager` dracut module** (was `network-legacy`+`dhcp-client`) and
|
||||
**overlay → `systemd.volatile=overlay`**: see the bench-verify items above.
|
||||
- **Test toggle**: `disable_dot1x=true` bakes `rd.dot1x=0`; the hook then skips
|
||||
802.1X/MACsec and DHCPs the NIC directly for NVMe-oF on an open port. The auth
|
||||
stack stays in the image, dormant. The matching lab DHCP/HTTP/NVMe-oF serving
|
||||
|
||||
+20
-10
@@ -211,22 +211,26 @@ build {
|
||||
inline = [
|
||||
"set -euxo pipefail",
|
||||
|
||||
# dhcp-client provides dhclient, which the dracut 'network-legacy' module
|
||||
# (and therefore the 95dot1x-macsec hook's ifup step) requires. RHEL9
|
||||
# deprecated dhclient in favour of NetworkManager, so the minimal ISO
|
||||
# omits it; without it network-legacy fails dracut's check() and the
|
||||
# diskless module 'cannot be installed'. (Strategic path: port the hook to
|
||||
# the 'network-manager' dracut module — see CLAUDE.md bench-verify notes.)
|
||||
# No dhcp-client: the initramfs now uses the 'network-manager' dracut
|
||||
# module (90-diskless.conf), not 'network-legacy'/dhclient. NM renews the
|
||||
# DHCP lease in the running system after switch-root, fixing the lease-
|
||||
# expiry outage (legacy's one-shot lease was never renewed -> address
|
||||
# dropped -> NVMe/TCP root died). NetworkManager is already pulled in by
|
||||
# @^minimal-environment, so nothing extra is needed for it here.
|
||||
# zstd: dracut compresses the initramfs with compress="zstd"
|
||||
# (90-diskless.conf); the minimal ISO lacks the zstd binary, so without it
|
||||
# dracut warns and silently falls back to gzip (a larger initramfs).
|
||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network dhcp-client zstd openssl",
|
||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
|
||||
|
||||
# Stage etc files into place
|
||||
"install -D -m0644 /tmp/etc-stage/dot1x/radius-ca.pem /etc/dot1x/radius-ca.pem",
|
||||
"install -D -m0644 /tmp/etc-stage/dot1x/wpa_supplicant-dot1x.conf /etc/dot1x/wpa_supplicant-dot1x.conf",
|
||||
"install -D -m0644 /tmp/etc-stage/dracut.conf.d/90-diskless.conf /etc/dracut.conf.d/90-diskless.conf",
|
||||
"install -D -m0644 /tmp/etc-stage/NetworkManager/99-dot1x-unmanaged.conf /etc/NetworkManager/conf.d/99-dot1x-unmanaged.conf",
|
||||
# NB: the old static /etc/NetworkManager/conf.d/99-dot1x-unmanaged.conf is
|
||||
# GONE. It can't be mode-aware (it wrongly unmanaged en* in test mode too,
|
||||
# blocking renewal). The 95dot1x-macsec cmdline hook nm-unmanaged.sh now
|
||||
# writes /run/NetworkManager/conf.d/10-dot1x-unmanaged.conf at boot, only in
|
||||
# secure mode, reserving the physical NIC for wpa_supplicant.
|
||||
"chmod 0755 /usr/lib/dracut/modules.d/95dot1x-macsec/*.sh",
|
||||
|
||||
# Inject the fleet NIC drivers into the dracut config
|
||||
@@ -253,8 +257,14 @@ build {
|
||||
# present (a false negative that wrongly failed the build for many runs).
|
||||
"lsinitrd /boot/initramfs-generic.img > /tmp/lsinitrd.txt",
|
||||
"grep -E 'macsec|wpa_supplicant|tpm2_nvread|nvme.tcp|erofs|veritysetup' /tmp/lsinitrd.txt",
|
||||
# Assert the test-mode NVMe-oF connect unit made it in (systemd-driven
|
||||
# initramfs → the connect is a systemd unit, not a dracut hook).
|
||||
# Assert NetworkManager is in the initramfs — the network-manager dracut
|
||||
# module is what renews the DHCP lease post-switch-root. A missing/renamed
|
||||
# network module would silently regress to no-renewal, so fail loudly.
|
||||
"grep -q 'NetworkManager' /tmp/lsinitrd.txt || { echo 'ERROR: NetworkManager missing from initramfs (network-manager dracut module not installed)' >&2; exit 1; }",
|
||||
# Assert the secure-mode NM unmanaged hook + test-mode NVMe-oF connect unit
|
||||
# made it in (systemd-driven initramfs → the connect is a systemd unit, not
|
||||
# a dracut hook).
|
||||
"grep -q 'nm-unmanaged.sh' /tmp/lsinitrd.txt || { echo 'ERROR: nm-unmanaged.sh hook missing from initramfs' >&2; exit 1; }",
|
||||
"grep -q 'nvmf-connect.service' /tmp/lsinitrd.txt || { echo 'ERROR: nvmf-connect.service missing from initramfs' >&2; exit 1; }",
|
||||
|
||||
# ---- Build hygiene ---------------------------------------------------
|
||||
|
||||
+32
-19
@@ -5,27 +5,40 @@ Persistent backlog, kept in sync with the working session task tracker. Status:
|
||||
|
||||
---
|
||||
|
||||
## [ ] 1. Port the dracut module off `network-legacy` → `network-manager`
|
||||
## [~] 1. Port the dracut module off `network-legacy` → `network-manager`
|
||||
|
||||
Durable replacement for the current `dhcp-client` stopgap. `dhclient` is
|
||||
deprecated in RHEL 9 and will eventually leave the repos; the build only works
|
||||
today because the provisioner installs `dhcp-client` so `network-legacy` passes
|
||||
dracut's `check()`.
|
||||
**IMPLEMENTED 2026-06-17 — awaiting boot-bench verification.** Fixes the
|
||||
confirmed lease-expiry outage: network-legacy's one-shot DHCP lease was never
|
||||
renewed post-switch-root, so the address dropped at lease expiry → NVMe/TCP root
|
||||
died ("keep alive timeout" / "no route to host"). NM hands its initramfs
|
||||
connections to the real-root NM (`/run/NetworkManager` state), which renews.
|
||||
|
||||
- Rewrite both network bring-up paths in
|
||||
[../files/dracut/95dot1x-macsec/dot1x-macsec-up.sh](../files/dracut/95dot1x-macsec/dot1x-macsec-up.sh):
|
||||
the secure path's `ifup macsec0` and the `rd.dot1x=0` bypass path's
|
||||
`ifup $NIC` — use `nmcli`/NetworkManager-in-initramfs (or `ip=` cmdline + the
|
||||
nm module) instead.
|
||||
- Change `depends()` `network-legacy` → `network-manager` in
|
||||
[../files/dracut/95dot1x-macsec/module-setup.sh](../files/dracut/95dot1x-macsec/module-setup.sh).
|
||||
- Update `add_dracutmodules` in
|
||||
[../files/etc/dracut.conf.d/90-diskless.conf](../files/etc/dracut.conf.d/90-diskless.conf).
|
||||
- Drop `dhcp-client` from the provisioner `dnf install` in
|
||||
[../diskless.pkr.hcl](../diskless.pkr.hcl).
|
||||
- Re-verify the `lsinitrd` assertion.
|
||||
- **Gate:** full boot-chain smoke test (swtpm + hostapd + FreeRADIUS) before
|
||||
merge, per CLAUDE.md.
|
||||
Done:
|
||||
- `90-diskless.conf` `add_dracutmodules`: `network-legacy` → `network-manager`.
|
||||
- `module-setup.sh` `depends()` → `network-manager`; added `nmcli`; installs the
|
||||
new cmdline hook.
|
||||
- NEW `nm-unmanaged.sh` (cmdline hook): secure mode writes
|
||||
`/run/NetworkManager/conf.d/10-dot1x-unmanaged.conf`
|
||||
(`unmanaged-devices=interface-name:en*`) **before NM starts**; no-op in test
|
||||
mode. Replaces the deleted static `99-dot1x-unmanaged.conf` (couldn't be
|
||||
mode-aware — it wrongly unmanaged `en*` in test mode, blocking renewal).
|
||||
- `dot1x-macsec-up.sh` secure path: `ifup macsec0` → `nmcli` (managed + DHCP,
|
||||
poll for IPv4). macsec0 MANAGED so NM renews; physical NIC UNMANAGED.
|
||||
- `diskless.pkr.hcl`: dropped `dhcp-client`, added explicit `NetworkManager`;
|
||||
removed the static-config install; lsinitrd now asserts `NetworkManager` +
|
||||
`nm-unmanaged.sh` present.
|
||||
- Static lint clean (`bash -n`, `shellcheck`, `packer validate`).
|
||||
|
||||
**BENCH-VERIFY before trusting (the gate):**
|
||||
- Test path (`disable_dot1x=true`): boot, `nmcli device` shows boot NIC MANAGED;
|
||||
run past the DHCP lease time (drop the lab dnsmasq lease low) — address
|
||||
persists, lease renews (`journalctl -u NetworkManager`), NVMe + SSH stay up.
|
||||
- Secure path (swtpm + hostapd + FreeRADIUS, `disable_dot1x=false`): `en*`
|
||||
UNMANAGED, `macsec0` MANAGED + renewing; MKA survives switch-root; verity root
|
||||
mapped; NVMe stays connected past lease time; no "Address already assigned".
|
||||
- **Risk:** ordering of the unmanaged drop-in vs NM start in secure mode. If NM
|
||||
transiently grabs `en*`, fall back to also baking a static `en*`-unmanaged file
|
||||
for secure-only builds.
|
||||
|
||||
## [ ] 2. Apply RHEL9 STIG hardening to the built image
|
||||
|
||||
|
||||
@@ -61,11 +61,16 @@ CLAUDE.md invariant #9.
|
||||
|
||||
These don't contradict the PDF (it doesn't specify them) but are worth recording.
|
||||
|
||||
- **Network backend.** The dracut hook uses `network-legacy`/`ifup`, which needs
|
||||
`dhclient`; RHEL 9 deprecated it and the minimal ISO omits it, so the
|
||||
provisioner installs `dhcp-client` explicitly. dhclient is on its way out — the
|
||||
durable fix is porting the hook to the `network-manager` dracut module
|
||||
(tracked; see project todo).
|
||||
- **Network backend.** The dracut hook uses the `network-manager` dracut module
|
||||
(ported 2026-06-17 from `network-legacy`/`ifup`/`dhclient`). NM hands its
|
||||
initramfs connections to the real-root NetworkManager, which **renews the DHCP
|
||||
lease** in the running system — network-legacy's one-shot lease was never
|
||||
renewed, so the address dropped at lease expiry and the NVMe/TCP root died.
|
||||
Managed/unmanaged is mode-aware: secure path → `macsec0` managed (NM DHCPs +
|
||||
renews it), physical `en*` unmanaged (wpa_supplicant owns EAPOL/MKA), enforced
|
||||
by the `nm-unmanaged.sh` cmdline hook writing a `/run/NetworkManager/conf.d`
|
||||
drop-in before NM starts; test path → boot NIC managed, hook is a no-op. No
|
||||
static unmanaged file (it couldn't be mode-aware). Awaiting boot-bench verify.
|
||||
- **Build ISO.** Defaults to a version-pinned Rocky 9.x **minimal** ISO on the
|
||||
internal mirror (`iso_url` + `sha256` `iso_checksum`). `http/ks.cfg` installs
|
||||
only core+sshd+chrony from the ISO; the diskless tooling is pulled online by
|
||||
|
||||
@@ -107,8 +107,26 @@ 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
|
||||
# DHCP on macsec0 via NetworkManager (NOT the old network-legacy `ifup`): NM owns
|
||||
# macsec0, so it RENEWS the lease in the running system after switch-root — the
|
||||
# whole point of the network-manager port. macsec0 was created externally by
|
||||
# wpa_supplicant; ask NM to manage+activate it with a default DHCP profile. The
|
||||
# physical NIC stays unmanaged (nm-unmanaged.sh cmdline drop-in), so
|
||||
# wpa_supplicant keeps EAPOL/MKA on it. -w 0 returns immediately; we poll below.
|
||||
nmcli device set macsec0 managed yes 2>/dev/null || true
|
||||
nmcli -w 0 device connect macsec0 2>/dev/null || true
|
||||
|
||||
# Wait for the DHCP lease (IPv4 on macsec0) before netroot/NVMe connect.
|
||||
i=0
|
||||
while [ "$i" -lt "$TIMEOUT" ]; do
|
||||
ip -4 addr show macsec0 2>/dev/null | grep -q 'inet ' && break
|
||||
sleep 1; i=$((i + 1))
|
||||
done
|
||||
if [ "$i" -ge "$TIMEOUT" ]; then
|
||||
warn "dot1x-macsec: no DHCP lease on macsec0 after ${TIMEOUT}s"
|
||||
exit 1
|
||||
fi
|
||||
info "dot1x-macsec: macsec0 addressed via NetworkManager (DHCP)"
|
||||
|
||||
# --- 5. derive per-host NVMe identity from the TPM certificate --------------
|
||||
# Keeps the image fleet-generic: the host NQN is a function of the machine
|
||||
|
||||
@@ -11,7 +11,10 @@ check() {
|
||||
}
|
||||
|
||||
depends() {
|
||||
echo "network-legacy bash"
|
||||
# network-manager (not network-legacy): NM renews the DHCP lease in the real
|
||||
# root after switch-root (the legacy one-shot lease was never renewed). The
|
||||
# secure path drives DHCP on macsec0 via nmcli instead of the old `ifup`.
|
||||
echo "network-manager bash"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -29,7 +32,9 @@ install() {
|
||||
# distro's dracut nvmf module populates discovery.conf but never registers
|
||||
# an initqueue/online autoconnect hook). The nvmf module installs nvme too,
|
||||
# but list it here so the hook's dependency is explicit and load-order-safe.
|
||||
inst_multiple wpa_supplicant wpa_cli tpm2_nvread openssl ip setsid \
|
||||
# nmcli: the secure path brings macsec0 up via NetworkManager (DHCP+renewal)
|
||||
# instead of the old network-legacy `ifup`.
|
||||
inst_multiple wpa_supplicant wpa_cli tpm2_nvread openssl ip setsid nmcli \
|
||||
grep sed cut tr sha256sum sleep mkdir nvme
|
||||
|
||||
# TSS libraries that tpm2_nvread dlopens
|
||||
@@ -39,6 +44,13 @@ install() {
|
||||
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
|
||||
|
||||
# cmdline-stage hook (runs BEFORE NetworkManager starts): in secure mode it
|
||||
# drops /run/NetworkManager/conf.d/10-dot1x-unmanaged.conf so NM never grabs
|
||||
# the physical EAPOL NIC (wpa_supplicant owns it). No-op in rd.dot1x=0 test
|
||||
# mode, so NM manages+renews the boot NIC. Replaces the old static
|
||||
# 99-dot1x-unmanaged.conf, which couldn't be mode-aware.
|
||||
inst_hook cmdline 10 "$moddir/nm-unmanaged.sh"
|
||||
|
||||
# Hook: runs from the initqueue once the NIC exists, before netroot
|
||||
inst_hook initqueue/settled 30 "$moddir/dot1x-macsec-up.sh"
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# nm-unmanaged.sh — dracut cmdline-stage hook (runs BEFORE NetworkManager starts).
|
||||
#
|
||||
# In the SECURE path (802.1X/MACsec), wpa_supplicant owns the physical NIC: it
|
||||
# runs EAPOL and the MACsec key agreement (MKA) on it, and that link carries the
|
||||
# root filesystem. NetworkManager must NOT touch that NIC — a competing NM
|
||||
# supplicant would drop MKA and kill netroot. So drop an NM config that marks the
|
||||
# physical NIC unmanaged. IP is carried by macsec0, which is left MANAGED so NM
|
||||
# does DHCP *and renews the lease* on it.
|
||||
#
|
||||
# In the TEST path (rd.dot1x=0) there is no macsec0; IP rides on the physical NIC
|
||||
# directly and we WANT NM to manage+renew it — so write nothing.
|
||||
#
|
||||
# Why /run (not /etc) and why the cmdline stage: /run/NetworkManager/conf.d is
|
||||
# read by NM and the tmpfs survives switch-root, so the same rule applies in both
|
||||
# the initramfs and the real root. Writing here at the cmdline stage guarantees
|
||||
# the file exists before NM is started, so NM never transiently manages the EAPOL
|
||||
# NIC. This replaces the old static /etc/.../99-dot1x-unmanaged.conf, which could
|
||||
# not be mode-aware (it wrongly unmanaged en* in test mode too, blocking renewal).
|
||||
|
||||
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
|
||||
|
||||
# Test mode: leave the boot NIC managed so NM renews its lease.
|
||||
[ "$(getarg rd.dot1x=)" = "0" ] && return 0
|
||||
|
||||
_dir=/run/NetworkManager/conf.d
|
||||
_conf="$_dir/10-dot1x-unmanaged.conf"
|
||||
[ -e "$_conf" ] && return 0 # idempotent (cmdline hooks may re-run)
|
||||
|
||||
mkdir -p "$_dir"
|
||||
# interface-name:en* matches the predictable physical NIC names (eno/ens/enp);
|
||||
# macsec0 is deliberately NOT listed, so NM manages it for DHCP/renewal.
|
||||
{
|
||||
echo "[keyfile]"
|
||||
echo "unmanaged-devices=interface-name:en*"
|
||||
} > "$_conf"
|
||||
info "dot1x-macsec: NM unmanaged drop-in written ($_conf) — physical NIC reserved for wpa_supplicant"
|
||||
@@ -1,6 +0,0 @@
|
||||
# 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*
|
||||
@@ -9,7 +9,12 @@ hostonly="no"
|
||||
# by systemd.volatile=overlay on the kernel cmdline (RHEL/Rocky dracut has no
|
||||
# 'overlayfs'/'rootovl' module — that's a Debian/third-party feature); we only
|
||||
# need the overlay kernel filesystem, included via 'filesystems' below.
|
||||
add_dracutmodules+=" dot1x-macsec network-legacy nvmf systemd-veritysetup "
|
||||
# network-manager (NOT network-legacy): NM in the initramfs hands its
|
||||
# connections off to the real-root NetworkManager via /run/NetworkManager state,
|
||||
# so the DHCP lease is RENEWED in the running system. network-legacy's one-shot
|
||||
# lease was never renewed post-switch-root -> the address was dropped at lease
|
||||
# expiry -> NVMe/TCP root died ("keep alive timeout" / "no route to host").
|
||||
add_dracutmodules+=" dot1x-macsec network-manager nvmf systemd-veritysetup "
|
||||
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 "
|
||||
|
||||
Reference in New Issue
Block a user