Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ci/ci-tests/test_bind_pull_request_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_workflow_runs_only_for_relevant_pull_request_changes():
assert "- 'dns/bind/**'" in workflow
assert "- '.github/ci/**'" in workflow
assert "- '.resolver-plugins/target-pkg.json'" in workflow
assert "- '.resolver-plugins/target-pkg-content.json'" in workflow
assert "- '.github/workflows/bind-tests.yml'" in workflow


Expand Down
36 changes: 35 additions & 1 deletion .github/ci/ci-tests/test_package_release_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ def test_package_affecting_master_pushes_publish_the_newest_release_series():
assert "'26.7'" not in select


def test_merging_a_repack_recovery_rebuilds_its_exact_series():
select = workflow_text().split(' select:', 1)[1].split(' profile:', 1)[0]

assert 'ref: ${{ github.workflow_sha }}' in select
assert 'fetch-depth: 0' in select
assert 'persist-credentials: false' in select
assert 'BEFORE_SHA: ${{ github.event.before }}' in select
assert 'changed=$(git diff --name-only "$BEFORE_SHA" "$GITHUB_SHA")' in select
assert '[ "$changed" = .resolver-plugins/target-pkg.json ]' in select
assert 'git show "$BEFORE_SHA:.resolver-plugins/target-pkg.json"' in select
assert 'target_pkg.py changed-series "$before" "$after"' in select
assert 'series=$recovered_series' in select


def test_production_runs_only_from_the_master_control_plane():
workflow = workflow_text()
select = workflow.split(' select:', 1)[1].split(' profile:', 1)[0]
Expand Down Expand Up @@ -112,12 +126,32 @@ def test_workflow_materializes_the_distribution_bind_pair_before_building_the_pl
)


def test_failed_production_bind_job_can_only_propose_a_content_identical_pkg_repack():
workflow = workflow_text()
recovery = workflow.split(' recover-target-pkg:', 1)[1].split(' build:', 1)[0]
validator = recovery.split(' propose-target-pkg:', 1)[0]
proposer = recovery.split(' propose-target-pkg:', 1)[1]

assert "needs.bind.result == 'failure'" in validator
assert "needs.select.outputs.mode == 'production'" in validator
assert 'contents: read' in validator
assert 'contents: write' not in validator
assert 'persist-credentials: false' in validator
assert '.resolver-plugins/target-pkg-content.json' in validator
assert 'target_pkg.py refresh' in validator
assert "needs.recover-target-pkg.result == 'success'" in proposer
assert 'contents: write\n pull-requests: write' in proposer
assert '[ "$changed" = .resolver-plugins/target-pkg.json ]' in proposer
assert 'gh pr create' in proposer
assert 'gh pr merge' not in recovery


def test_workflow_uses_sha_pinned_actions_and_nonpersistent_checkout_credentials():
workflow = workflow_text()
references = action_references(workflow)
assert references
assert all(PINNED_ACTION.fullmatch(reference) for reference in references)
assert workflow.count('persist-credentials: false') == 10
assert workflow.count('persist-credentials: false') == 12
assert 'actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803' in references
assert 'vmactions/freebsd-vm@77ed28d336d03fe19a3f4f7266c1d2c4714dd79d' in references

Expand Down
205 changes: 203 additions & 2 deletions .github/ci/ci-tests/test_target_pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import hashlib
import importlib.util
import json
import shutil
import subprocess
import sys
import tarfile
import tempfile
from contextlib import contextmanager
from collections.abc import Iterator
Expand Down Expand Up @@ -47,20 +50,62 @@ def write_metadata(path: Path, archive: Path, pkg_static: Path) -> None:
)


def write_content_metadata(path: Path, archive: Path) -> None:
digest = target_pkg.package_content_sha256(archive)
record = {
"baseline_archive_sha256": hashlib.sha256(archive.read_bytes()).hexdigest(),
"content_sha256": digest,
}
path.write_text(
json.dumps({"schema": 2, "series": {"26.1": record, "26.7": record}}),
encoding="utf-8",
)


def write_archive(
path: Path,
pkg_static: Path,
payload: bytes = b"payload\n",
*,
payload_mode: int = 0o644,
link_target: str = "payload",
hardlink_payload: bool = True,
) -> None:
content = path.parent / "archive-content"
if content.exists():
shutil.rmtree(content)
executable = content / "usr/local/sbin/pkg-static"
executable.parent.mkdir(parents=True, exist_ok=True)
executable.write_bytes(pkg_static.read_bytes())
executable.chmod(0o755)
payload_path = content / "payload"
payload_path.write_bytes(payload)
payload_path.chmod(payload_mode)
hardlink_path = content / "payload-hardlink"
if hardlink_payload:
hardlink_path.hardlink_to(payload_path)
else:
hardlink_path.write_bytes(payload)
hardlink_path.chmod(payload_mode)
(content / "payload-link").symlink_to(link_target)
with tarfile.open(path, "w") as archive:
archive.add(content, arcname="")


