Merge remote-tracking branch 'origin/main' into fapolicyd-tmpfs

This commit is contained in:
Audiobuzz
2026-07-04 12:11:05 +09:30
8 changed files with 510 additions and 11 deletions
+118 -1
View File
@@ -296,6 +296,16 @@ build {
# talks to the TPM directly via go-tpm. So this list stays at the baseline.)
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
# Locale trim: @^minimal-environment pulls glibc-all-langpacks — ~228 MB of
# locale data for EVERY language (measured 2026-07-03, 3rd-largest package in
# the whole image). This fleet is English-only (we also strip
# /usr/share/locale in 2d). Swap it for the English langpack + the C/POSIX
# minimal set → reclaims ~220 MB of the read-only erofs. Install the small
# replacements first, then drop the monolith (nothing Requires it; strict so
# a surprise dep surfaces in CI rather than silently bloating the image).
"dnf -y install glibc-minimal-langpack glibc-langpack-en",
"dnf -y remove glibc-all-langpacks",
# EFI / Secure Boot management tools (efivar, mokutil, efibootmgr — all base
# repos). Used over SSH on a workstation: `mokutil --sb-state/--pk/--kek/--db`
# reads Secure Boot state + the enrolled PK/KEK/db; efibootmgr manages boot
@@ -396,6 +406,39 @@ build {
"install -D -m0755 /tmp/fleet-enrol /usr/sbin/fleet-enrol",
"restorecon /usr/sbin/fleet-enrol 2>/dev/null || true",
# ---- Firmware slimming (BEFORE dracut so the initramfs shrinks too) ---
# linux-firmware ships ~678 MB of blobs for hardware this fixed fleet will
# never have. A live fleet audit (`modinfo -F firmware` over loaded modules +
# sysfs/`lspci` driver bindings on the real 820-class hosts AND the VMs,
# 2026-07-03) proved the ONLY loadable firmware any host requests is Intel
# i915 (GPU); every NIC is firmware-free (e1000e's ethtool "firmware-version"
# is on-chip NVM, not a /usr/lib/firmware file; virtio_net loads nothing), so
# NO NIC needs firmware. This runs PRE-dracut deliberately: --no-hostonly +
# plymouth's `drm` dep instmods a broad DRM set (i915/amdgpu/radeon/…) and
# dracut bundles each module's firmware into the initramfs (amdgpu fw is the
# classic UKI bloater), so trimming FIRST shrinks BOTH the rootfs erofs AND
# the UKI's initramfs. Keep i915 (all Intel gens — the fleet's only
# firmware-loading GPU) + intel/ice (the ice server-NIC DDP, the SOLE intel/
# blob any host needs — ish/sof/ibt are sensor-hub/audio/bluetooth, none
# loaded at boot) + intel-ucode; strip the rest (~600 MB). NOT mellanox/ —
# those are mlxsw_spectrum SWITCH-ASIC blobs, not NIC firmware (mlx5 NIC fw is
# on-card, never request_firmware'd from /usr/lib/firmware), so they're dead
# weight even for an mlx5 build. Safe for the initramfs: the fleet
# splash GPU (i915) fw is kept and the build-VM splash uses virtio-gpu/bochs
# (no fw) — dracut just warns "firmware not found" for the removed drm modules
# and continues. Risk asymmetry: missing NIC fw would brick the fleet (none
# needed), missing GPU fw only degrades a display. BENCH-VERIFY the kiosk
# renders on the 820. Strip-in-place (rm, not rpm -e): linux-firmware can't be
# dropped as a package without also losing i915. See docs/TODO #18.
"if [ -d /usr/lib/firmware ]; then find /usr/lib/firmware -mindepth 1 -maxdepth 1 ! -name i915 ! -name intel ! -name intel-ucode -exec rm -rf {} + ; fi",
# Narrow intel/ to just ice (NIC DDP); drop ish (sensor hub), sof (audio DSP),
# ibt (bluetooth) etc. — none are loaded on this fleet (audit 2026-07-03).
"[ -d /usr/lib/firmware/intel ] && find /usr/lib/firmware/intel -mindepth 1 -maxdepth 1 ! -name ice -exec rm -rf {} + || true",
# Wi-Fi firmware: wired-only fleet (no iwlwifi). Remove the packages cleanly
# so the rpmdb stays honest (the find above dropped their iwlwifi-*.ucode).
"dnf remove -y 'iwl*-firmware' 2>/dev/null || true",
"echo \"==> firmware after strip: $(du -sh /usr/lib/firmware 2>/dev/null | cut -f1) (was ~678 MB)\"",
# ---- Build the fleet-generic initramfs ------------------------------
# %%{ escapes the rpm query tags so Packer's HCL template engine treats
# them as literals (a bare %{...} is parsed as a template directive).
@@ -406,6 +449,22 @@ build {
# under pipefail the pipeline reports failure even when the file is
# present (a false negative that wrongly failed the build for many runs).
"lsinitrd /boot/initramfs-generic.img > /tmp/lsinitrd.txt",
# Initramfs footprint (this .img is embedded in the signed UKI and pulled
# over PXE/HTTP on every boot). Report the built size + any firmware that
# still made it in — post-strip this should be i915 only (pulled by
# plymouth's drm dep); if amdgpu/radeon/etc. appear, the strip didn't run
# before dracut. See docs/TODO #18.
"echo \"==> initramfs size: $(du -h /boot/initramfs-generic.img | cut -f1)\"",
"echo '==> firmware bundled in initramfs (expect i915 only):'; grep -i '/firmware/' /tmp/lsinitrd.txt || echo ' (none)'",
# Largest files IN the initramfs (in-cpio size, before the outer zstd) — after
# the firmware strip the .img is module-bound (the --no-hostonly DRM/net/crypto
# set), so this surfaces the next trim targets (e.g. GPU driver kmods). See #18.
# Sort into a FILE then head the file — NOT `sort | head`: head closes the pipe
# after 25 lines, sort dies of SIGPIPE (exit 141), and step 2's
# `set -euxo pipefail` turns that into a build failure (same trap as the
# lsinitrd `| grep -q` note above). (The kiosk step's rpm-manifest `| head` is
# safe only because that step runs plain `set -e`, no pipefail.)
"echo '==> initramfs largest files:'; awk '/^-/{print $5, $NF}' /tmp/lsinitrd.txt | sort -rn > /tmp/initrd-sizes.txt; head -25 /tmp/initrd-sizes.txt; rm -f /tmp/initrd-sizes.txt",
"grep -E 'macsec|wpa_supplicant|tpm2_nvread|nvme.tcp|erofs|veritysetup' /tmp/lsinitrd.txt",
# Assert NetworkManager is in the initramfs — the network-manager dracut
# module is what renews the DHCP lease post-switch-root. A missing/renamed
@@ -459,7 +518,27 @@ build {
# the heaviest items here and are likely unnecessary given weston
# --use-pixman + chromium --disable-gpu — candidate to drop once the kiosk
# is display-verified; see TODO #4.)
"dnf install -y --setopt=tsflags=nodocs weston chromium mesa-dri-drivers mesa-libEGL mesa-libgbm dejavu-sans-fonts",
#
# install_weak_deps=False: a locked-down single-URL kiosk needs none of the
# Recommends: this transaction otherwise drags in — flatpak + its ostree/
# bubblewrap sandbox runtime, tracker-miners (a filesystem indexer, both
# dead weight AND a threat surface), the pipewire/pulseaudio audio stack,
# and libproxy-webkitgtk4 → webkit2gtk3-jsc (a whole SECOND browser engine
# pulled into a Chromium kiosk just for libproxy's PAC runner). Measured:
# the default (weak-deps=on) transaction was 229 pkgs / 1.0 GB. The floor
# that survives is chromium + chromium-common + llvm-libs (mesa sw-GL) +
# gtk3/adwaita + libicu — all HARD deps. If a genuine runtime dep turns out
# to be a weak dep (e.g. a font, or libva for VA-API on the GPU path), add
# it to the explicit list below rather than re-enabling weak deps wholesale.
"dnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=False weston chromium mesa-dri-drivers mesa-libEGL mesa-libgbm dejavu-sans-fonts",
# Footprint audit (surfaces in the CI build log): the installed set, largest
# first, + a total count. Turns 'seems unnecessary' into data for the next
# trim round (e.g. dropping gstreamer/media codecs if the SPA plays no HTML5
# media, or mesa-dri-drivers if the GPU path is abandoned). %%{...} is escaped
# for Packer's HCL2 template engine (a bare %%{ is parsed as a directive).
"echo '==> kiosk package footprint (installed size, largest 40 first):'",
"rpm -qa --qf '%%{SIZE}\\t%%{NAME}-%%{VERSION}-%%{RELEASE}\\n' | sort -rn | head -40",
"echo \"==> total packages installed after kiosk: $(rpm -qa | wc -l)\"",
# Unprivileged, passwordless kiosk user. Groups: 'video' (KMS /dev/dri/card0
# for weston) + 'input' (libinput) as a seat fallback; 'render'
# (/dev/dri/renderD128) so Chromium's GPU process can do hardware raster on
@@ -912,6 +991,25 @@ build {
# hard-needs it) and mask as a backstop against future weak-dep reinstalls.
"dnf remove -y firewalld 2>/dev/null || true",
"systemctl mask firewalld.service 2>/dev/null || true",
# nss-mdns / avahi: DO NOT REMOVE — the EPEL `chromium` package hard-Requires
# nss-mdns (Chromium resolves `.local` mDNS hostnames for WebRTC ICE
# candidates, to avoid leaking local IPs in peer connections). `dnf remove
# nss-mdns` therefore takes CHROMIUM with it as a dependent package, then
# reaps its whole orphaned GUI stack (avahi, at-spi2, atk, adwaita,
# chromium-common, ...) — a ~300 MB drop that ships a browserless, bricked
# kiosk (flashing cursor; kiosk-session dies at `${BROWSER:?}`). This bit us
# 2026-07-03: the earlier commit checked what nss-mdns NEEDS (avahi) but not
# what NEEDS nss-mdns (chromium). The FINAL GUARD below now asserts the kiosk
# stack survived so this can never ship silently again.
#
# We still don't want the avahi mDNS DAEMON listening on :5353 on a
# locked-down kiosk (that was the real attack-surface concern). nss-mdns is
# only an NSS module (libnss_mdns.so) — it needs no daemon; if avahi-daemon
# isn't running, `.local` lookups just fall through. So keep the PACKAGES
# (satisfy chromium's Requires) but mask the listener: no socket, no daemon,
# dep satisfied, browser intact. ~1.5 MB kept — negligible vs a working kiosk.
"systemctl mask avahi-daemon.service avahi-daemon.socket 2>/dev/null || true",
"systemctl disable avahi-daemon.service avahi-daemon.socket 2>/dev/null || true",
# (No Go toolchain to remove — fleet-hostapi + fleet-enrol are built OUT OF
# the VM on the CI runner and file-provisioned in as static binaries.)
# The build-time STIG reports must not ship inside the read-only rootfs.
@@ -928,6 +1026,25 @@ build {
"if [ -d /usr/lib64/chromium-browser/locales ]; then find /usr/lib64/chromium-browser/locales -type f -name '*.pak' ! -name 'en-US.pak' -delete; fi",
# App translation catalogs (.mo) for locales this fleet never uses.
"[ -d /usr/share/locale ] && find /usr/share/locale -mindepth 1 -maxdepth 1 -type d ! -name 'en*' ! -name 'C*' -exec rm -rf {} + || true",
# (linux-firmware slimming moved to step 2, BEFORE the dracut build, so the
# UKI initramfs shrinks too — see TODO #18. Nothing re-adds firmware after
# that point, so the rootfs erofs is already trimmed by the time we get here.)
#
# ---- FINAL GUARD: assert the kiosk stack survived the whole pipeline ----
# 2026-07-03 incident: chromium installed cleanly in the 2b kiosk step (the
# footprint audit showed chromium-149 + chromium-common, 523 pkgs), but by
# the final image it was GONE (472 pkgs) — `dnf remove -y nss-mdns` in 2d
# removed chromium AS A DEPENDENT PACKAGE (EPEL chromium hard-Requires
# nss-mdns for WebRTC mDNS ICE candidates), then reaped its orphaned GUI
# stack. Result: a ~500 MB image whose kiosk died at the `${BROWSER:?}` guard
# in kiosk-session — a flashing-cursor boot with no weston, shipped green.
# The nss-mdns removal is fixed above (kept + avahi listener masked), but a
# missing browser must fail the BUILD, never brick the fleet: assert every
# kiosk-critical package is still installed at the very end of the pipeline
# (after remediation AND all 2d removes). This catches ANY future silent drop
# — a new hard-Requires cascade, an oscap package_*_removed rule, autoremove.
# enable_kiosk=false legitimately ships no browser, so gate on it.
"if [ \"${var.enable_kiosk}\" = \"true\" ] && [ -n \"${var.kiosk_url}\" ]; then for p in chromium chromium-common weston mesa-dri-drivers mesa-libEGL mesa-libgbm; do rpm -q \"$p\" >/dev/null 2>&1 || { echo \"ERROR: kiosk-critical package '$p' missing from the FINAL image — a later step removed it after the 2b kiosk install (e.g. a dnf remove whose dependency cascade took chromium, cf. the 2026-07-03 nss-mdns incident). Refusing to ship a bricked kiosk.\" >&2; exit 1; }; done; echo '==> guard: kiosk stack intact (chromium/weston/mesa present after remediation + hygiene)'; fi",
]
}