diff --git a/.github/workflows/package-smoke.yml b/.github/workflows/package-smoke.yml index 9ebd66b4..da91e660 100644 --- a/.github/workflows/package-smoke.yml +++ b/.github/workflows/package-smoke.yml @@ -214,14 +214,15 @@ jobs: # The package manager enforces the engine/corpus pairing (spec # release-upgrade C-06). AC-09 is verified in Go CI against the resolvers; - # this runs the same scenarios in real containers, scriptlets included. A Kensa engine older than its corpus + # this runs the same scenarios in real containers, with scriptlets and, on + # RPM, again under tsflags=noscripts. A Kensa engine older than its corpus # cannot load it and the service starts anyway with every scan failing, so # a rules-only upgrade beside an older openwatch must be refused while the # coordinated upgrade, an openwatch-only upgrade and a fresh install # succeed. The previous GA predates the engine provide, which is the state # the refusal has to hold against. kensa-rules-compat: - name: kensa-rules compat ${{ matrix.distro }} + name: kensa-rules compat ${{ matrix.distro }}${{ matrix.mode && format(' {0}', matrix.mode) || '' }} needs: build runs-on: ubuntu-latest strategy: @@ -230,6 +231,10 @@ jobs: include: - { distro: 'rockylinux:9', kind: rpm } - { distro: 'ubuntu:24.04', kind: deb } + # The same scenarios with every RPM transaction under + # tsflags=noscripts, so the refusals rest on dependency + # resolution alone. A pass with scriptlets is not evidence for it. + - { distro: 'rockylinux:9', kind: rpm, mode: noscripts } steps: - uses: actions/checkout@v7 - uses: actions/download-artifact@v4 @@ -256,6 +261,7 @@ jobs: env: OPENWATCH_KENSA_COMPAT_IMAGE: ${{ matrix.distro }} OPENWATCH_KENSA_COMPAT_KIND: ${{ matrix.kind }} + OPENWATCH_KENSA_COMPAT_MODE: ${{ matrix.mode }} run: | OPENWATCH_KENSA_COMPAT_OLD_DIR="$PWD/old" OPENWATCH_KENSA_COMPAT_NEW_DIR="$PWD/new" \ go test -count=1 -v -run 'TestUpgrade_EngineCorpusPairingInContainers' ./packaging/tests/ diff --git a/packaging/tests/kensa-rules-compat-container-test.sh b/packaging/tests/kensa-rules-compat-container-test.sh index 962f9ca5..43b9ffa3 100755 --- a/packaging/tests/kensa-rules-compat-container-test.sh +++ b/packaging/tests/kensa-rules-compat-container-test.sh @@ -38,20 +38,41 @@ # should be installed. A failed `dpkg -i` can leave the recorded version # looking unchanged while the files are already replaced. # -# Usage: kensa-rules-compat-container-test.sh +# Usage: kensa-rules-compat-container-test.sh [scripts|noscripts] # old-dir: an openwatch release that predates the engine provide, and its # kensa-rules (package-smoke passes the previous GA). # new-dir: openwatch and kensa-rules built from this tree. -# -# Scriptlets run on both formats. The openwatch scriptlets tolerate a -# container without systemd or a database: the upgrade scriptlet skips its -# migration when `openwatch migrate --status` cannot connect. +# mode: scripts (the default) runs scriptlets on both formats. The +# openwatch scriptlets tolerate a container without systemd or a +# database: the upgrade scriptlet skips its migration when +# `openwatch migrate --status` cannot connect. +# noscripts (RPM only) runs every transaction with +# tsflags=noscripts (dnf) or --noscripts (rpm), so the refusals +# rest on dependency resolution alone. Every scenario and check +# is the same, except that the scriptlet check is inverted: it +# asserts no identity keys were provisioned, which proves the +# mode took effect. A pass in one mode is not evidence for the +# other. set -uo pipefail KIND="${1:?usage: $0 }" OLD="${2:?old-dir}" NEW="${3:?new-dir}" +MODE="${4:-scripts}" + +# Options every RPM transaction takes in this mode. +DNF_OPTS=() +RPM_OPTS=() +case "$MODE" in + scripts) ;; + noscripts) + [ "$KIND" = rpm ] || { echo "noscripts mode is RPM only" >&2; exit 2; } + DNF_OPTS=(--setopt=tsflags=noscripts) + RPM_OPTS=(--noscripts) + ;; + *) echo "unknown mode: $MODE" >&2; exit 2 ;; +esac FAILURES=0 pass() { echo "PASS: $*"; } @@ -87,7 +108,7 @@ txn() { if [ "$KIND" = deb ]; then DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades "$@" >"$log" 2>&1 else - dnf install -y "$@" >"$log" 2>&1 + dnf install -y "${DNF_OPTS[@]}" "$@" >"$log" 2>&1 fi local rc=$? LAST_LOG="$log" @@ -199,7 +220,7 @@ fi check_state "after the refused rules-only upgrade" "$V_OLD_OW" "$V_OLD_KR" old if [ "$KIND" = rpm ]; then # The plain rpm path checks dependencies too; --nodeps is the only bypass. - if rpm -U "$NEW_KR" >/tmp/rpmU.log 2>&1; then + if rpm -U "${RPM_OPTS[@]}" "$NEW_KR" >/tmp/rpmU.log 2>&1; then fail "rpm -U installed the new corpus beside the older openwatch" else pass "rpm -U refused: $(grep -m1 openwatch-kensa-engine /tmp/rpmU.log | sed 's/^[[:space:]]*//')" @@ -225,8 +246,15 @@ else fi check_state "after the coordinated upgrade" "$V_NEW_OW" "$V_NEW_KR" new # Scriptlets ran: the openwatch pre-install creates the service user, and the -# post-install provisions the identity keys. -if getent passwd openwatch >/dev/null && [ -n "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then +# post-install provisions the identity keys. Under noscripts neither may have +# happened; the keys are the check, since only %post creates them. +if [ "$MODE" = noscripts ]; then + if [ -z "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then + pass "scriptlets did not run: no identity keys are provisioned" + else + fail "identity keys exist, so a scriptlet ran despite noscripts" + fi +elif getent passwd openwatch >/dev/null && [ -n "$(ls -A /etc/openwatch/keys 2>/dev/null)" ]; then pass "scriptlets ran: the openwatch user exists and identity keys are provisioned" else fail "scriptlets did not run: no openwatch user or no identity keys" @@ -235,7 +263,7 @@ fi if [ "$KIND" = rpm ]; then echo "### 3b. dnf upgrade of both packages succeeds" reset_to_old - if dnf upgrade -y "$NEW_OW" "$NEW_KR" >/tmp/dnfup.log 2>&1; then + if dnf upgrade -y "${DNF_OPTS[@]}" "$NEW_OW" "$NEW_KR" >/tmp/dnfup.log 2>&1; then pass "dnf upgrade accepted" else fail "dnf upgrade failed; log follows"; cat /tmp/dnfup.log @@ -244,7 +272,7 @@ if [ "$KIND" = rpm ]; then echo "### 3c. a joint rpm -Uvh of both packages succeeds" reset_to_old - if rpm -Uvh "$NEW_OW" "$NEW_KR" >/tmp/rpmUvh.log 2>&1; then + if rpm -Uvh "${RPM_OPTS[@]}" "$NEW_OW" "$NEW_KR" >/tmp/rpmUvh.log 2>&1; then pass "joint rpm -Uvh accepted" else fail "joint rpm -Uvh failed; log follows"; cat /tmp/rpmUvh.log @@ -283,9 +311,9 @@ check_state "kensa-rules refused, then openwatch, then kensa-rules" "$V_NEW_OW" if [ "$KIND" = rpm ]; then reset_to_old - if rpm -U "$NEW_OW" >/tmp/rpmU1.log 2>&1; then pass "rpm -U openwatch first: accepted"; else fail "rpm -U openwatch first failed"; cat /tmp/rpmU1.log; fi + if rpm -U "${RPM_OPTS[@]}" "$NEW_OW" >/tmp/rpmU1.log 2>&1; then pass "rpm -U openwatch first: accepted"; else fail "rpm -U openwatch first failed"; cat /tmp/rpmU1.log; fi check_state "rpm -U openwatch first" "$V_NEW_OW" "$V_OLD_KR" old - if rpm -U "$NEW_KR" >/tmp/rpmU2.log 2>&1; then pass "then rpm -U kensa-rules: accepted"; else fail "then rpm -U kensa-rules failed"; cat /tmp/rpmU2.log; fi + if rpm -U "${RPM_OPTS[@]}" "$NEW_KR" >/tmp/rpmU2.log 2>&1; then pass "then rpm -U kensa-rules: accepted"; else fail "then rpm -U kensa-rules failed"; cat /tmp/rpmU2.log; fi check_state "rpm -U openwatch, then kensa-rules" "$V_NEW_OW" "$V_NEW_KR" new fi @@ -348,10 +376,12 @@ for pair in "1:0.8.0~rc.5 1:0.8.0~rc.6" "1:0.8.0~rc.6 1:0.8.0~rc.10" "1:0.8.0~rc older_than "$1" "$2" && pass "$1 < $2" || fail "$1 does not sort before $2" done +LABEL="$KIND" +[ "$MODE" = scripts ] || LABEL="$KIND $MODE" echo if [ "$FAILURES" -eq 0 ]; then - echo "kensa-rules compat ($KIND): all checks passed" + echo "kensa-rules compat ($LABEL): all checks passed" exit 0 fi -echo "kensa-rules compat ($KIND): $FAILURES check(s) failed" +echo "kensa-rules compat ($LABEL): $FAILURES check(s) failed" exit 1 diff --git a/packaging/tests/run-kensa-rules-compat-test.sh b/packaging/tests/run-kensa-rules-compat-test.sh index 859ee089..bcb3b4fd 100755 --- a/packaging/tests/run-kensa-rules-compat-test.sh +++ b/packaging/tests/run-kensa-rules-compat-test.sh @@ -2,8 +2,8 @@ # run-kensa-rules-compat-test.sh: host-side runner for # kensa-rules-compat-container-test.sh (spec release-upgrade AC-09). # -# Usage: run-kensa-rules-compat-test.sh -# e.g. run-kensa-rules-compat-test.sh rockylinux:9 rpm /tmp/old dist +# Usage: run-kensa-rules-compat-test.sh [scripts|noscripts] +# e.g. run-kensa-rules-compat-test.sh rockylinux:9 rpm /tmp/old dist noscripts # old-dir holds a release that predates the engine provide (the published # v0.8.0-rc.5 openwatch and kensa-rules assets); new-dir holds the packages # built from this tree. Needs docker. @@ -12,6 +12,7 @@ IMAGE="${1:?usage: $0 }" KIND="${2:?kind}" OLD="$(cd "${3:?old-dir}" && pwd)" NEW="$(cd "${4:?new-dir}" && pwd)" +MODE="${5:-scripts}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" command -v docker >/dev/null || { echo "docker is required" >&2; exit 1; } @@ -19,4 +20,4 @@ docker run --rm \ -v "$OLD":/pk/old:ro \ -v "$NEW":/pk/new:ro \ -v "$SCRIPT_DIR/kensa-rules-compat-container-test.sh":/t.sh:ro \ - "$IMAGE" bash /t.sh "$KIND" /pk/old /pk/new + "$IMAGE" bash /t.sh "$KIND" /pk/old /pk/new "$MODE" diff --git a/packaging/tests/upgrade_test.go b/packaging/tests/upgrade_test.go index d422ed14..4c557263 100644 --- a/packaging/tests/upgrade_test.go +++ b/packaging/tests/upgrade_test.go @@ -206,6 +206,8 @@ func TestUpgrade_PackagesDeclareEngineCorpusPairing(t *testing.T) { // engine_pairing_test.go, which runs in Go CI. package-smoke's // kensa-rules-compat job sets the four variables (previous GA as the old // release, the candidate's packages as the new); elsewhere this skips. +// OPENWATCH_KENSA_COMPAT_MODE=noscripts runs every RPM transaction without +// scriptlets; unset, scriptlets run. func TestUpgrade_EngineCorpusPairingInContainers(t *testing.T) { t.Run("containers", func(t *testing.T) { image := os.Getenv("OPENWATCH_KENSA_COMPAT_IMAGE") @@ -215,14 +217,21 @@ func TestUpgrade_EngineCorpusPairingInContainers(t *testing.T) { if image == "" || kind == "" || oldDir == "" || newDir == "" { t.Skip("set OPENWATCH_KENSA_COMPAT_{IMAGE,KIND,OLD_DIR,NEW_DIR} to run the container pairing test") } + mode := os.Getenv("OPENWATCH_KENSA_COMPAT_MODE") + label := kind + if mode == "" { + mode = "scripts" + } else if mode != "scripts" { + label = kind + " " + mode + } haveTool(t, "docker") runner := filepath.Join(appDir(t), "packaging", "tests", "run-kensa-rules-compat-test.sh") - out, err := exec.Command("bash", runner, image, kind, oldDir, newDir).CombinedOutput() + out, err := exec.Command("bash", runner, image, kind, oldDir, newDir, mode).CombinedOutput() t.Logf("%s", out) if err != nil { - t.Fatalf("pairing test failed on %s (%s): %v", image, kind, err) + t.Fatalf("pairing test failed on %s (%s): %v", image, label, err) } - if !strings.Contains(string(out), "kensa-rules compat ("+kind+"): all checks passed") { + if !strings.Contains(string(out), "kensa-rules compat ("+label+"): all checks passed") { t.Fatal("the container test did not report a complete pass") } })