mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-30 07:37:36 +00:00
build-ipk.sh and build-apk.sh, run without --bin-dir, compiled with cargo zigbuild and then looked for the binaries under target/ in the checkout. A build with CARGO_TARGET_DIR, CARGO_BUILD_TARGET_DIR or a build.target-dir setting in any cargo config puts them elsewhere, so the builders failed after compiling, at `du: .../target/<triple>/release/fips: No such file or directory`. Both now ask `cargo metadata` for the target directory, read with sed so a build host needs no jq or Python, and stop with a clear error when it cannot be read rather than fall back to target/. The apk README describes packaging in an alpine:3.23 container, whose own apk-tools 3.0.8 builds packages OpenWrt 25.12 installs, instead of building apk-tools from source. It passes PKG_VERSION and APK_VERSION from the host, since without git in the container both fall back silently to versions that sort below an installed development build. Both READMEs give the flags that install or upgrade on a router with no internet access (`--no-network --repositories-file /dev/null`). package-test.sh builds both packages without --bin-dir against a cargo stub, once with each environment variable, and checks the packages hold the binaries cargo wrote there.
609 lines
27 KiB
Bash
Executable File
609 lines
27 KiB
Bash
Executable File
#!/bin/bash
|
|
# ── OpenWrt package contents, checked on the host ───────────────────────────
|
|
# Runs the real packaging/openwrt-apk/build-apk.sh against placeholder
|
|
# binaries and a stub `apk` that records what `apk mkpkg` was asked to
|
|
# package. No docker, no apk-tools and no FIPS build are needed.
|
|
#
|
|
# What it checks is which maintainer scripts the .apk registers and what each
|
|
# one does with apk's upgrade arguments and environment (apk-tools v3 passes
|
|
# "<new-version> <old-version>" and a PATH-only environment to pre-upgrade and
|
|
# post-upgrade), and which files the .apk and the .ipk install. The .ipk is
|
|
# built for real by build-ipk.sh, which needs only tar. Whether a real
|
|
# `apk mkpkg` accepts the result is left to the GitHub packaging workflow,
|
|
# which builds with the real tool.
|
|
#
|
|
# Usage: package-test.sh [--keep <dir>]
|
|
# --keep <dir> copy the captured apk scripts into <dir> as post-install,
|
|
# pre-upgrade, post-upgrade and pre-deinstall, so
|
|
# scenarios.sh can run them under ash, and extract the .ipk's
|
|
# payload into <dir>/ipk-root for firewall-scenarios.sh.
|
|
#
|
|
# Exit 0 = every check passed. Exit 1 = at least one failed. Exit 2 = the
|
|
# harness could not run; never treated as a pass.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
SCRIPTS_SRC="$PROJECT_ROOT/packaging/openwrt-ipk/scripts"
|
|
|
|
KEEP=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--keep)
|
|
[[ $# -ge 2 ]] || { echo "package-test: --keep needs a directory" >&2; exit 2; }
|
|
KEEP="$2"
|
|
shift 2
|
|
;;
|
|
*) echo "package-test: unknown argument: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
if [[ -n "$KEEP" && ! -d "$KEEP" ]]; then
|
|
echo "package-test: --keep needs an existing directory" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# The version is unique to this run so the cleanup below removes only what
|
|
# this run's builders wrote into dist/.
|
|
PKG_VERSION="pkgtest.$$"
|
|
TMP="$(mktemp -d)" || { echo "package-test: mktemp failed" >&2; exit 2; }
|
|
|
|
trap 'rm -rf "$TMP"; rm -f "$PROJECT_ROOT/dist/fips_${PKG_VERSION}"[_~]*' EXIT
|
|
|
|
harness_fail() {
|
|
echo "package-test: $*" >&2
|
|
exit 2
|
|
}
|
|
|
|
FAILURES=0
|
|
CASES=0
|
|
|
|
ok() {
|
|
CASES=$((CASES + 1))
|
|
echo " ok $*"
|
|
return 0
|
|
}
|
|
|
|
bad() {
|
|
CASES=$((CASES + 1))
|
|
FAILURES=$((FAILURES + 1))
|
|
echo " FAIL $*"
|
|
return 0
|
|
}
|
|
|
|
# ── Build the .apk against a stub apk ───────────────────────────────────────
|
|
|
|
BINS="$TMP/bins"
|
|
CAPTURE="$TMP/capture"
|
|
mkdir -p "$BINS" "$CAPTURE" || harness_fail "cannot create $TMP subdirectories"
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
printf 'x' > "$BINS/$bin" || harness_fail "cannot write placeholder $bin"
|
|
done
|
|
|
|
# The stub knows only the arguments build-apk.sh passes today. Anything else
|
|
# exits 64, so a new mkpkg argument fails the build rather than going unseen.
|
|
cat > "$TMP/apk" <<STUB
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
cap='$CAPTURE'
|
|
[ "\${1-}" = mkpkg ] || { echo "stub apk: only mkpkg is supported, got '\${1-}'" >&2; exit 64; }
|
|
shift
|
|
files=""
|
|
out=""
|
|
while [ \$# -gt 0 ]; do
|
|
[ \$# -ge 2 ] || { echo "stub apk: \$1 has no value" >&2; exit 64; }
|
|
case "\$1" in
|
|
--info) ;;
|
|
--script)
|
|
phase="\${2%%:*}"
|
|
cp "\${2#*:}" "\$cap/script.\$phase"
|
|
echo "\$phase" >> "\$cap/phases"
|
|
;;
|
|
--files) files="\$2" ;;
|
|
--output) out="\$2" ;;
|
|
*) echo "stub apk: unknown argument \$1" >&2; exit 64 ;;
|
|
esac
|
|
shift 2
|
|
done
|
|
[ -n "\$files" ] && [ -n "\$out" ] || { echo "stub apk: --files and --output are required" >&2; exit 64; }
|
|
(cd "\$files" && find . -mindepth 1 | LC_ALL=C sort) > "\$cap/payload"
|
|
cp "\$files/usr/bin/fips" "\$cap/fips-bin" 2>/dev/null || true
|
|
: > "\$out"
|
|
: > "\$cap/called"
|
|
STUB
|
|
chmod 0755 "$TMP/apk" || harness_fail "cannot make the stub apk executable"
|
|
|
|
echo "OpenWrt package checks"
|
|
echo "==> build-apk.sh with a stub apk"
|
|
if ! PKG_VERSION="$PKG_VERSION" APK_VERSION=0.0.0-r0 APK_BIN="$TMP/apk" \
|
|
bash "$PROJECT_ROOT/packaging/openwrt-apk/build-apk.sh" --arch x86_64 --bin-dir "$BINS" \
|
|
> "$TMP/build-apk.log" 2>&1; then
|
|
cat "$TMP/build-apk.log" >&2
|
|
harness_fail "build-apk.sh failed, so nothing was checked"
|
|
fi
|
|
[[ -f "$CAPTURE/called" ]] || harness_fail "build-apk.sh exited 0 but never ran apk mkpkg"
|
|
[[ -f "$CAPTURE/phases" ]] || harness_fail "apk mkpkg ran but no script phase was recorded"
|
|
|
|
# ── A1. The .apk registers exactly the four lifecycle scripts ───────────────
|
|
# apk runs only post-install on a fresh install, only pre-upgrade and
|
|
# post-upgrade on an upgrade, and only pre-deinstall on a removal.
|
|
WANT_PHASES="post-install post-upgrade pre-deinstall pre-upgrade"
|
|
got_phases="$(LC_ALL=C sort "$CAPTURE/phases" | tr '\n' ' ')"
|
|
got_phases="${got_phases% }"
|
|
if [[ "$got_phases" == "$WANT_PHASES" ]]; then
|
|
ok "A1 the .apk registers $WANT_PHASES"
|
|
else
|
|
missing=""
|
|
for phase in $WANT_PHASES; do
|
|
grep -qxF "$phase" "$CAPTURE/phases" || missing="$missing $phase"
|
|
done
|
|
bad "A1 registered phases are '$got_phases', want '$WANT_PHASES'; missing:${missing:- none}"
|
|
fi
|
|
|
|
# ── A2. Every registered script starts with a working #! line ───────────────
|
|
# apk execs the script directly, so the kernel reads line 1.
|
|
for phase in $WANT_PHASES; do
|
|
script="$CAPTURE/script.$phase"
|
|
if [[ ! -f "$script" ]]; then
|
|
bad "A2 $phase is not registered, so it has no #! line"
|
|
elif [[ "$(head -n 1 "$script")" == "#!/bin/sh" ]]; then
|
|
ok "A2 $phase starts with #!/bin/sh"
|
|
else
|
|
bad "A2 $phase starts with '$(head -n 1 "$script")', not #!/bin/sh"
|
|
fi
|
|
done
|
|
|
|
# ── A3. Install and removal ship the shared bodies unchanged ────────────────
|
|
for pair in post-install:postinst pre-deinstall:prerm; do
|
|
phase="${pair%%:*}"
|
|
src="$SCRIPTS_SRC/${pair#*:}"
|
|
if cmp -s "$CAPTURE/script.$phase" "$src"; then
|
|
ok "A3 $phase is the shipped ${pair#*:}, byte for byte"
|
|
else
|
|
bad "A3 $phase differs from the shipped ${pair#*:}"
|
|
fi
|
|
done
|
|
|
|
# ── A4 and A5. The upgrade pair wraps the shared bodies ─────────────────────
|
|
# A4 reads the structure: the script ends with the body after its #! line, and
|
|
# at least one header line sits between the two. A5 runs the header with apk's
|
|
# upgrade argv and environment, so what is judged is what the shell does with
|
|
# it, not how it reads.
|
|
for pair in pre-upgrade:prerm post-upgrade:postinst; do
|
|
phase="${pair%%:*}"
|
|
src="$SCRIPTS_SRC/${pair#*:}"
|
|
script="$CAPTURE/script.$phase"
|
|
if [[ ! -f "$script" ]]; then
|
|
bad "A4 $phase is not registered"
|
|
bad "A5 $phase is not registered, so its header cannot run"
|
|
continue
|
|
fi
|
|
|
|
tail -n +2 "$src" > "$TMP/body" || harness_fail "cannot read $src"
|
|
body_lines=$(wc -l < "$TMP/body")
|
|
script_lines=$(wc -l < "$script")
|
|
header_lines=$((script_lines - body_lines))
|
|
if [[ $header_lines -lt 2 ]]; then
|
|
bad "A4 $phase has $header_lines header line(s) before the ${pair#*:} body, want at least 2"
|
|
elif ! tail -n "$body_lines" "$script" | cmp -s - "$TMP/body"; then
|
|
bad "A4 $phase does not end with the ${pair#*:} body"
|
|
else
|
|
ok "A4 $phase is a $header_lines-line header and the ${pair#*:} body"
|
|
fi
|
|
|
|
if [[ $header_lines -lt 1 ]]; then
|
|
bad "A5 $phase has no header to run"
|
|
continue
|
|
fi
|
|
probe="$TMP/probe.$phase"
|
|
{
|
|
head -n "$header_lines" "$script"
|
|
# shellcheck disable=SC2016
|
|
printf '%s\n' 'printf '\''%s|%s|%s\n'\'' "${1-}" "${2-}" "${PKG_UPGRADE-}"'
|
|
} > "$probe"
|
|
chmod 0755 "$probe" || harness_fail "cannot make the $phase probe executable"
|
|
got="$(env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin "$probe" 0.6.0-r1 0.5.2-r1)"
|
|
rc=$?
|
|
if [[ $rc -eq 126 || $rc -eq 127 ]]; then
|
|
harness_fail "the $phase probe could not be executed (exit $rc)"
|
|
fi
|
|
IFS='|' read -r f1 f2 f3 <<< "$got"
|
|
case "$phase" in
|
|
pre-upgrade)
|
|
if [[ $rc -eq 0 && "$f1|$f2" == "upgrade|0.6.0-r1" ]]; then
|
|
ok "A5 pre-upgrade hands the body 'upgrade 0.6.0-r1'"
|
|
else
|
|
bad "A5 pre-upgrade hands the body '$f1 $f2' (exit $rc), want 'upgrade 0.6.0-r1'"
|
|
fi
|
|
;;
|
|
post-upgrade)
|
|
if [[ $rc -eq 0 && "$f3" == "1" ]]; then
|
|
ok "A5 post-upgrade runs the body with PKG_UPGRADE=1"
|
|
else
|
|
bad "A5 post-upgrade runs the body with PKG_UPGRADE='$f3' (exit $rc), want 1"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ── Build the .ipk ──────────────────────────────────────────────────────────
|
|
|
|
echo "==> build-ipk.sh"
|
|
# The first build is V1's "IPK_VERSION unset" case, so it must not inherit an
|
|
# IPK_VERSION from the caller's environment.
|
|
if ! env -u IPK_VERSION PKG_VERSION="$PKG_VERSION" \
|
|
bash "$PROJECT_ROOT/packaging/openwrt-ipk/build-ipk.sh" --arch x86_64 --bin-dir "$BINS" \
|
|
> "$TMP/build-ipk.log" 2>&1; then
|
|
cat "$TMP/build-ipk.log" >&2
|
|
harness_fail "build-ipk.sh failed, so the .ipk was not checked"
|
|
fi
|
|
IPK="$PROJECT_ROOT/dist/fips_${PKG_VERSION}_x86_64.ipk"
|
|
[[ -f "$IPK" ]] || harness_fail "build-ipk.sh exited 0 but wrote no $IPK"
|
|
tar -xzf "$IPK" -O ./data.tar.gz | tar -tzf - > "$TMP/ipk-data" \
|
|
|| harness_fail "cannot list data.tar.gz in $IPK"
|
|
tar -xzf "$IPK" -O ./control.tar.gz | tar -tzf - > "$TMP/ipk-control" \
|
|
|| harness_fail "cannot list control.tar.gz in $IPK"
|
|
|
|
# Print the Version field of an .ipk's control file.
|
|
ipk_version() {
|
|
tar -xzf "$1" -O ./control.tar.gz | tar -xzOf - ./control \
|
|
| sed -n 's/^Version: //p'
|
|
}
|
|
|
|
# ── V1 and V2. The control Version comes from IPK_VERSION ───────────────────
|
|
# The workflow passes a candidate's opkg-sortable version (vX.Y.Z~rcN) as
|
|
# IPK_VERSION and keeps the tag's form in PKG_VERSION for the file name, since
|
|
# a GitHub release renames an asset with '~' in its name. Unset, IPK_VERSION
|
|
# falls back to PKG_VERSION, as a local build expects.
|
|
got="$(ipk_version "$IPK")" || harness_fail "cannot read the control file in $IPK"
|
|
if [[ "$got" == "$PKG_VERSION" ]]; then
|
|
ok "V1 with IPK_VERSION unset the control Version is PKG_VERSION ($got)"
|
|
else
|
|
bad "V1 with IPK_VERSION unset the control Version is '$got', want $PKG_VERSION"
|
|
fi
|
|
# The first build's package is removed so a second build that names its file
|
|
# from anything but PKG_VERSION cannot be passed by reading the stale one.
|
|
RC_VERSION="${PKG_VERSION}~rc1"
|
|
rm -f "$IPK" || harness_fail "cannot remove $IPK before the second build"
|
|
if ! PKG_VERSION="$PKG_VERSION" IPK_VERSION="$RC_VERSION" \
|
|
bash "$PROJECT_ROOT/packaging/openwrt-ipk/build-ipk.sh" --arch x86_64 --bin-dir "$BINS" \
|
|
> "$TMP/build-ipk-rc.log" 2>&1; then
|
|
cat "$TMP/build-ipk-rc.log" >&2
|
|
harness_fail "build-ipk.sh failed with IPK_VERSION set"
|
|
fi
|
|
if [[ ! -f "$IPK" ]]; then
|
|
bad "V2 with IPK_VERSION set build-ipk.sh wrote no $IPK; the file name must come from PKG_VERSION"
|
|
else
|
|
got="$(ipk_version "$IPK")" || harness_fail "cannot read the control file in $IPK"
|
|
if [[ "$got" == "$RC_VERSION" ]]; then
|
|
ok "V2 with IPK_VERSION set the control Version is IPK_VERSION ($got), file named from PKG_VERSION"
|
|
else
|
|
bad "V2 with IPK_VERSION set the control Version is '$got', want $RC_VERSION"
|
|
fi
|
|
fi
|
|
|
|
# ── P1 and P2. Neither package ships the dnsmasq drop-in ────────────────────
|
|
# OpenWrt's dnsmasq builds its config from UCI and reads no directory under
|
|
# /etc, so .fips forwarding comes from the UCI entry 90-fips-setup adds. The
|
|
# match is on the directory prefix: tar lists the directory with a trailing
|
|
# slash and the stub's find without one.
|
|
for pair in "P1:apk:$CAPTURE/payload" "P2:ipk:$TMP/ipk-data"; do
|
|
id="${pair%%:*}"
|
|
rest="${pair#*:}"
|
|
kind="${rest%%:*}"
|
|
listing="${rest#*:}"
|
|
hits="$(grep -F './etc/dnsmasq.d' "$listing" | tr '\n' ' ')"
|
|
if [[ -z "$hits" ]]; then
|
|
ok "$id the .$kind installs nothing under /etc/dnsmasq.d"
|
|
else
|
|
bad "$id the .$kind still installs: $hits"
|
|
fi
|
|
done
|
|
|
|
# ── P3. Positive control for P1 and P2 ──────────────────────────────────────
|
|
# An empty or unreadable listing would pass P1 and P2, so each listing must
|
|
# show files that are known to ship.
|
|
for pair in "apk:$CAPTURE/payload" "ipk:$TMP/ipk-data"; do
|
|
kind="${pair%%:*}"
|
|
listing="${pair#*:}"
|
|
for path in ./etc/init.d/fips-gateway ./etc/uci-defaults/90-fips-setup; do
|
|
if grep -qxF "$path" "$listing"; then
|
|
ok "P3 the .$kind payload lists $path"
|
|
else
|
|
bad "P3 the .$kind payload does not list $path, so P1/P2 saw no real listing"
|
|
fi
|
|
done
|
|
done
|
|
for path in ./postinst ./prerm; do
|
|
if grep -qxF "$path" "$TMP/ipk-control"; then
|
|
ok "P3 the .ipk control archive lists $path"
|
|
else
|
|
bad "P3 the .ipk control archive does not list $path"
|
|
fi
|
|
done
|
|
|
|
# ── P4. No source still names the drop-in ───────────────────────────────────
|
|
# With P5, one of the two checks on the SDK feed Makefile, which nothing here
|
|
# builds.
|
|
# grep exits 1 when nothing matches and 2 when it could not read a path; only
|
|
# the first is a pass.
|
|
(cd "$PROJECT_ROOT" && grep -rlF 'dnsmasq.d/fips.conf' \
|
|
packaging/openwrt-ipk packaging/openwrt-apk .github/workflows/package-openwrt.yml) \
|
|
> "$TMP/refs"
|
|
rc=$?
|
|
[[ $rc -le 1 ]] || harness_fail "the drop-in reference search failed (grep exit $rc)"
|
|
refs="$(tr '\n' ' ' < "$TMP/refs")"
|
|
if [[ -z "$refs" ]]; then
|
|
ok "P4 no OpenWrt packaging file names the dnsmasq drop-in"
|
|
else
|
|
bad "P4 the dnsmasq drop-in is still named in: $refs"
|
|
fi
|
|
if [[ -e "$PROJECT_ROOT/packaging/openwrt-ipk/files/etc/dnsmasq.d/fips.conf" ]]; then
|
|
bad "P4 packaging/openwrt-ipk/files/etc/dnsmasq.d/fips.conf still exists"
|
|
else
|
|
ok "P4 the drop-in source file is gone"
|
|
fi
|
|
|
|
# ── P5. The SDK feed Makefile ships the same maintainer scripts ─────────────
|
|
# Read, not built: building it needs the OpenWrt SDK. Each script must be the
|
|
# file in scripts/ that the scenarios run, and fips.yaml must be a conffile.
|
|
MAKEFILE="$PROJECT_ROOT/packaging/openwrt-ipk/Makefile"
|
|
[[ -r "$MAKEFILE" ]] || harness_fail "cannot read $MAKEFILE"
|
|
for script in preinst postinst prerm; do
|
|
if awk -v want="define Package/fips/$script" -v body="\$(file < \$(CURDIR)/scripts/$script)" '
|
|
$0 == want { inside = 1; next }
|
|
inside && $0 == body { found = 1 }
|
|
inside && $0 == "endef" { inside = 0 }
|
|
END { exit !found }' "$MAKEFILE"; then
|
|
ok "P5 the SDK Makefile's $script is scripts/$script"
|
|
else
|
|
bad "P5 the SDK Makefile does not define Package/fips/$script as scripts/$script"
|
|
fi
|
|
done
|
|
if awk '$0 == "define Package/fips/conffiles" { inside = 1; next }
|
|
inside && $0 == "/etc/fips/fips.yaml" { found = 1 }
|
|
inside && $0 == "endef" { inside = 0 }
|
|
END { exit !found }' "$MAKEFILE"; then
|
|
ok "P5 the SDK Makefile lists /etc/fips/fips.yaml as a conffile"
|
|
else
|
|
bad "P5 the SDK Makefile does not list /etc/fips/fips.yaml as a conffile"
|
|
fi
|
|
|
|
# ── P6. The firewall files the packages carry ──────────────────────────────
|
|
# fips0 has a firewall zone of its own, so the old accept-everything script and
|
|
# its hotplug hook are gone, the fw4 include that lets fips-gateway's port
|
|
# forwards through the zone is installed for both of its chains, and the one
|
|
# that refuses ICMPv6 from wan into the mesh heads the wan zone's forward
|
|
# chain. The includes' sources sit outside their install paths, so neither
|
|
# package may carry those paths.
|
|
SNIPPET=10-fips-port-forwards.nft
|
|
SNIPPET_PATHS="./usr/share/nftables.d/chain-post/input_fips/$SNIPPET ./usr/share/nftables.d/chain-post/forward_fips/$SNIPPET"
|
|
WANSNIP=10-fips-wan-icmpv6.nft
|
|
WANSNIP_PATH="./usr/share/nftables.d/chain-pre/forward_wan/$WANSNIP"
|
|
for pair in "apk:$CAPTURE/payload" "ipk:$TMP/ipk-data"; do
|
|
kind="${pair%%:*}"
|
|
listing="${pair#*:}"
|
|
for path in ./etc/fips/firewall.sh ./etc/hotplug.d/net/99-fips "./usr/share/nftables.d/$SNIPPET" "./usr/share/nftables.d/$WANSNIP"; do
|
|
if grep -qxF "$path" "$listing"; then
|
|
bad "P6 the .$kind still installs $path"
|
|
else
|
|
ok "P6 the .$kind does not install $path"
|
|
fi
|
|
done
|
|
for path in $SNIPPET_PATHS $WANSNIP_PATH; do
|
|
if grep -qxF "$path" "$listing"; then
|
|
ok "P6 the .$kind installs $path"
|
|
else
|
|
bad "P6 the .$kind does not install $path"
|
|
fi
|
|
done
|
|
done
|
|
# The include is spliced into both chains of the fips zone, so any rule beside
|
|
# the port-forward accept would apply to every FIPS peer. Its one rule is that
|
|
# accept, and the .ipk installs it unchanged for both chains. A missing source
|
|
# file is a packaging defect, so it fails its checks rather than the harness.
|
|
SNIPPET_SRC="$PROJECT_ROOT/packaging/openwrt-ipk/files/usr/share/nftables.d/$SNIPPET"
|
|
SNIPPET_RULE='ct status dnat accept comment "fips: accept fips-gateway port forwards"'
|
|
WANSNIP_SRC="$PROJECT_ROOT/packaging/openwrt-ipk/files/usr/share/nftables.d/$WANSNIP"
|
|
WANSNIP_RULE='oifname "fips0" meta l4proto ipv6-icmp counter reject comment "fips: no ICMPv6 from wan into the mesh"'
|
|
# The wan include runs for every packet forwarded from wan, so it holds its
|
|
# one reject and nothing else.
|
|
for spec in "port-forward|one accept|$SNIPPET_SRC|$SNIPPET_RULE" \
|
|
"wan|one ICMPv6 reject|$WANSNIP_SRC|$WANSNIP_RULE"; do
|
|
IFS='|' read -r name what src want <<< "$spec"
|
|
if [[ ! -e "$src" ]]; then
|
|
bad "P6 the $name include's source $src is missing"
|
|
continue
|
|
fi
|
|
[[ -r "$src" ]] || harness_fail "cannot read $src"
|
|
rules="$(grep -vE '^[[:space:]]*(#|$)' "$src")"
|
|
if [[ "$rules" == "$want" ]]; then
|
|
ok "P6 the $name include holds the $what and no other rule"
|
|
else
|
|
bad "P6 the $name include holds other rules than the $what: $(printf '%s' "$rules" | tr '\n' '|')"
|
|
fi
|
|
done
|
|
for path in $SNIPPET_PATHS $WANSNIP_PATH; do
|
|
src="$SNIPPET_SRC"
|
|
[[ "$path" == "$WANSNIP_PATH" ]] && src="$WANSNIP_SRC"
|
|
# Each of these already failed above.
|
|
if [[ ! -e "$src" ]] || ! grep -qxF "$path" "$TMP/ipk-data"; then
|
|
bad "P6 the .ipk's $path cannot be compared with the packaged include"
|
|
continue
|
|
fi
|
|
tar -xzf "$IPK" -O ./data.tar.gz | tar -xzOf - "$path" > "$TMP/snippet" \
|
|
|| harness_fail "cannot extract $path from $IPK"
|
|
if cmp -s "$TMP/snippet" "$src"; then
|
|
ok "P6 the .ipk's $path is the packaged include"
|
|
else
|
|
bad "P6 the .ipk's $path differs from the packaged include"
|
|
fi
|
|
done
|
|
# grep exits 1 when nothing matches and 2 when it could not read a path; only
|
|
# the first is a pass. 90-fips-setup and the READMEs name the old installed
|
|
# paths on purpose, so they are not searched.
|
|
(cd "$PROJECT_ROOT" || exit 2; grep -lE 'etc/fips/firewall\.sh|etc/hotplug\.d/net/99-fips' \
|
|
packaging/openwrt-ipk/build-ipk.sh packaging/openwrt-apk/build-apk.sh \
|
|
packaging/openwrt-ipk/Makefile .github/workflows/package-openwrt.yml \
|
|
testing/check-shellcheck.sh) > "$TMP/fwrefs"
|
|
rc=$?
|
|
[[ $rc -le 1 ]] || harness_fail "the old firewall file search failed (grep exit $rc)"
|
|
refs="$(tr '\n' ' ' < "$TMP/fwrefs")"
|
|
if [[ -z "$refs" ]]; then
|
|
ok "P6 no builder, workflow or shellcheck list names the old firewall files"
|
|
else
|
|
bad "P6 the old firewall files are still named in: $refs"
|
|
fi
|
|
WORKFLOW="$PROJECT_ROOT/.github/workflows/package-openwrt.yml"
|
|
[[ -r "$WORKFLOW" ]] || harness_fail "cannot read $WORKFLOW"
|
|
for path in $SNIPPET_PATHS $WANSNIP_PATH; do
|
|
if grep -qE "^[[:space:]]*${path//./\\.}\$" "$WORKFLOW"; then
|
|
ok "P6 the workflow's .ipk list names $path"
|
|
else
|
|
bad "P6 the workflow's .ipk list does not name $path"
|
|
fi
|
|
bare="${path#./}"
|
|
if grep -qE "(^|[[:space:]])${bare//./\\.}([[:space:]]|\$)" "$WORKFLOW"; then
|
|
ok "P6 the workflow's .apk list names $bare"
|
|
else
|
|
bad "P6 the workflow's .apk list does not name $bare"
|
|
fi
|
|
done
|
|
|
|
# ── P7. The SDK feed Makefile installs the same firewall files ──────────────
|
|
# Read, not built, like P5.
|
|
for chain in input_fips forward_fips; do
|
|
want="\$(INSTALL_DATA) \$(CURDIR)/files/usr/share/nftables.d/$SNIPPET \$(1)/usr/share/nftables.d/chain-post/$chain/$SNIPPET"
|
|
if awk -v want="$want" '
|
|
$0 == "define Package/fips/install" { inside = 1; next }
|
|
inside && $0 == "endef" { inside = 0 }
|
|
inside { sub(/^[[:space:]]+/, ""); if ($0 == want) found = 1 }
|
|
END { exit !found }' "$MAKEFILE"; then
|
|
ok "P7 the SDK Makefile installs the port-forward include for $chain"
|
|
else
|
|
bad "P7 the SDK Makefile does not install the port-forward include for $chain"
|
|
fi
|
|
done
|
|
want="\$(INSTALL_DATA) \$(CURDIR)/files/usr/share/nftables.d/$WANSNIP \$(1)/usr/share/nftables.d/chain-pre/forward_wan/$WANSNIP"
|
|
if awk -v want="$want" '
|
|
$0 == "define Package/fips/install" { inside = 1; next }
|
|
inside && $0 == "endef" { inside = 0 }
|
|
inside { sub(/^[[:space:]]+/, ""); if ($0 == want) found = 1 }
|
|
END { exit !found }' "$MAKEFILE"; then
|
|
ok "P7 the SDK Makefile installs the wan include for forward_wan"
|
|
else
|
|
bad "P7 the SDK Makefile does not install the wan include for forward_wan"
|
|
fi
|
|
if grep -qE 'etc/fips/firewall\.sh|hotplug\.d/net/99-fips' "$MAKEFILE"; then
|
|
bad "P7 the SDK Makefile still names firewall.sh or 99-fips"
|
|
else
|
|
ok "P7 the SDK Makefile names neither firewall.sh nor 99-fips"
|
|
fi
|
|
|
|
# ── Hand the scripts and the payload to the ash scenarios ───────────────────
|
|
if [[ -n "$KEEP" ]]; then
|
|
for phase in $WANT_PHASES; do
|
|
[[ -f "$CAPTURE/script.$phase" ]] || continue
|
|
install -m 0755 "$CAPTURE/script.$phase" "$KEEP/$phase" \
|
|
|| harness_fail "cannot copy $phase into $KEEP"
|
|
done
|
|
mkdir -p "$KEEP/ipk-root" || harness_fail "cannot create $KEEP/ipk-root"
|
|
tar -xzf "$IPK" -O ./data.tar.gz | tar -xzf - -C "$KEEP/ipk-root" \
|
|
|| harness_fail "cannot extract the .ipk payload into $KEEP/ipk-root"
|
|
fi
|
|
|
|
# ── T1 and T2. Without --bin-dir, the builders find cargo's target directory ─
|
|
# CARGO_TARGET_DIR, CARGO_BUILD_TARGET_DIR and build.target-dir move the
|
|
# binaries out of target/. This job has no Rust toolchain, so a cargo stub
|
|
# stands in: for zigbuild it writes placeholders under the directory either
|
|
# variable names, and for metadata it reports that directory as
|
|
# target_directory; anything else exits 64. With neither variable set, the
|
|
# stub takes the directory from STUB_CONFIG_TARGET_DIR, which only it reads.
|
|
# That stands in for build.target-dir in a config.toml, the reported case: a
|
|
# builder that reads the two variables instead of asking cargo fails it.
|
|
# The stub is first on PATH and handles zigbuild itself, since cargo looks
|
|
# for a subcommand in $CARGO_HOME/bin before PATH and would find a real
|
|
# cargo-zigbuild there. The cargo-zigbuild and rustup stubs only satisfy the
|
|
# builders' tool checks. What the stub cannot show, the real cargo
|
|
# metadata's answer, was measured once by hand.
|
|
STUBS="$TMP/stubs"
|
|
mkdir -p "$STUBS" || harness_fail "cannot create $STUBS"
|
|
cat > "$STUBS/cargo" <<'STUB'
|
|
#!/bin/bash
|
|
dir="${CARGO_TARGET_DIR:-${CARGO_BUILD_TARGET_DIR:-${STUB_CONFIG_TARGET_DIR:-}}}"
|
|
[ -n "$dir" ] || { echo "stub cargo: no target directory configured" >&2; exit 64; }
|
|
case "${1-}" in
|
|
zigbuild)
|
|
target=""
|
|
while [ $# -gt 0 ]; do
|
|
[ "$1" = --target ] && target="${2-}"
|
|
shift
|
|
done
|
|
[ -n "$target" ] || { echo "stub cargo: zigbuild without --target" >&2; exit 64; }
|
|
mkdir -p "$dir/$target/release" || exit 1
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
printf 'stub-%s' "$bin" > "$dir/$target/release/$bin" || exit 1
|
|
done
|
|
;;
|
|
metadata)
|
|
printf '{"packages":[],"target_directory":"%s","version":1,"workspace_root":"/x"}\n' "$dir"
|
|
;;
|
|
*) echo "stub cargo: unsupported command '${1-}'" >&2; exit 64 ;;
|
|
esac
|
|
STUB
|
|
printf '#!/bin/sh\nexit 0\n' > "$STUBS/cargo-zigbuild"
|
|
printf '#!/bin/sh\n[ "$*" = "target list --installed" ] || exit 64\necho x86_64-unknown-linux-musl\n' \
|
|
> "$STUBS/rustup"
|
|
chmod 0755 "$STUBS/cargo" "$STUBS/cargo-zigbuild" "$STUBS/rustup" \
|
|
|| harness_fail "cannot make the cargo stubs executable"
|
|
|
|
# A version of its own, so these builds never overwrite the package --keep
|
|
# extracted, and the exit trap's pattern still removes what they write.
|
|
TD_VERSION="${PKG_VERSION}~td"
|
|
for var in CARGO_TARGET_DIR CARGO_BUILD_TARGET_DIR STUB_CONFIG_TARGET_DIR; do
|
|
tdir="$TMP/target-$var"
|
|
how="$var"
|
|
[[ "$var" == STUB_CONFIG_TARGET_DIR ]] && how="build.target-dir (neither variable set)"
|
|
for kind in ipk apk; do
|
|
log="$TMP/build-$kind-$var.log"
|
|
rm -f "$CAPTURE/fips-bin"
|
|
if [[ "$kind" == ipk ]]; then
|
|
id=T1
|
|
env -u CARGO_TARGET_DIR -u CARGO_BUILD_TARGET_DIR -u IPK_VERSION \
|
|
PATH="$STUBS:$PATH" LLVM_STRIP=true PKG_VERSION="$TD_VERSION" "$var=$tdir" \
|
|
bash "$PROJECT_ROOT/packaging/openwrt-ipk/build-ipk.sh" --arch x86_64 > "$log" 2>&1
|
|
else
|
|
id=T2
|
|
env -u CARGO_TARGET_DIR -u CARGO_BUILD_TARGET_DIR \
|
|
PATH="$STUBS:$PATH" LLVM_STRIP=true PKG_VERSION="$TD_VERSION" APK_VERSION=0.0.0-r0 \
|
|
APK_BIN="$TMP/apk" "$var=$tdir" \
|
|
bash "$PROJECT_ROOT/packaging/openwrt-apk/build-apk.sh" --arch x86_64 > "$log" 2>&1
|
|
fi
|
|
rc=$?
|
|
if [[ $rc -ne 0 ]]; then
|
|
bad "$id build-$kind.sh with $how and no --bin-dir exited $rc: $(grep -m1 -iE 'error|no such file' "$log")"
|
|
continue
|
|
fi
|
|
if [[ "$kind" == ipk ]]; then
|
|
got="$(tar -xzf "$PROJECT_ROOT/dist/fips_${TD_VERSION}_x86_64.ipk" -O ./data.tar.gz \
|
|
| tar -xzOf - ./usr/bin/fips 2>/dev/null)"
|
|
else
|
|
got="$(cat "$CAPTURE/fips-bin" 2>/dev/null)"
|
|
fi
|
|
if [[ "$got" == stub-fips ]]; then
|
|
ok "$id build-$kind.sh with $how and no --bin-dir packages the binaries cargo wrote there"
|
|
else
|
|
bad "$id build-$kind.sh with $how packaged /usr/bin/fips '${got:0:20}', not the binary cargo wrote under $tdir"
|
|
fi
|
|
done
|
|
done
|
|
rm -f "$PROJECT_ROOT/dist/fips_${TD_VERSION}"_*
|
|
|
|
echo ""
|
|
if [[ $FAILURES -eq 0 ]]; then
|
|
echo "package-test: all $CASES checks passed"
|
|
exit 0
|
|
fi
|
|
echo "package-test: $FAILURES of $CASES checks failed"
|
|
exit 1
|