From 9375796bc011112f9a011462c4fa4faabeca31b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20M=C3=A9ndez?= Date: Sun, 27 Sep 2026 20:10:14 +0000 Subject: [PATCH] fix: the trusted=yes that is refused is one on the project archive bin/keel-archive-check refused a trusted=yes on any apt source in the build tree. The captured pool of decision 0012 sets Trusted: yes on purpose, for a file: index generated on this machine from files keel-pool verify checks against the same digests apt does, so the wider rule would have failed every pinned build. What is refused is now a source that names the project archive and switches verification off, which is the defect of tracker#7, in whichever file and in either of apt's two formats. What the pool does is the pool's business. Measured with the same suite: bin/keel-archive-check 100 percent (54/54) over 27 bats tests, three of them new: the pool's Trusted: yes passes, a trusted=yes on the project archive in another file fails, and the archive the rule applies to is overridable. --- COVERAGE.md | 6 +++--- bin/keel-archive-check | 24 ++++++++++++++++++------ changelog | 11 +++++++++++ tests/archive-check.bats | 33 ++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/COVERAGE.md b/COVERAGE.md index 714d5d7..d1833af 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -12,14 +12,14 @@ acceptance test of an appliance recipe, docs/org-plan.md section 1). | overlay/usr/lib/inithooks/firstboot.d/40nodebb | tests/hook.bats (15 tests) | 96.97 percent (32/33) under kcov | the one uncovered line is inside the dialog loop, which needs a terminal | | overlay/etc/nginx/* | tests/nginx.bats (12 tests) | not executable | asserted as content: the geo variable, the map, the listeners, the proxy headers | | tests/lib/boot-test-lib.sh | tests/boot-test.bats (41 tests) | 100 percent (137/137) under kcov | the logic of the boot test: argument parsing, address discovery, deadlines, the HTTP and diff verdicts | -| bin/keel-archive-check | tests/archive-check.bats (25 tests) | 100 percent (52/52) under kcov | the build time check: the archive copy in the build tree is the live archive, the source entry names the keyring through signed-by, nothing says trusted=yes, and the signature on the copied InRelease verifies against the staging key (tracker#7) | +| bin/keel-archive-check | tests/archive-check.bats (27 tests) | 100 percent (54/54) under kcov | the build time check: the archive copy in the build tree is the live archive, the source entry names the keyring through signed-by, nothing says trusted=yes, and the signature on the copied InRelease verifies against the staging key (tracker#7) | | conf.d/zz-project-packages | tests/project-packages.bats (14 tests) | 100 percent (31/31) under kcov | the build time check that each project package is the candidate of the archive and a project build, and that the archive copy, its source entry and the staging keyring leave the image | | overlay/usr/lib/inithooks/bin/nodebb.py | none | 0 | dialog wrapper, only reached with a terminal attached | | conf.d/main | tests/boot-test.sh (build step) | integration only | build time script, 0004 pragmatic limits | | tests/boot-test.sh | itself | integration only | the thin main of the acceptance test: keel and LXC as root | -Total over the five measured shell files: 99.66 percent (295/296) before the -terminal test, 100 percent (296/296) with it, over 132 bats tests. +Total over the five measured shell files: 99.66 percent (297/298) before the +terminal test, 100 percent (298/298) with it, over 134 bats tests. `tests/coverage.sh` runs the whole bats suite under kcov, measures the library, the first boot hook, the boot test's own library and the two build diff --git a/bin/keel-archive-check b/bin/keel-archive-check index 4de10e6..a61607d 100755 --- a/bin/keel-archive-check +++ b/bin/keel-archive-check @@ -41,6 +41,8 @@ # promise until the key inside it is named. # KEEL_ARCHIVE_KEYRING where in TREE the keyring is # KEEL_ARCHIVE_LIST where in TREE the source entry is +# KEEL_ARCHIVE_PATH the path an apt source names this archive by, which is +# the archive a trusted=yes is refused for # # Exit 0 when everything holds, 1 otherwise. set -eu @@ -74,6 +76,9 @@ fatal() { [ -n "$KEEL_ARCHIVE_KEY" ] \ || fatal "KEEL_ARCHIVE_KEY names no key: the archive would be verified against whatever the keyring happens to hold" +# The path an apt source names this archive by, which is what makes a +# trusted=yes elsewhere in the tree somebody else's business. +ARCHIVE_PATH="${KEEL_ARCHIVE_PATH:-/srv/keel-apt/repo}" index=dists/$dist/main/binary-$arch/Packages source_index=$source_repo/$index copy=$tree/srv/keel-apt/repo @@ -95,16 +100,23 @@ if ! cmp -s "$source_index" "$copy_index"; then exit 1 fi -# 2. Nothing in the tree switches verification off. A single trusted=yes -# anywhere turns every failure below into a warning, which is the defect this -# check exists for, so it is refused wherever it is written, in either of the -# two formats apt reads a source in. +# 2. Nothing in the tree switches verification off for this archive. A single +# trusted=yes on it turns every failure below into a warning, which is the +# defect this check exists for, so it is refused in either of the two formats +# apt reads a source in and in whichever file it is written. +# +# Scoped to the sources that name this archive, not to every source in the +# tree: the captured pool of decision 0012 sets Trusted: yes on purpose, for a +# file: index generated on this machine from files keel-pool verify checks +# against the same digests apt does. Refusing that would fail every pinned +# build. What the pool does is the pool's business; this archive is verified. verification_off='trusted *= *yes|^ *Trusted: *yes' apt_sources="$tree/etc/apt/sources.list $tree/etc/apt/sources.list.d" # shellcheck disable=SC2086 # the two paths are one word each, on purpose -untrusted=$(grep -rlE "$verification_off" $apt_sources 2>/dev/null || true) +distrusting=$(grep -rlE "$verification_off" $apt_sources 2>/dev/null || true) +untrusted=$(echo "$distrusting" | xargs -r grep -lF "$ARCHIVE_PATH" || true) [ -z "$untrusted" ] \ - || fatal "verification is switched off in: $(echo "$untrusted" | tr '\n' ' ')" + || fatal "verification is switched off for $ARCHIVE_PATH in: $(echo "$untrusted" | tr '\n' ' ')" # 3. The source entry names this distribution and the keyring it is verified # with. apt takes signed-by from the entry, so the entry is what decides diff --git a/changelog b/changelog index 664ca06..cf5ebd4 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,14 @@ +turnkey-nodebb-19.0 (4) turnkey; urgency=low + + * bin/keel-archive-check refuses a trusted=yes on the project archive rather + than on every apt source in the build tree. The captured pool of decision + 0012 sets Trusted: yes on purpose, for a file: index generated on this + machine from files keel-pool verify checks against the same digests apt + does, so the wider rule would have failed every pinned build. What the pool + does is the pool's business; the project archive is verified (tracker#7). + + -- Keel Linux maintainers Sun, 27 Sep 2026 20:10:00 +0000 + turnkey-nodebb-19.0 (3) turnkey; urgency=low * The build verifies the project's own APT archive instead of reading it diff --git a/tests/archive-check.bats b/tests/archive-check.bats index ace9f31..1ba2380 100644 --- a/tests/archive-check.bats +++ b/tests/archive-check.bats @@ -142,25 +142,44 @@ check() { echo "deb [trusted=yes] file:///srv/keel-apt/repo $DIST main" > "$TREE$LIST_PATH" check bootstrap [ "$status" -eq 1 ] - [[ "$output" == *"verification is switched off in"* ]] + [[ "$output" == *"verification is switched off for /srv/keel-apt/repo"* ]] [[ "$output" == *"keel-staging.list"* ]] } -@test "trusted=yes anywhere else in the tree fails too" { - printf 'deb [ trusted = yes ] http://example.invalid trixie main\n' \ +@test "trusted=yes on this archive in another file fails too" { + printf 'deb [ trusted = yes ] file:///srv/keel-apt/repo trixie-staging main\n' \ > "$TREE/etc/apt/sources.list.d/other.list" check bootstrap [ "$status" -eq 1 ] - [[ "$output" == *"verification is switched off in"* ]] + [[ "$output" == *"verification is switched off for /srv/keel-apt/repo"* ]] [[ "$output" == *"other.list"* ]] } -@test "Trusted: yes in a deb822 source fails as well" { - printf 'Types: deb\nURIs: http://example.invalid\nSuites: trixie\nTrusted: yes\n' \ +@test "Trusted: yes on this archive in a deb822 source fails as well" { + printf 'Types: deb\nURIs: file:///srv/keel-apt/repo\nSuites: trixie-staging\nTrusted: yes\n' \ > "$TREE/etc/apt/sources.list.d/other.sources" check bootstrap [ "$status" -eq 1 ] - [[ "$output" == *"verification is switched off in"* ]] + [[ "$output" == *"verification is switched off for"* ]] +} + +@test "the captured pool may say Trusted: yes, because it is not this archive" { + # Decision 0012: a file: index generated on this machine, whose digests + # keel-pool verify checks. Refusing it would fail every pinned build. + printf 'Types: deb\nURIs: file:/keel-pool\nSuites: 2026-09-27\nTrusted: yes\n' \ + > "$TREE/etc/apt/sources.list.d/keel-pool.sources" + check bootstrap + [ "$status" -eq 0 ] + [[ "$output" == *"nothing is trusted unverified"* ]] +} + +@test "the archive a trusted=yes is refused for is overridable" { + printf 'deb [trusted=yes] file:///elsewhere/repo trixie main\n' \ + > "$TREE/etc/apt/sources.list.d/other.list" + run env KEEL_ARCHIVE_KEY="$GOOD_KEY" KEEL_ARCHIVE_PATH=/elsewhere/repo \ + "$CHECK" "$SOURCE" "$TREE" "$DIST" "$ARCH" bootstrap + [ "$status" -eq 1 ] + [[ "$output" == *"switched off for /elsewhere/repo"* ]] } @test "a tree with no source entry at all fails" {