fix: verify full prebuilt images before offline production deployment
This commit is contained in:
+60
-17
@@ -127,6 +127,48 @@ def main():
|
||||
release(lock)
|
||||
|
||||
|
||||
def verify_prebuilt_image(image, commit, version, hashes):
|
||||
expected = os.getenv("FRAME_FLOW_PREBUILT_IMAGE_ID", "")
|
||||
if not expected:
|
||||
return False
|
||||
assert re.fullmatch(r"sha256:[0-9a-f]{64}", expected), "Require immutable image ID"
|
||||
metadata = json.loads(run("docker", "image", "inspect", image))[0]
|
||||
assert metadata["Id"] == expected, "Prebuilt image ID mismatch"
|
||||
labels = metadata["Config"].get("Labels") or {}
|
||||
assert labels.get("org.opencontainers.image.revision") == commit
|
||||
assert labels.get("org.opencontainers.image.version") == version
|
||||
project = {
|
||||
n: h
|
||||
for n, h in hashes.items()
|
||||
if n.startswith(("pigway_frame_flow/", "vendor/"))
|
||||
or n in {"requirements.lock", "LICENSE", "NOTICE.md"}
|
||||
}
|
||||
# Validate the artifact before it can access production config or data.
|
||||
code = "import hashlib,json,pathlib,sys; h=json.load(sys.stdin); assert h and all(hashlib.sha256((pathlib.Path('/app')/n).read_bytes()).hexdigest()==v for n,v in h.items())"
|
||||
subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"-i",
|
||||
"--network=none",
|
||||
"--read-only",
|
||||
"--cap-drop=ALL",
|
||||
"--security-opt=no-new-privileges",
|
||||
"--user=10333:10333",
|
||||
"--entrypoint=python",
|
||||
expected,
|
||||
"-c",
|
||||
code,
|
||||
],
|
||||
input=json.dumps(project),
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=60,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def release(lock):
|
||||
ctx = Path(__file__).resolve().parent.parent
|
||||
assert ctx.parent == ROOT / "migration-inbox"
|
||||
@@ -154,23 +196,24 @@ def release(lock):
|
||||
version = tomllib.loads((ctx / "pyproject.toml").read_text())["project"]["version"]
|
||||
assert re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version)
|
||||
image = "pigway-frame-flow:" + version + "-" + commit[:12]
|
||||
# A source-only overlay would retain vulnerable packages from the old image.
|
||||
run(
|
||||
"docker",
|
||||
"build",
|
||||
"--pull",
|
||||
"-f",
|
||||
str(ctx / "Dockerfile"),
|
||||
"--build-arg",
|
||||
"BUILD_COMMIT=" + commit,
|
||||
"--build-arg",
|
||||
"BUILD_TIME=" + stamp,
|
||||
"--build-arg",
|
||||
"ALPINE_MIRROR=" + os.getenv("ALPINE_MIRROR", "dl-cdn.alpinelinux.org"),
|
||||
"-t",
|
||||
image,
|
||||
str(ctx),
|
||||
)
|
||||
if not verify_prebuilt_image(image, commit, version, hashes):
|
||||
# A source-only overlay would retain vulnerable packages from the old image.
|
||||
run(
|
||||
"docker",
|
||||
"build",
|
||||
"--pull",
|
||||
"-f",
|
||||
str(ctx / "Dockerfile"),
|
||||
"--build-arg",
|
||||
"BUILD_COMMIT=" + commit,
|
||||
"--build-arg",
|
||||
"BUILD_TIME=" + stamp,
|
||||
"--build-arg",
|
||||
"ALPINE_MIRROR=" + os.getenv("ALPINE_MIRROR", "dl-cdn.alpinelinux.org"),
|
||||
"-t",
|
||||
image,
|
||||
str(ctx),
|
||||
)
|
||||
compose = json.loads((APP / "compose.json").read_text())
|
||||
service = compose["services"][CONTAINER]
|
||||
service["image"] = image
|
||||
|
||||
Reference in New Issue
Block a user