diff --git a/.gitea/workflows/gitea-main-ci.yaml b/.gitea/workflows/gitea-main-ci.yaml index 40f5e46..45cd395 100644 --- a/.gitea/workflows/gitea-main-ci.yaml +++ b/.gitea/workflows/gitea-main-ci.yaml @@ -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-. + # 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/) -> "-" (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}" diff --git a/CLAUDE.md b/CLAUDE.md index 0998f5f..218c108 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/diskless.pkr.hcl b/diskless.pkr.hcl index 830f810..a87b0d3 100644 --- a/diskless.pkr.hcl +++ b/diskless.pkr.hcl @@ -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 --------------------------------------------------- diff --git a/docs/TODO.md b/docs/TODO.md index a94a513..6ce83b6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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 diff --git a/docs/design-v2-errata.md b/docs/design-v2-errata.md index c11f2dd..0ee5e9a 100644 --- a/docs/design-v2-errata.md +++ b/docs/design-v2-errata.md @@ -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 diff --git a/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh b/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh index 5286247..d718a01 100755 --- a/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh +++ b/files/dracut/95dot1x-macsec/dot1x-macsec-up.sh @@ -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 diff --git a/files/dracut/95dot1x-macsec/module-setup.sh b/files/dracut/95dot1x-macsec/module-setup.sh index f33d7d7..2651292 100755 --- a/files/dracut/95dot1x-macsec/module-setup.sh +++ b/files/dracut/95dot1x-macsec/module-setup.sh @@ -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" diff --git a/files/dracut/95dot1x-macsec/nm-unmanaged.sh b/files/dracut/95dot1x-macsec/nm-unmanaged.sh new file mode 100644 index 0000000..44e9c2b --- /dev/null +++ b/files/dracut/95dot1x-macsec/nm-unmanaged.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" diff --git a/files/etc/NetworkManager/99-dot1x-unmanaged.conf b/files/etc/NetworkManager/99-dot1x-unmanaged.conf deleted file mode 100644 index b321770..0000000 --- a/files/etc/NetworkManager/99-dot1x-unmanaged.conf +++ /dev/null @@ -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* diff --git a/files/etc/dracut.conf.d/90-diskless.conf b/files/etc/dracut.conf.d/90-diskless.conf index f1d23a5..f959368 100644 --- a/files/etc/dracut.conf.d/90-diskless.conf +++ b/files/etc/dracut.conf.d/90-diskless.conf @@ -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 "