RedFlag/scripts/release-contract.py
Fimeg 71a324ac7c projection: admit the native Windows release
The native Desktop payload now enters the same public candidate and receipt as the Linux and server artifacts.

Source-Sha: 550aff16577a07f888f3ca150749265726072048
Policy-Sha: 550aff16577a07f888f3ca150749265726072048
Tree-Digest: f18eb172754ead927788c5aca2f3ad78b37103a21b657eb1a3328eb07cef0c7c
2026-09-10 12:57:20 -04:00

202 lines
11 KiB
Python

#!/usr/bin/env python3
"""RedFlag policy adapter for the unmodified shared release contract.
scripts/vendor/release-contract-core.py is byte-identical to
bin/release-contract-core.py from the shared release contract at commit
9fc81398b6696ee858468b774c66c0cba44a22b3. Provenance is held by that SHA
rather than by the name of the infrastructure that hosts it.
Three identities are kept apart, and must not be collapsed into one:
the public source repository that a release is built from and signed
against; the control repository whose reviewed workflow drives the build;
and the invocation that a particular run happened under. Only the first
belongs in public source, so the other two are supplied by the build
environment and never committed here.
The builder ID denotes the declared workflow lane, not a hardware attestation.
"""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
from types import SimpleNamespace
SPEC = importlib.util.spec_from_file_location("release_contract_core",
Path(__file__).parent / "vendor/release-contract-core.py")
CONTRACT = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(CONTRACT)
SOURCE_REPOSITORY = "https://forge.caseytunturi.com/Fimeg/RedFlag"
BUILD_TYPE = "https://caseytunturi.com/release-contract/build-types/gitea-actions/v1"
WORKFLOW = "Fimeg/RedFlag/.gitea/workflows/release.yml"
TARGETS = {"linux-amd64", "linux-arm64", "windows-amd64", "darwin-arm64"}
def control_repository():
# The repository whose reviewed workflow drives the build. It is read from
# the build environment rather than committed, so that public source never
# has to name the forge that development is hosted on. Verification is not
# weakened: the trusted stage supplies this and the value is still pinned.
server = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
repository = os.environ.get("GITHUB_REPOSITORY", "")
require(server and repository, "GITHUB_SERVER_URL and GITHUB_REPOSITORY are required")
return server + "/" + repository
def builder_id():
return control_repository() + "/.gitea/workflows/release.yml#stage_package"
def require(condition, message):
if not condition:
raise CONTRACT.ContractError(message)
def receipt(candidate, check_files=True):
entries = CONTRACT.parse_receipt(candidate)
require("manifest.json" in entries and "provenance.json" in entries, "receipt lacks evidence")
require("files.sha256" not in entries, "receipt cannot name itself")
for name, digest in entries.items():
require(re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-]*", name), "unsafe transport filename")
if not check_files:
continue
path = candidate / name
require(path.is_file() and not path.is_symlink(), "missing or unsafe receipt file: " + name)
require(CONTRACT.sha256(path) == digest, "receipt digest mismatch: " + name)
if check_files:
require({p.name for p in candidate.iterdir()} == set(entries) | {"files.sha256"}, "unreceipted candidate files")
return entries
def verify(candidate, version, commit, tag):
receipt(candidate)
CONTRACT.verify(SimpleNamespace(candidate=str(candidate), expect_project="redflag",
expect_source_commit=commit, expect_source_tag=tag, expect_builder_id=builder_id(),
expect_workflow_id=WORKFLOW + "@" + commit))
manifest = CONTRACT.load_json(candidate / "manifest.json")
provenance = CONTRACT.load_json(candidate / "provenance.json")
require(manifest["source"]["repository"] == SOURCE_REPOSITORY, "unexpected source repository")
require(manifest["version"] == version and tag == "v" + version, "unexpected version")
require(manifest.get("tag") == tag and manifest.get("source_sha") == commit, "legacy source fields disagree")
require(provenance["predicate"]["buildDefinition"]["externalParameters"] ==
{"project": "redflag", "sourceTag": tag, "version": version}, "provenance parameters disagree")
invocation = manifest["build"]["invocation_id"]
require(re.fullmatch(re.escape(control_repository()) + r"/actions/runs/[1-9][0-9]*", invocation),
"invalid invocation identity")
files = {a["filename"]: a for a in manifest["artifacts"]}
require(all(a["target_os"] + "-" + a["target_arch"] in TARGETS for a in files.values()), "unexpected artifact target")
for target in TARGETS:
os_name, arch = target.split("-")
for component in ("server", "agent", "helper"):
if component == "helper" and os_name == "windows":
continue
name = f"redflag-{component}-{target}" + (".exe" if os_name == "windows" else "")
require(name in files, "missing required artifact: " + name)
item = files[name]
platform = os_name if component == "agent" else component + "-" + os_name
require(item.get("platform") == platform and item.get("architecture") == arch,
"legacy target mismatch: " + name)
require(item["target_os"] == os_name and item["target_arch"] == arch,
"contract target mismatch: " + name)
require(f"redflag_{version}_amd64.deb" in files, "missing Debian package")
require("RedFlagSetup-windows-amd64.msi" in files, "missing Windows installer")
desktop = "redflag-desktop-windows-amd64.exe"
require(desktop in files, "missing native Windows Desktop: " + desktop)
require(files[desktop].get("platform") == "desktop-windows" and
files[desktop].get("architecture") == "amd64", "native Windows Desktop target mismatch")
require(f"redflag-{version}-windows-desktop-amd64.zip" in files,
"missing complete native Windows Desktop payload")
def assemble(args):
source = Path(args.artifacts)
candidate = Path(args.candidate)
require(not candidate.exists() and not candidate.is_symlink(), "candidate output already exists; choose a fresh directory")
CONTRACT.validate_commit(args.commit)
require(re.fullmatch(r"[0-9]+(?:\.[0-9]+){2,3}(?:[.-][0-9A-Za-z]+)*", args.version), "invalid version")
require(args.tag == "v" + args.version, "version/tag mismatch")
generated = int(subprocess.check_output(["git", "show", "-s", "--format=%ct", args.commit], text=True))
descriptors, legacy = [], {}
for target in sorted(TARGETS):
directory = source / ("release-" + target)
snippet = directory / ("release-" + target + ".artifacts.json")
for item in CONTRACT.load_json(snippet):
name = CONTRACT.safe_filename(item["filename"])
require(name not in legacy, "duplicate snippet filename: " + name)
path = directory / name
require(path.is_file() and not path.is_symlink(), "snippet payload missing: " + name)
require(CONTRACT.sha256(path) == item["sha256"] and path.stat().st_size == item["size"], "snippet digest mismatch: " + name)
legacy[name] = item
for path in sorted(directory.iterdir()):
if path == snippet:
continue
require(path.is_file() and not path.is_symlink(), "unsafe payload")
require(path.name not in {"manifest.json", "provenance.json", "files.sha256"}, "reserved payload name")
os_name, arch = target.split("-")
media, package = "application/octet-stream", "binary"
for suffix, kind, mime in ((".tar.gz", "generic", "application/gzip"), (".zip", "generic", "application/zip"),
(".deb", "deb", "application/vnd.debian.binary-package"), (".msi", "msi", "application/x-msi"),
(".txt", "generic", "text/plain")):
if path.name.endswith(suffix):
media, package = mime, kind
break
descriptors.append(dict(path=str(path), filename=path.name, target_os=os_name,
target_arch=arch, media_type=media, package_type=package))
with tempfile.TemporaryDirectory(prefix="redflag-contract-") as temporary:
temporary = Path(temporary)
declaration = temporary / "release.toml"
declaration.write_text('schema_version = 1\nproject = "redflag"\nversion = ' + json.dumps(args.version)
+ '\nsource_repository = ' + json.dumps(SOURCE_REPOSITORY)
+ '\nbuild_type = ' + json.dumps(BUILD_TYPE) + '\n')
descriptor_path = temporary / "artifacts.json"
CONTRACT.write_json(descriptor_path, descriptors)
CONTRACT.assemble(SimpleNamespace(declaration=str(declaration), artifacts=str(descriptor_path),
output=str(candidate), source_commit=args.commit, source_tag=args.tag, builder_id=builder_id(),
workflow_id=WORKFLOW + "@" + args.commit,
invocation_id=control_repository() + "/actions/runs/" + args.run_id))
manifest = CONTRACT.load_json(candidate / "manifest.json")
manifest.update(tag=args.tag, source_sha=args.commit, generated_at=generated, key_id="")
manifest["components"] = [dict(name=name, kind="embedded" if name == "web" else "binary",
required=name not in {"desktop", "installer"}, **({"version_cmd": "--version"} if name not in {"web", "installer"} else {}))
for name in ("server", "agent", "helper", "desktop", "web", "installer")]
manifest["components"][3]["provisioning"] = ["autostart_entry", "redflag-local_group", "desktop_user_membership"]
for item in manifest["artifacts"]:
if item["filename"] in legacy:
item.update(legacy[item["filename"]])
CONTRACT.write_json(candidate / "manifest.json", manifest)
names = [item["filename"] for item in manifest["artifacts"]] + ["manifest.json", "provenance.json"]
(candidate / "files.sha256").write_text("".join(f"{CONTRACT.sha256(candidate / name)} {name}\n" for name in sorted(names)))
verify(candidate, args.version, args.commit, args.tag)
def main():
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
for command in ("assemble", "verify", "receipt", "names"):
child = sub.add_parser(command)
child.add_argument("candidate", type=Path)
if command not in {"receipt", "names"}:
child.add_argument("version")
child.add_argument("commit")
child.add_argument("tag")
if command == "assemble":
child.add_argument("artifacts")
child.add_argument("run_id")
args = parser.parse_args()
try:
if args.command == "assemble":
assemble(args)
elif args.command == "verify":
verify(args.candidate, args.version, args.commit, args.tag)
else:
for name in receipt(args.candidate, check_files=args.command != "names"):
print(name)
except (CONTRACT.ContractError, OSError, ValueError, KeyError, TypeError) as error:
parser.exit(1, f"release contract: {error}\n")
if __name__ == "__main__":
main()