diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 217875614d..b0629eaa8c 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -4417,7 +4417,8 @@ release packet, and make post-publication validation authenticate `PULSE_UPDATE_SIGNING_PUBLIC_KEY`, not merely test that sidecars are present. Validation must fail if the trust root is unavailable, if any signature is invalid, if any published artifact or `checksums.txt` is missing its -`.sshsig` sidecar, or if the canonical +`.sshsig` sidecar, if the authenticated checksum manifest contains duplicate +asset filenames or trailing fields, or if the canonical release-packet SBOM is absent so published RC/stable downloads can keep the updater and installer trust chain fail-closed instead of downgrading to checksum-only trust and can publish a shareable non-image software inventory diff --git a/scripts/tests/test_validate_published_release.py b/scripts/tests/test_validate_published_release.py index b568b51a3b..a651d180f6 100644 --- a/scripts/tests/test_validate_published_release.py +++ b/scripts/tests/test_validate_published_release.py @@ -87,6 +87,12 @@ class ValidatePublishedReleaseTest(unittest.TestCase): path.with_suffix(path.suffix + ".sshsig") ) + def replace_signed_checksums(self, content: str) -> None: + checksums = self.assets / "checksums.txt" + checksums.write_text(content, encoding="utf-8") + checksums.with_suffix(".txt.sshsig").unlink() + self.sign(checksums) + def write_fake_tools(self) -> None: curl = self.bin_dir / "curl" curl.write_text( @@ -161,6 +167,46 @@ printf '%s\n' "${FAKE_SSH_PUBLIC_KEY}" self.assertNotEqual(result.returncode, 0) self.assertIn("PULSE_UPDATE_SIGNING_PUBLIC_KEY is required", result.stderr) + def test_rejects_authenticated_manifest_with_duplicate_filename(self) -> None: + checksums = (self.assets / "checksums.txt").read_text(encoding="utf-8") + install_line = next( + line for line in checksums.splitlines() if line.endswith(" install.sh") + ) + self.replace_signed_checksums(f"{checksums}{install_line}\n") + + result = self.run_validator() + + self.assertNotEqual(result.returncode, 0) + self.assertIn( + "Duplicate release asset filename in checksums.txt: install.sh", + result.stderr, + ) + + def test_rejects_authenticated_manifest_with_trailing_fields(self) -> None: + checksums = (self.assets / "checksums.txt").read_text(encoding="utf-8") + malformed = checksums.replace(" install.sh\n", " install.sh unexpected\n") + self.replace_signed_checksums(malformed) + + result = self.run_validator() + + self.assertNotEqual(result.returncode, 0) + self.assertIn( + "Malformed checksums line (unexpected fields for install.sh).", + result.stderr, + ) + + def test_validates_final_manifest_entry_without_newline(self) -> None: + checksums = (self.assets / "checksums.txt").read_text(encoding="utf-8") + self.replace_signed_checksums(checksums.rstrip("\n")) + (self.assets / "install.sh.sshsig").write_text( + "not a signature\n", encoding="utf-8" + ) + + result = self.run_validator() + + self.assertNotEqual(result.returncode, 0) + self.assertIn("SSH signature verification failed for install.sh", result.stderr) + if __name__ == "__main__": unittest.main() diff --git a/scripts/validate-published-release.sh b/scripts/validate-published-release.sh index 73e214fdce..34cc36d1d1 100755 --- a/scripts/validate-published-release.sh +++ b/scripts/validate-published-release.sh @@ -103,13 +103,20 @@ if [[ ! -s "$RELEASE_SBOM_PATH" ]]; then fi status=0 +SEEN_FILENAMES_PATH="${TMP_DIR}/checksums-filenames.txt" +: >"$SEEN_FILENAMES_PATH" -while read -r checksum filename _; do +while read -r checksum filename extra || [[ -n "${checksum:-}${filename:-}${extra:-}" ]]; do [[ -z "${checksum:-}" ]] && continue [[ "$checksum" =~ ^# ]] && continue if [[ -z "${filename:-}" ]]; then echo "Malformed checksums line (missing filename): $checksum" >&2 - status=1 + status=$((status + 1)) + continue + fi + if [[ -n "${extra:-}" ]]; then + echo "Malformed checksums line (unexpected fields for ${filename})." >&2 + status=$((status + 1)) continue fi if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then @@ -122,6 +129,12 @@ while read -r checksum filename _; do status=$((status + 1)) continue fi + if grep -Fxq -- "$filename" "$SEEN_FILENAMES_PATH"; then + echo "Duplicate release asset filename in checksums.txt: ${filename}" >&2 + status=$((status + 1)) + continue + fi + printf '%s\n' "$filename" >>"$SEEN_FILENAMES_PATH" artifact_url="${BASE_URL}/${filename}" echo "Verifying ${filename}..."