Harden first-release validation and dependency hygiene
Some checks failed
ci / rust (push) Has been cancelled
ci / docker (push) Has been cancelled
ci / audit (push) Has been cancelled

This commit is contained in:
2026-09-10 09:53:45 +08:00
parent 277cbca878
commit 591bed90a8
61 changed files with 1641 additions and 622 deletions

9
tools/check_format.py Normal file
View File

@@ -0,0 +1,9 @@
"""Check every Rust source file, including include! fragments."""
import subprocess
from pathlib import Path
root = Path(__file__).resolve().parents[1]
files = sorted(p for folder in ("src", "tests") for p in (root / folder).rglob("*.rs"))
subprocess.run(["cargo", "fmt", "--all", "--check"], cwd=root, check=True)
subprocess.run(["rustfmt", "--check", "--edition", "2024", *map(str, files)],
cwd=root, check=True)

View File

@@ -0,0 +1,86 @@
"""Reproduce notices from locked Cargo archives and pinned upstream supplements."""
import argparse
import hashlib
import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HEADER = """# Third-party notices
Panda RPKI is distributed under BSD-3-Clause; dependencies retain their own
licenses. The table below records the locked Cargo dependency graph, including
build-time and platform-specific packages that may not be linked on Linux.
Upstream license expressions are reproduced as declared by each package.
The accompanying [license texts](docs/third-party-licenses.txt) retain notices
from package archives, including bundled native code. These files must accompany
binary distributions. Container OS package notices remain under
`/usr/share/doc/` and `/usr/share/common-licenses/` in the Debian image.
Python cryptography and OpenSSL are external test tools, not bundled source.
Consult their installed license notices when distributing a test environment.
No production trust-anchor or repository data is included in this source tree.
| Package | Locked version | Declared license |
| --- | --- | --- |
"""
def generate():
metadata = json.loads(subprocess.check_output(
["cargo", "metadata", "--locked", "--format-version", "1"], cwd=ROOT))
supplements = json.loads((ROOT / "tools/license_supplements.json").read_text())
rows, texts = [], []
for package in sorted(metadata["packages"], key=lambda p: (p["name"], p["version"])):
if package["id"] in metadata["workspace_members"]:
continue
name, version = package["name"], package["version"]
source = Path(package["manifest_path"]).parent
candidates = sorted(p for p in source.rglob("*") if p.is_file()
and (p.name.upper().startswith(("LICENSE", "LICENCE", "COPYING", "COPYRIGHT", "NOTICE", "AUTHORS"))
or "LICENSES" in p.relative_to(source).parts)
and p.stat().st_size < 500_000)
contents = []
for path in candidates:
try:
contents.append((str(path.relative_to(source)), path.read_text(encoding="utf-8")))
except UnicodeDecodeError:
continue
if not contents:
entry = supplements.get(f"{name}@{version}")
if entry is None:
raise RuntimeError(f"Missing license texts: {name}@{version}; review upstream sources")
data = subprocess.check_output([
"curl", "--fail", "--silent", "--show-error", "--location",
"--proto", "=https", "--proto-redir", "=https", "--connect-timeout", "15",
"--max-time", "120", entry["url"]])
if hashlib.sha256(data).hexdigest() != entry["sha256"]:
raise RuntimeError(f"Supplement checksum mismatch: {name}@{version}")
contents.append(("LICENSE (upstream package revision)", data.decode("utf-8")))
rows.append(f"| {name} | {version} | {package['license'] or 'See upstream license'} |")
for label, content in contents:
texts.append(f"\n{'=' * 72}\n{name} {version} — {label}\n{'=' * 72}\n{content}\n")
return {
ROOT / "THIRD_PARTY_NOTICES.md": HEADER + "\n".join(rows) + "\n",
ROOT / "docs/third-party-licenses.txt":
"Third-party license texts from locked package archives.\n" + "".join(texts),
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true", help="Fail if tracked notices differ; do not write files")
args = parser.parse_args()
outputs = generate() # Resolve everything before writing any output.
for path, content in outputs.items():
if args.check:
if not path.exists() or path.read_bytes() != content.encode("utf-8"):
raise SystemExit(f"Outdated notices: {path.name}; run tools/dependency_notices.py")
else:
path.write_bytes(content.encode("utf-8"))
print("Dependency notices are current." if args.check else "Dependency notices generated.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,14 @@
{
"alloc-stdlib@0.2.4": {
"url": "https://raw.githubusercontent.com/dropbox/rust-alloc-no-stdlib/ae42d22078b98549e987d2f03d12df7b984fde47/LICENSE",
"sha256": "c0c56f26d9c051cac4d200c34c84e7ae9aaa853e01a982a1df08b09931e518ae"
},
"asn1-rs-impl@0.2.0": {
"url": "https://raw.githubusercontent.com/rusticata/asn1-rs/a20e5f7319c896737ad0f2557037817b91ad854f/LICENSE-MIT",
"sha256": "a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513"
},
"cookie-factory@0.3.3": {
"url": "https://raw.githubusercontent.com/rust-bakery/cookie-factory/d36b805dbd7dd65f2df947235c5bcc573afe2c76/LICENSES/MIT.txt",
"sha256": "d09216dc1ea5f273997667935a8d6514d80f00b89fb6954ecc3a43e0a6e360de"
}
}

View File

@@ -0,0 +1,59 @@
"""Offline regression checks for release-maintenance failure handling."""
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
import dependency_notices as notices
class NoticeTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
(self.root / "tools").mkdir()
(self.root / "package").mkdir()
(self.root / "tools/license_supplements.json").write_text("{}")
self.metadata = json.dumps({"workspace_members": [], "packages": [{
"id": "example", "name": "example", "version": "1.0.0", "license": "MIT",
"manifest_path": str(self.root / "package/Cargo.toml"),
}]}).encode()
self.patch = patch.object(notices, "ROOT", self.root)
self.patch.start()
self.addCleanup(self.patch.stop)
def test_missing_license_fails(self):
with patch.object(notices.subprocess, "check_output", return_value=self.metadata):
with self.assertRaisesRegex(RuntimeError, "Missing license"):
notices.generate()
def test_license_text_is_preserved(self):
original = "Copyright Example \n\nMIT license text\n"
(self.root / "package/LICENSE").write_text(original)
with patch.object(notices.subprocess, "check_output", return_value=self.metadata):
outputs = notices.generate()
self.assertIn(original, outputs[self.root / "docs/third-party-licenses.txt"])
self.assertFalse((self.root / "THIRD_PARTY_NOTICES.md").exists())
def test_supplement_checksum_mismatch_fails(self):
(self.root / "tools/license_supplements.json").write_text(json.dumps({
"example@1.0.0": {"url": "https://example.invalid/LICENSE", "sha256": "0" * 64}
}))
with patch.object(notices.subprocess, "check_output", side_effect=[self.metadata, b"bad"]):
with self.assertRaisesRegex(RuntimeError, "checksum mismatch"):
notices.generate()
def test_check_does_not_overwrite_outdated_file(self):
path = self.root / "THIRD_PARTY_NOTICES.md"
path.write_text("old")
with patch.object(notices, "generate", return_value={path: "new"}):
with patch("sys.argv", ["dependency_notices.py", "--check"]):
with self.assertRaisesRegex(SystemExit, "Outdated notices"):
notices.main()
self.assertEqual(path.read_text(), "old")
if __name__ == "__main__":
unittest.main()