Files
KF-OnlineJudge/scripts/release/proxy_runtime.py
T

112 lines
4.1 KiB
Python

"""Closed public NGINX runtime inventory, independent from TLS and service state."""
import re
import stat
from pathlib import Path
from .policy import DeliveryError, digest, fields, integer, read_json, sha256
VERSION = "1.30.5"
MAX_BYTES = 64 * 1024**2
def validate(value: dict) -> dict:
fields(
value,
{
"schema_version",
"format",
"version",
"architecture",
"package_sha256",
"files",
"directories",
"payload_bytes",
},
"proxy runtime",
)
if (
type(value["schema_version"]) is not int
or value["schema_version"] != 1
or value["format"] != "kf.nginx.runtime.v1"
or value["version"] != VERSION
or value["architecture"] != "x86_64"
or value["directories"] != ["bin", "lib"]
or not isinstance(value["files"], list)
or not 1 <= len(value["files"]) <= 32
):
raise DeliveryError("unsupported proxy runtime profile")
sha256(value["package_sha256"])
names = set()
total = 0
for entry in value["files"]:
fields(entry, {"path", "bytes", "sha256", "mode"}, "proxy runtime file")
name = entry["path"]
if not isinstance(name, str) or (
name != "bin/nginx" and not re.fullmatch(r"lib/[A-Za-z0-9_.+-]+\.so(?:\.[0-9]+)*", name)
):
raise DeliveryError("non-runtime or private proxy member")
if name in names or entry["mode"] != ("0555" if name == "bin/nginx" else "0444"):
raise DeliveryError("duplicate proxy member or unsafe mode")
names.add(name)
total += integer(entry["bytes"], 1, 32 * 1024**2, "proxy file bytes")
sha256(entry["sha256"])
if (
not {"bin/nginx", "lib/libc.so.6", "lib/ld-linux-x86-64.so.2"} <= names
or total > MAX_BYTES
or type(value["payload_bytes"]) is not int
or value["payload_bytes"] != total
):
raise DeliveryError("incomplete or unbounded proxy runtime")
return value
def verify(root: Path, expected: str) -> dict:
if root.is_symlink() or not root.is_dir():
raise DeliveryError("ordinary proxy runtime directory required")
catalog = root / "manifest.json"
if catalog.is_symlink() or digest(catalog) != sha256(expected):
raise DeliveryError("proxy runtime out-of-band fingerprint differs")
value = validate(read_json(catalog, 16384))
if {p.name for p in root.iterdir()} != {"manifest.json", "bin", "lib"}:
raise DeliveryError("unexpected proxy runtime tree member")
for directory in (root / "bin", root / "lib"):
if (
directory.is_symlink()
or not directory.is_dir()
or stat.S_IMODE(directory.stat().st_mode) != 0o555
):
raise DeliveryError("readonly ordinary proxy runtime directories required")
names = set()
for directory in (root / "bin", root / "lib"):
for p in directory.iterdir():
names.add(p.relative_to(root).as_posix())
if names != {entry["path"] for entry in value["files"]}:
raise DeliveryError("proxy runtime inventory differs")
for entry in value["files"]:
p = root / entry["path"]
info = p.lstat()
if (
not stat.S_ISREG(info.st_mode)
or info.st_nlink != 1
or info.st_size != entry["bytes"]
or stat.S_IMODE(info.st_mode) != int(entry["mode"], 8)
or digest(p) != entry["sha256"]
):
raise DeliveryError("proxy runtime bytes or permission differ")
return value
def copy_to_payload(root: Path, expected: str, destination: Path) -> None:
from .build import copy
value = verify(root, expected)
if destination.exists() or destination.is_symlink():
raise DeliveryError("proxy runtime payload must be new")
destination.mkdir()
for directory in value["directories"]:
(destination / directory).mkdir()
for entry in value["files"]:
copy(root / entry["path"], destination / entry["path"])
copy(root / "manifest.json", destination / "manifest.json")