From 450a989d4ea5840e0fa7c9ef40d713a16a10ff4f Mon Sep 17 00:00:00 2001 From: Audiobuzz Date: Mon, 15 Jun 2026 21:45:59 +0000 Subject: [PATCH] maintain selinux attributes into the erofs build --- .gitea/workflows/gitea-main-ci.yaml | 2 +- CLAUDE.md | 3 ++- docs/TODO.md | 26 ++++++++++++++++++++++++-- scripts/extract-artifacts.sh | 25 +++++++++++++++++++++---- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/gitea-main-ci.yaml b/.gitea/workflows/gitea-main-ci.yaml index 008aeef..fadcf82 100644 --- a/.gitea/workflows/gitea-main-ci.yaml +++ b/.gitea/workflows/gitea-main-ci.yaml @@ -95,7 +95,7 @@ jobs: sudo apt-get install -y --no-install-recommends \ qemu-system-x86 qemu-utils libguestfs-tools linux-image-generic \ erofs-utils cryptsetup-bin jq systemd-ukify systemd-boot-efi \ - buildah shellcheck + buildah shellcheck attr sudo chmod 0644 /boot/vmlinuz-* 2>/dev/null || true - diff --git a/CLAUDE.md b/CLAUDE.md index da68f48..0998f5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,8 @@ shellcheck --exclude=SC1091,SC2154 files/dracut/95dot1x-macsec/*.sh scripts/*.sh Build-host deps: qemu-kvm, libguestfs-tools (+ a kernel for its supermin appliance), erofs-utils, cryptsetup, jq, systemd-ukify **and systemd-boot-efi** (the latter ships the `linuxx64.efi.stub` ukify needs), sbsigntools, -buildah (or docker). +buildah (or docker), **attr** (`getfattr` — `extract-artifacts.sh` asserts the +SELinux labels survived the export; an unlabeled enforcing root denies login). ## Architecture invariants — do not violate these diff --git a/docs/TODO.md b/docs/TODO.md index 37739d3..94e758e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -99,9 +99,19 @@ is the tmpfs overlay), fleet-generic (no per-host state). e.g. a tightly-constrained kiosk session + documented deviation — when hardening lands. -## [~] 5. Deploy & validate the end-to-end boot path (open-network test) +## [x] 5. Deploy & validate the end-to-end boot path (open-network test) -In progress. Stand up `serving/` (dnsmasq + nginx + SPDK) per +✅ **DONE (2026-06-15).** Full chain validated on QEMU/Proxmox: UEFI HTTP boot → +initramfs → DHCP → `nvmf-connect.service` auto-connects the NVMe/TCP subsystem → +two namespaces (data + verity hash) appear by pinned UUID → `systemd-veritysetup` +maps `/dev/mapper/root` → uncompressed erofs mounts → `systemd.volatile=overlay` +→ multi-user. Booted with `rd.dot1x=0` (open network) and the IP discovery +address. See bench findings below for the full list of fixes. Remaining cleanup +to leave the lab-test posture (now unblocked): revert the hardcoded IP to the +`nvme.fleet.lab` DNS name, and re-enable security (`DISABLE_DOT1X=false`) — the +secure path needs the same connect trigger over macsec0 (see ⚠ note below). + +Original task: stand up `serving/` (dnsmasq + nginx + SPDK) per [serving/README.md](../serving/README.md), rebuild the UKI with `nvmet_traddr=nvme.fleet.lab` (now a Packer var — older UKIs have the literal `NVMET_TRADDR` placeholder and won't connect), then boot a UEFI HTTP-boot client @@ -185,6 +195,18 @@ boot LAN. `systemd.verity_root_data=`/`systemd.verity_root_hash=` by-id devices with no offset. Touches `extract-artifacts.sh`, `build-oci.sh` (artifact list + SHA256SUMS), `serving/spdk-setup.sh` (2nd bdev/namespace), and invariant #9. + - **SELinux labels must survive into the erofs.** It booted to multi-user but + `root`/`packer-build-only` login was accepted then the session was killed + instantly. Cause: kickstart sets `selinux --enforcing`, but the build dropped + `security.selinux` xattrs (`virt-tar-out` + plain `tar -xpf` don't carry + them), so the enforcing target denied the login session on an unlabeled root + — and a read-only erofs can't autorelabel. Confirmed by booting with + `enforcing=0` (SMBIOS-injected) → login worked. **Fixed:** export via + `guestfish tar-out … selinux:true xattrs:true acls:true`, restore with + `tar --xattrs --xattrs-include='*' --acls --selinux`, and a `getfattr` + build-time assertion that `/usr/bin/bash` is labeled before `mkfs.erofs` + (mkfs.erofs preserves xattrs). Needs `attr` on the build host. The image now + boots **enforcing**. **⚠ The secure path has the same dependency on the (absent) nvmf autoconnect** — when 802.1X is re-enabled, the macsec0 path must likewise drive `connect-all` (or the network-manager port #1 must wire diff --git a/scripts/extract-artifacts.sh b/scripts/extract-artifacts.sh index cec4094..45df610 100755 --- a/scripts/extract-artifacts.sh +++ b/scripts/extract-artifacts.sh @@ -37,7 +37,7 @@ ROOT_HASH_UUID=${ROOT_HASH_UUID:-f1ee7000-0000-4000-8000-000000000002} ROOT_DATA_DEV="/dev/disk/by-id/nvme-uuid.${ROOT_DATA_UUID}" ROOT_HASH_DEV="/dev/disk/by-id/nvme-uuid.${ROOT_HASH_UUID}" -for bin in virt-copy-out virt-tar-out virt-ls mkfs.erofs veritysetup jq; do +for bin in virt-copy-out virt-ls guestfish mkfs.erofs veritysetup jq getfattr; do command -v "$bin" >/dev/null || { echo "ERROR: $bin not found on build host" >&2; exit 1; } done @@ -54,12 +54,29 @@ virt-copy-out -a "$QCOW2" "/boot/$KERNEL" "/boot/initramfs-generic.img" "$OUT/" mv "$OUT/$KERNEL" "$OUT/vmlinuz" mv "$OUT/initramfs-generic.img" "$OUT/initramfs.img" -echo "==> Exporting root tree" -virt-tar-out -a "$QCOW2" / "$WORK/rootfs.tar" +echo "==> Exporting root tree (preserving SELinux labels + xattrs)" +# SELinux labels MUST survive into the erofs, or the enforcing target denies the +# login session — you authenticate, then logind kills the session, and a +# read-only erofs can't autorelabel to fix itself. guestfish tar-out with +# selinux:true/xattrs:true captures security.selinux; the extraction restores it +# (--xattrs), and mkfs.erofs preserves xattrs into the image. (The old +# virt-tar-out + plain `tar -xpf` dropped the labels — that was the login bug.) +guestfish --ro -a "$QCOW2" -i \ + tar-out / "$WORK/rootfs.tar" numericowner:true xattrs:true selinux:true acls:true mkdir "$WORK/root" -tar -C "$WORK/root" --numeric-owner -xpf "$WORK/rootfs.tar" +tar -C "$WORK/root" --numeric-owner --xattrs --xattrs-include='*' --acls --selinux \ + -xpf "$WORK/rootfs.tar" rm -rf "$WORK/root/boot/"* "$WORK/rootfs.tar" # diskless hosts don't need /boot +# Fail loudly if labels didn't make it — shipping an unlabeled enforcing image +# boots but denies all logins (the symptom we just chased down). +if ! getfattr -h -n security.selinux "$WORK/root/usr/bin/bash" >/dev/null 2>&1; then + echo "ERROR: SELinux label missing on /usr/bin/bash in the extracted tree." >&2 + echo " An enforcing boot would deny login. Check guestfish/tar xattr flags." >&2 + exit 1 +fi +echo "==> SELinux labels preserved (bash: $(getfattr -h --only-values -n security.selinux "$WORK/root/usr/bin/bash" 2>/dev/null))" + echo "==> Building reproducible erofs image" # UNCOMPRESSED on purpose: the target Rocky 9 kernel's erofs is built without # CONFIG_EROFS_FS_ZIP, so mounting a compressed image fails at boot with