@contextmanager
def pkg_fixture() -> Iterator[tuple[Path, Path, Path, Path]]:
"""Create a stateful pkg boundary with real archive/hash side effects."""
FIXTURE_ROOT.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=FIXTURE_ROOT) as directory_text:
directory = Path(directory_text)
archive = directory / "source-pkg-2.3.1_1.pkg"
archive.write_bytes(b"pinned target package archive\n")
pkg_static = directory / "pkg-static"
pkg_static.write_text(
"#!/bin/sh\n[ \"$1\" = -v ] || exit 64\nprintf '%s\\n' '2.3.1'\n",
encoding="utf-8",
)
pkg_static.chmod(0o755)
archive = directory / "source-pkg-2.3.1_1.pkg"
write_archive(archive, pkg_static)
log = directory / "commands.log"
lock = directory / "locked"
executable = directory / "pkg"
Expand Down Expand Up @@ -147,3 +192,159 @@ def test_rejects_unknown_or_malformed_series_metadata(tmp_path: Path) -> None:

with pytest.raises(target_pkg.TargetPackageError, match="26.1"):
target_pkg.load_target(metadata, "26.1")


def test_refreshes_only_the_outer_archive_hash_for_identical_contents(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
archive.write_bytes(archive.read_bytes() + b"repacked\n")

digest = target_pkg.refresh_archive_sha256(
metadata,
content_metadata,
"26.1",
str(pkg),
"OPNsense",
metadata,
)

assert digest == hashlib.sha256(archive.read_bytes()).hexdigest()
assert target_pkg.load_target(metadata, "26.1").sha256 == digest


def test_refresh_rejects_changed_extracted_contents(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
write_archive(archive, pkg_static, b"changed\n")

with pytest.raises(target_pkg.TargetPackageError, match="extracted contents"):
target_pkg.refresh_archive_sha256(
metadata,
content_metadata,
"26.1",
str(pkg),
"OPNsense",
metadata,
)


def test_refresh_rejects_changed_file_mode(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
write_archive(archive, pkg_static, payload_mode=0o755)

with pytest.raises(target_pkg.TargetPackageError, match="extracted contents"):
target_pkg.refresh_archive_sha256(
metadata, content_metadata, "26.1", str(pkg), "OPNsense", metadata
)


def test_refresh_rejects_changed_symlink_target(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
write_archive(archive, pkg_static, link_target="usr/local/sbin/pkg-static")

with pytest.raises(target_pkg.TargetPackageError, match="extracted contents"):
target_pkg.refresh_archive_sha256(
metadata, content_metadata, "26.1", str(pkg), "OPNsense", metadata
)


def test_refresh_rejects_changed_hardlink_relationship(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
write_archive(archive, pkg_static, hardlink_payload=False)

with pytest.raises(target_pkg.TargetPackageError, match="extracted contents"):
target_pkg.refresh_archive_sha256(
metadata, content_metadata, "26.1", str(pkg), "OPNsense", metadata
)


def test_refresh_rejects_changed_identity(tmp_path: Path) -> None:
with pkg_fixture() as (pkg, archive, pkg_static, _):
metadata = tmp_path / "target-pkg.json"
content_metadata = tmp_path / "target-pkg-content.json"
write_metadata(metadata, archive, pkg_static)
write_content_metadata(content_metadata, archive)
document = json.loads(metadata.read_text(encoding="utf-8"))
document["series"]["26.1"]["version"] = "2.3.2"
metadata.write_text(json.dumps(document), encoding="utf-8")

with pytest.raises(target_pkg.TargetPackageError, match="identity"):
target_pkg.refresh_archive_sha256(
metadata, content_metadata, "26.1", str(pkg), "OPNsense", metadata
)


def test_identifies_a_single_archive_only_change(tmp_path: Path) -> None:
with pkg_fixture() as (_, archive, pkg_static, _):
before = tmp_path / "before.json"
after = tmp_path / "after.json"
write_metadata(before, archive, pkg_static)
write_metadata(after, archive, pkg_static)
document = json.loads(after.read_text(encoding="utf-8"))
document["series"]["26.1"]["sha256"] = "0" * 64
after.write_text(json.dumps(document), encoding="utf-8")

assert target_pkg.changed_archive_series(before, after) == "26.1"

document["series"]["26.1"]["version"] = "2.3.2"
after.write_text(json.dumps(document), encoding="utf-8")
assert target_pkg.changed_archive_series(before, after) is None


def test_changed_series_command_fails_when_provenance_is_unavailable(tmp_path: Path) -> None:
result = subprocess.run(
[
sys.executable,
str(MODULE_PATH),
"changed-series",
str(tmp_path / "missing-before.json"),
str(tmp_path / "missing-after.json"),
],
check=False,
capture_output=True,
text=True,
)

assert result.returncode == 1
assert "target pkg selection failed" in result.stderr


def test_content_hash_rejects_archive_path_traversal(tmp_path: Path) -> None:
archive_path = tmp_path / "traversal.pkg"
with tarfile.open(archive_path, "w") as archive:
entry = tarfile.TarInfo("../escape")
entry.size = 0
archive.addfile(entry)

with pytest.raises(subprocess.CalledProcessError):
target_pkg.package_content_sha256(archive_path)
assert not (tmp_path / "escape").exists()


def test_content_hash_rejects_special_entries(tmp_path: Path) -> None:
archive_path = tmp_path / "special.pkg"
with tarfile.open(archive_path, "w") as archive:
entry = tarfile.TarInfo("named-pipe")
entry.type = tarfile.FIFOTYPE
archive.addfile(entry)

with pytest.raises(target_pkg.TargetPackageError, match="unsupported entry"):
target_pkg.package_content_sha256(archive_path)
Loading
Loading