Files
fips/testing/check-rpm-floor.sh
Gustavo Lima ChavesandJohnathan Corgan 17ca52e694 Package FIPS for RPM-based distributions
Add an RPM package for Fedora and RHEL. `make -C packaging rpm` builds it
from packaging/rpm/fips.spec, and the release workflow attaches it beside
the .deb and the systemd tarball.

- The package is named `fips-mesh`, because Fedora already ships an
  unrelated `fips` (a FITS image viewer) that owns /usr/bin/fips. The spec
  declares `Conflicts: fips`.
- It installs the same files, units and fips group as the .deb. fips.service
  and fips-dns.service are enabled but not started on install;
  fips-firewall and fips-gateway stay opt-in.
- An upgrade queues `systemctl --no-block try-restart` of fips, fips-dns and
  fips-gateway, and reloads fips-firewall rather than restarting it.
- The binaries come from the pinned build image via build-deb-container.sh,
  so the RPM passes the same glibc floor and dependency checks as the .deb.
  rpmbuild runs in FIPS_RPM_BUILD_IMAGE, AlmaLinux 9 pinned by digest.
- The glibc floor is now 2.34 project-wide, the lowest supported RPM
  distribution (RHEL 9). testing/check-rpm-floor.sh fails a package that
  requires a newer glibc. RHEL 8 and openSUSE are not supported.
- Erase removes the DNS drop-ins fips-dns-setup wrote and restarts or
  reloads the resolver whose file it removed, as the Debian postrm does.
  /etc/fips is kept, since rpm has no purge.
- Dev builds are versioned 0.6.0-0.dev.git<date>.<sha>.

Tested on Fedora 44 and in AlmaLinux 9 containers with systemd: the package
requires GLIBC_2.34 and passes the floor check; install leaves both units
enabled and inactive; upgrade returns immediately and the daemon restarts
on the new binary; erase removes the units and DNS files and keeps
/etc/fips; host-built binaries (GLIBC_2.39) fail the floor check; dnf
refuses to install alongside the FITS viewer. The erase branch's resolver
restart and reload were exercised in AlmaLinux 9 with systemctl stubbed.

No install-test suite covers the RPM yet; it is only built.
2026-09-26 15:58:19 +00:00

94 lines
3.2 KiB
Bash
Executable File

#!/bin/bash
# Fail when an RPM records a glibc requirement above the declared floor.
#
# The counterpart of check-deb-depends.sh, for the other package format and for
# a different failure. On the Debian side the package's Depends are written by
# hand and can disagree with what the binaries need, so that check compares the
# two. rpm derives the requirement from the ELF files and cannot disagree with
# them -- which moves the risk one step back: the binaries themselves may have
# been built somewhere above the floor, and the package that results installs
# nowhere older, silently, until someone tries.
#
# This reads the requirement out of the finished package, which is the artifact
# that ships and the same table dnf enforces at install time.
#
# Usage: check-rpm-floor.sh <package.rpm>...
#
# Reads the floor from packaging/build-floor.env unless FIPS_GLIBC_FLOOR is set.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ -z "${FIPS_GLIBC_FLOOR:-}" ]; then
# shellcheck source=../packaging/build-floor.env
. "$REPO_ROOT/packaging/build-floor.env"
fi
FLOOR="${FIPS_GLIBC_FLOOR:?no floor declared}"
command -v rpm >/dev/null 2>&1 || {
echo "check-rpm-floor: rpm is not installed; cannot check anything." >&2
echo " Refusing to report a pass I did not establish." >&2
exit 2
}
[ $# -gt 0 ] || {
echo "usage: check-rpm-floor.sh <package.rpm>..." >&2
exit 2
}
# Sorts versions the way rpm does, so 2.10 is above 2.9 rather than below it.
version_gt() {
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1)" = "$1" ] && [ "$1" != "$2" ]
}
FAILED=0
CHECKED=0
for pkg in "$@"; do
if [ ! -f "$pkg" ]; then
echo " ERROR $pkg does not exist" >&2
FAILED=$((FAILED + 1))
continue
fi
# Every libc.so.6(GLIBC_x.y) entry rpm derived from the packaged binaries.
# The highest one is the floor the package will be held to.
need=$(rpm -qp --requires "$pkg" 2>/dev/null \
| grep -oE 'GLIBC_[0-9.]+' \
| sed 's/GLIBC_//' \
| sort -V \
| tail -1) || true
if [ -z "$need" ]; then
# No requirement at all means the package holds no dynamically linked
# binary, which for this package means the file list moved. Not a pass.
echo " ERROR $(basename "$pkg") records no glibc requirement" >&2
FAILED=$((FAILED + 1))
continue
fi
CHECKED=$((CHECKED + 1))
if version_gt "$need" "$FLOOR"; then
echo " FAIL $(basename "$pkg") requires glibc $need, above the declared floor $FLOOR" >&2
FAILED=$((FAILED + 1))
else
echo " ok $(basename "$pkg") requires glibc $need"
fi
done
if [ "$FAILED" -ne 0 ]; then
echo "check-rpm-floor: $FAILED check(s) failed against floor $FLOOR." >&2
echo " The package was built from binaries compiled above the floor. Build" >&2
echo " them in the pinned container: packaging/rpm/build-rpm-container.sh." >&2
exit 1
fi
if [ "$CHECKED" -eq 0 ]; then
echo "check-rpm-floor: nothing was checked; refusing to report a pass." >&2
exit 2
fi
echo "=== RPM glibc floor check passed ($CHECKED package(s)) ==="