mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-30 07:37:36 +00:00
The startup-stop scenarios in the gateway suite, the OpenWrt maintainer script scenarios and the Debian package build and depends check started containers with no CI label at all. ci-cleanup.sh selects containers by label or compose project, never by name, so a container from one of these sites that outlived a killed run was reaped by nothing: not the run's own teardown, and not the builder's broad sweep. Each site now carries the generic and run labels, through CI_LABELS where the script sources testing/lib/image-build.sh and as the literal pair in startup-stop-test.sh, which sources nothing. Outside local CI the run label reads "manual", which no teardown reaps by run. ci-cleanup.sh's two ip(8) helpers stay unlabelled on purpose: the builder skips its orphan sweep while any generic-labelled container is up, so a labelled helper would let one run's teardown hold off that sweep. Each is marked with a "# ci-labels:" comment on the command's last line, ready for the label check to read as an exemption; on the first line it would follow the continuation backslash and end the command there. check-ci-labels.py failed a docker command that gave the generic CI label without the run label, but passed one that carried no label at all, so a new container site written without labels passed every repository check. Such a container is worse off than a generic-labelled one: ci-cleanup.sh selects by label or compose project, never by name, so nothing reaps it. Rule 3 now fails a docker run, create, container run, container create, network create or volume create under testing/ or packaging/ that carries no run label, unless rule 1 already reported the line. It reads shell words, so a quoted error message naming "docker network create" is not a command, while a command after a separator, behind timeout or "if !", or inside a $(...) or backtick substitution, quoted or not, is still found. Builds stay out: shared images keep fixed tags and no labels by design. A container that must stay unlabelled takes a trailing "# ci-labels: <reason>" comment. Rule 4 fails one with no reason, and one placed after a continuation backslash, where the comment would end the command on that line and run the rest as a separate command. File arguments are now scoped by their path relative to the work tree, so testing an uncommitted copy reads it as the sweep would. Docker's short -l flag is read as a label, and the self-test carries a red or a green for each new shape.
174 lines
8.0 KiB
Bash
Executable File
174 lines
8.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# ── OpenWrt maintainer-script scenarios ─────────────────────────────────────
|
|
# Runs testing/openwrt/scenarios.sh inside a busybox container, so the package
|
|
# scripts and the fips-gateway init script are interpreted by ash rather than
|
|
# by the host's bash or dash. The scripts ship to routers and are only ever run
|
|
# under ash there; a construct bash accepts and ash does not would otherwise
|
|
# surface on a router.
|
|
#
|
|
# Then runs testing/openwrt/firewall-scenarios.sh in four pinned OpenWrt
|
|
# rootfs images, which carry the real uci, fw4 and nft, so 90-fips-setup's
|
|
# firewall zone and upgrade migration, and fips-mesh-setup's mesh zone, run
|
|
# against the firewall they configure.
|
|
# Then runs testing/openwrt/init-scenarios.sh in the same four images, where
|
|
# the fips-gateway init script runs under the image's own rc.common, procd.sh,
|
|
# flock and uci, against a stub gateway, with no network.
|
|
# The firewall containers get NET_ADMIN so fw4 can load its table, into the
|
|
# container's own network namespace; they have no network otherwise. Each also
|
|
# gets a small tmpfs, where the cases that save onto a full overlay put
|
|
# /etc/config.
|
|
#
|
|
# No FIPS binary and no shared test image are used.
|
|
#
|
|
# Exit 0 = every scenario passed. Exit 1 = at least one failed. Exit 2 = the
|
|
# harness could not run; never treated as a pass. The firewall runs go ahead
|
|
# when the busybox run failed, and the worst code of all the runs is returned.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
# Pinned by digest so the shell under test does not change under a run: the
|
|
# 1.37 tag moves with every 1.37.x rebuild. This is the multi-arch index digest
|
|
# of busybox:1.37.0 as of 2026-10-01. Bump it deliberately, reading the new
|
|
# digest with `docker buildx imagetools inspect busybox:<version>`.
|
|
# Overridable for trying another ash build.
|
|
IMAGE="${OPENWRT_ASH_IMAGE:-busybox:1.37.0@sha256:bdf57e528e45e4433820e045b29b4597825a1c9e38353532d90a01445013f82e}"
|
|
|
|
# The OpenWrt releases the package supports, each pinned by digest as of
|
|
# 2026-10-10 and read the same way (25.12.5 is published as a single manifest,
|
|
# the others as multi-arch indexes). The two older releases run a subset of the
|
|
# cases to keep the job's time: an upgrade, a fresh install, port-forward
|
|
# scoping, a failed reload and its recovery, an unloaded firewall, and a save
|
|
# onto a full overlay, which matters most on 22.03, whose default input policy
|
|
# is ACCEPT; and for the mesh zone an add, a two-radio remove, a firewall
|
|
# reload during a remove, a failed reload, an upgrade, and a failed reload
|
|
# during an upgrade.
|
|
FW_OLDER_CASES="fw_upgrade_moves_fips0_into_its_own_zone fw_fresh_install_creates_live_zone fw_operator_scoping_renders_before_port_forwards fw_failed_reload_blocks_new_fips0_connections fw_recovery_after_failed_reload_clears_block fw_unloaded_firewall_is_reported fw_full_overlay_save_blocks_and_keeps_setup mesh_add_puts_interface_in_reject_zone mesh_two_radios_share_one_zone_and_remove_cleans_up mesh_remove_keeps_zone_until_interface_is_gone mesh_failed_reload_drops_mesh_input mesh_upgrade_zones_existing_interfaces mesh_hook_failed_reload_drops_mesh_input"
|
|
FW_IMAGES=(
|
|
"openwrt/rootfs:x86-64-25.12.5@sha256:c5d5f05bab4ce06a4e840b3573671a06ec8ae9842273188ffc53f7021836b8e2|"
|
|
"openwrt/rootfs:x86-64-24.10.8@sha256:9972a4b4747cd136abd597475d7b88c51a49fd849d0d53f069a2f4bf446061b9|"
|
|
"openwrt/rootfs:x86-64-23.05.6@sha256:f53afb386d05651ada9a4a964e0779930f30962a2abc2fadf1b03bfcbcbabb14|$FW_OLDER_CASES"
|
|
"openwrt/rootfs:x86-64-22.03.7@sha256:f6e5b98399ae4ff89fc1a2de2da5b94c520c8f82aa1e5c717fb190da8b3313ed|$FW_OLDER_CASES"
|
|
)
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "openwrt-scripts: docker not found; cannot run the ash scenarios" >&2
|
|
exit 2
|
|
fi
|
|
|
|
for f in scenarios.sh firewall-scenarios.sh init-scenarios.sh; do
|
|
if [[ ! -f "$SCRIPT_DIR/$f" ]]; then
|
|
echo "openwrt-scripts: missing $SCRIPT_DIR/$f" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
# Each image is pulled once, with a few attempts, so a registry outage reads as
|
|
# the harness not running rather than as a failed case.
|
|
pull() {
|
|
local attempt
|
|
for attempt in 1 2 3; do
|
|
docker pull -q "$1" >/dev/null && return 0
|
|
echo "openwrt-scripts: pulling $1 failed (attempt $attempt)" >&2
|
|
[[ $attempt -lt 3 ]] && sleep 5
|
|
done
|
|
return 1
|
|
}
|
|
pull_started=$SECONDS
|
|
for spec in "$IMAGE" "${FW_IMAGES[@]}"; do
|
|
image="${spec%%|*}"
|
|
if ! pull "$image"; then
|
|
echo "openwrt-scripts: could not pull $image" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
echo "openwrt-scripts: images ready in $((SECONDS - pull_started))s"
|
|
|
|
# The .apk wraps the shared bodies for its upgrade path. package-test.sh builds
|
|
# the package on the host with the real build-apk.sh, checks what it registers,
|
|
# and leaves the four scripts here so the scenarios run exactly what ships.
|
|
# The directory is bind-mounted into the container, so it is made by
|
|
# shared_tmpdir under the checkout, not /tmp; testing/lib/image-build.sh says
|
|
# why. --sweep clears what a killed run left behind.
|
|
# shellcheck source=SCRIPTDIR/../lib/image-build.sh
|
|
. "$SCRIPT_DIR/../lib/image-build.sh"
|
|
APK_DIR="$(shared_tmpdir --sweep "$PROJECT_ROOT/target" openwrt-apk)" || {
|
|
echo "openwrt-scripts: could not create a temporary directory under $PROJECT_ROOT/target" >&2
|
|
exit 2
|
|
}
|
|
trap 'rm -rf "$APK_DIR"' EXIT
|
|
bash "$SCRIPT_DIR/package-test.sh" --keep "$APK_DIR"
|
|
rc=$?
|
|
if [[ $rc -ne 0 ]]; then
|
|
echo "openwrt-scripts: package-test.sh exited $rc" >&2
|
|
exit $rc
|
|
fi
|
|
|
|
docker run --rm --network none "${CI_LABELS[@]}" \
|
|
-v "$PROJECT_ROOT:/src:ro" \
|
|
-v "$APK_DIR:/apk:ro" \
|
|
-e REPO=/src \
|
|
-e APK_SCRIPTS=/apk \
|
|
-e "POSTINST=${POSTINST:-}" \
|
|
-e "PRERM=${PRERM:-}" \
|
|
-e "PREINST=${PREINST:-}" \
|
|
-e "INIT_GATEWAY=${INIT_GATEWAY:-}" \
|
|
"$IMAGE" sh /src/testing/openwrt/scenarios.sh
|
|
rc=$?
|
|
|
|
if [[ $rc -ne 0 && $rc -ne 1 ]]; then
|
|
echo "openwrt-scripts: the container exited $rc, so the scenarios did not report" >&2
|
|
rc=2
|
|
fi
|
|
worst=$rc
|
|
|
|
# FW_SETUP, FW_MESH_SETUP and FW_SNIPPETS pass through from the caller when
|
|
# set, to run the firewall scenarios against another setup script or mesh
|
|
# helper, or without the snippets.
|
|
for spec in "${FW_IMAGES[@]}"; do
|
|
image="${spec%%|*}"
|
|
cases="${spec#*|}"
|
|
fw_env=(-e FW_SETUP -e FW_MESH_SETUP -e FW_SNIPPETS)
|
|
if [[ -n "$cases" ]]; then
|
|
fw_env+=(-e "FW_CASES=$cases")
|
|
else
|
|
fw_env+=(-e FW_CASES)
|
|
fi
|
|
echo "==> firewall scenarios in $image"
|
|
docker run --rm --network none --cap-add NET_ADMIN "${CI_LABELS[@]}" \
|
|
--tmpfs /fw-small:size=128k \
|
|
-v "$PROJECT_ROOT:/src:ro" \
|
|
-v "$APK_DIR:/apk:ro" \
|
|
"${fw_env[@]}" \
|
|
"$image" sh /src/testing/openwrt/firewall-scenarios.sh
|
|
rc=$?
|
|
if [[ $rc -ne 0 && $rc -ne 1 ]]; then
|
|
echo "openwrt-scripts: the $image container exited $rc, so its firewall scenarios did not report" >&2
|
|
rc=2
|
|
fi
|
|
[[ $rc -gt $worst ]] && worst=$rc
|
|
done
|
|
|
|
# INIT_GATEWAY and PRERM pass through from the caller when set, as for
|
|
# scenarios.sh; inside the container they name paths under /src or /apk.
|
|
for spec in "${FW_IMAGES[@]}"; do
|
|
image="${spec%%|*}"
|
|
echo "==> init script scenarios in $image"
|
|
docker run --rm --network none "${CI_LABELS[@]}" \
|
|
-v "$PROJECT_ROOT:/src:ro" \
|
|
-v "$APK_DIR:/apk:ro" \
|
|
-e INIT_GATEWAY -e PRERM -e INIT_CASES \
|
|
"$image" sh /src/testing/openwrt/init-scenarios.sh
|
|
rc=$?
|
|
if [[ $rc -ne 0 && $rc -ne 1 ]]; then
|
|
echo "openwrt-scripts: the $image container exited $rc, so its init script scenarios did not report" >&2
|
|
rc=2
|
|
fi
|
|
[[ $rc -gt $worst ]] && worst=$rc
|
|
done
|
|
|
|
echo "openwrt-scripts: finished in ${SECONDS}s, exit $worst"
|
|
exit $worst
|