kiosk: stop nss-mdns removal from cascading chromium out of the image

Root cause of the trim-rpm-48dc3e1 'flashing cursor, no kiosk' + mysteriously
small (~500 MB) image: the 2d hygiene step ran 'dnf remove -y nss-mdns', but the
EPEL chromium package HARD-Requires nss-mdns (Chromium uses .local mDNS hostnames
for WebRTC ICE candidates). So dnf removed chromium as a dependent package and
then reaped its orphaned GUI stack (avahi, at-spi2, atk, adwaita, chromium-common
...) — a ~300 MB drop. The shipped image had no browser, so kiosk-session died at
the ${BROWSER:?} guard before ever starting weston: a bare KMS cursor.

The earlier nss-mdns commit checked what nss-mdns NEEDS (avahi) but not what NEEDS
nss-mdns (chromium).

Fix:
- Keep nss-mdns + avahi (satisfy chromium's Requires); instead MASK the avahi
  mDNS daemon/socket so nothing listens on :5353 — that was the real
  attack-surface concern, and nss-mdns is only an NSS module (no daemon needed).
- Add a FINAL GUARD at the end of 2d: assert chromium/chromium-common/weston/mesa
  are still installed in the final image, else fail the BUILD. A missing browser
  must brick the build, never the fleet. Catches any future silent removal
  cascade, oscap package_*_removed rule, or autoremove.
This commit is contained in:
Audiobuzz
2026-07-03 12:21:22 +00:00
parent 48dc3e11ae
commit 2bc2dafb1c
+35 -11
View File
@@ -957,17 +957,25 @@ build {
# hard-needs it) and mask as a backstop against future weak-dep reinstalls. # hard-needs it) and mask as a backstop against future weak-dep reinstalls.
"dnf remove -y firewalld 2>/dev/null || true", "dnf remove -y firewalld 2>/dev/null || true",
"systemctl mask firewalld.service 2>/dev/null || true", "systemctl mask firewalld.service 2>/dev/null || true",
# nss-mdns: the mDNS/.local NSS resolver, and the ONLY reverse-dep of avahi # nss-mdns / avahi: DO NOT REMOVE — the EPEL `chromium` package hard-Requires
# (confirmed 2026-07-03: `rpm -e --test avahi` → "avahi is needed by nss-mdns", # nss-mdns (Chromium resolves `.local` mDNS hostnames for WebRTC ICE
# and avahi is absent from `ldd libavformat` so it's unrelated to Chromium's # candidates, to avoid leaking local IPs in peer connections). `dnf remove
# media stack — unlike samba/librabbitmq/librist/libsrt/libzmq, which ARE # nss-mdns` therefore takes CHROMIUM with it as a dependent package, then
# libavformat DT_NEEDED and must stay for future HTML5 media). This fleet # reaps its whole orphaned GUI stack (avahi, at-spi2, atk, adwaita,
# resolves via DHCP/DNS (boot.fleet.lab), never .local, so mDNS is dead weight # chromium-common, ...) — a ~300 MB drop that ships a browserless, bricked
# AND a network-listening service unwanted on a locked-down kiosk. Removing # kiosk (flashing cursor; kiosk-session dies at `${BROWSER:?}`). This bit us
# nss-mdns lets dnf's clean_requirements_on_remove reap avahi/avahi-glib/ # 2026-07-03: the earlier commit checked what nss-mdns NEEDS (avahi) but not
# avahi-libs as orphans. (install_weak_deps=False may already drop nss-mdns if # what NEEDS nss-mdns (chromium). The FINAL GUARD below now asserts the kiosk
# it's a Recommends; this is the belt-and-braces for the hard-dep case.) # stack survived so this can never ship silently again.
"dnf remove -y nss-mdns 2>/dev/null || true", #
# 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 # (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 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. # The build-time STIG reports must not ship inside the read-only rootfs.
@@ -987,6 +995,22 @@ build {
# (linux-firmware slimming moved to step 2, BEFORE the dracut build, so the # (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 # 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.) # 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",
] ]
} }