Publish complete signed module catalogs
Dependency Audit / dependency-audit (push) Successful in 1m46s
Deployment Installer / deployment-installer (push) Successful in 5s
Security Audit / security-audit (push) Successful in 11m49s

This commit is contained in:
2026-08-06 21:13:33 +02:00
parent f3cfd1bccc
commit 09046e6e62
17 changed files with 666 additions and 394 deletions
+80 -8
View File
@@ -64,17 +64,26 @@ def resolve(args: argparse.Namespace) -> dict[str, object]:
python=args.python,
index_url=str(package_set["registries"]["python"]),
)
_download_webui(
webui_registry_metadata = _download_webui(
packages=tuple(package_set["webui"]),
destination=webui,
npm=args.npm,
registry=str(package_set["registries"]["npm"]),
)
python_rows = _verify_wheels(tuple(package_set["python"]), wheels)
webui_rows = _verify_webui(tuple(package_set["webui"]), webui)
python_rows = _verify_wheels(
tuple(package_set["python"]),
wheels,
registry=str(package_set["registries"]["python"]),
)
webui_rows = _verify_webui(
tuple(package_set["webui"]),
webui,
registry_metadata=webui_registry_metadata,
)
lock: dict[str, object] = {
"schema_version": "1",
"release_version": package_set["release_version"],
"profile": package_set.get("profile", "base"),
"package_set_sha256": package_set["package_set_sha256"],
"registries": package_set["registries"],
"python": python_rows,
@@ -169,7 +178,7 @@ def _download_wheels(
def _download_webui(
*, packages: tuple[dict[str, object], ...], destination: Path, npm: str, registry: str
) -> None:
) -> dict[str, dict[str, str]]:
environment = dict(os.environ)
npmrc: tempfile.NamedTemporaryFile[bytes] | None = None
token = os.environ.get("GOVOPLAN_PACKAGE_TOKEN", "")
@@ -182,7 +191,31 @@ def _download_webui(
os.chmod(npmrc.name, 0o600)
environment["NPM_CONFIG_USERCONFIG"] = npmrc.name
try:
metadata: dict[str, dict[str, str]] = {}
for item in packages:
view = subprocess.run(
[npm, "view", f"{item['name']}@{item['version']}", "dist", "--json", "--registry", registry],
check=True,
env=environment,
text=True,
stdout=subprocess.PIPE,
)
dist = json.loads(view.stdout)
if not isinstance(dist, dict):
raise PackageArtifactError(f"npm registry returned no distribution metadata for {item['name']}")
tarball = dist.get("tarball")
integrity = dist.get("integrity")
parsed = urlsplit(str(tarball or ""))
if (
parsed.scheme != "https"
or not parsed.netloc
or parsed.username
or parsed.password
or not isinstance(integrity, str)
or not integrity.startswith("sha512-")
):
raise PackageArtifactError(f"npm registry returned unsafe distribution metadata for {item['name']}")
metadata[str(item["name"])] = {"url": str(tarball), "integrity": integrity}
subprocess.run(
[npm, "pack", f"{item['name']}@{item['version']}", "--ignore-scripts", "--pack-destination", str(destination), "--registry", registry],
check=True,
@@ -191,9 +224,15 @@ def _download_webui(
finally:
if npmrc is not None:
Path(npmrc.name).unlink(missing_ok=True)
return metadata
def _verify_wheels(packages: tuple[dict[str, object], ...], root: Path) -> list[dict[str, object]]:
def _verify_wheels(
packages: tuple[dict[str, object], ...],
root: Path,
*,
registry: str | None = None,
) -> list[dict[str, object]]:
expected = {_normalize(str(item["name"])): item for item in packages}
rows: list[dict[str, object]] = []
seen: set[str] = set()
@@ -204,13 +243,21 @@ def _verify_wheels(packages: tuple[dict[str, object], ...], root: Path) -> list[
if package is None or identity["version"] != package["version"] or name in seen:
raise PackageArtifactError(f"unexpected wheel artifact: {path.name}")
seen.add(name)
rows.append(_artifact_row(path, package))
row = _artifact_row(path, package)
if registry:
row["url"] = _python_artifact_url(registry, package=package, filename=path.name)
rows.append(row)
if seen != set(expected):
raise PackageArtifactError("registry did not return every selected Python wheel")
return sorted(rows, key=lambda item: str(item["name"]))
def _verify_webui(packages: tuple[dict[str, object], ...], root: Path) -> list[dict[str, object]]:
def _verify_webui(
packages: tuple[dict[str, object], ...],
root: Path,
*,
registry_metadata: dict[str, dict[str, str]] | None = None,
) -> list[dict[str, object]]:
expected = {str(item["name"]): item for item in packages}
rows: list[dict[str, object]] = []
seen: set[str] = set()
@@ -222,7 +269,13 @@ def _verify_webui(packages: tuple[dict[str, object], ...], root: Path) -> list[d
raise PackageArtifactError(f"unexpected WebUI artifact: {path.name}")
seen.add(name)
row = _artifact_row(path, package)
row["integrity"] = "sha512-" + base64.b64encode(hashlib.sha512(path.read_bytes()).digest()).decode("ascii")
integrity = "sha512-" + base64.b64encode(hashlib.sha512(path.read_bytes()).digest()).decode("ascii")
row["integrity"] = integrity
metadata = (registry_metadata or {}).get(name)
if metadata:
if metadata.get("integrity") != integrity:
raise PackageArtifactError(f"npm registry integrity does not match downloaded package: {name}")
row["url"] = metadata["url"]
rows.append(row)
if seen != set(expected):
raise PackageArtifactError("registry did not return every selected WebUI package")
@@ -283,6 +336,25 @@ def _artifact_row(path: Path, package: dict[str, object]) -> dict[str, object]:
return row
def _python_artifact_url(
registry: str,
*,
package: dict[str, object],
filename: str,
) -> str:
parsed = urlsplit(registry.rstrip("/"))
path = parsed.path.rstrip("/")
if not path.endswith("/simple"):
raise PackageArtifactError("Python registry URL must end in /simple to derive immutable artifacts")
artifact_path = (
f"{path.removesuffix('/simple')}/files/"
f"{quote(str(package['name']), safe='')}/"
f"{quote(str(package['version']), safe='')}/"
f"{quote(filename, safe='')}"
)
return urlunsplit((parsed.scheme, parsed.netloc, artifact_path, "", ""))
def _write_requirements(path: Path, rows: list[dict[str, object]]) -> None:
lines = ["--no-index", "--find-links ./local-wheels", "--require-hashes"]
for row in rows: