RedFlag/scripts/release-contract.py
Fimeg 67e26be2d9 projection: begin the exact-path public epoch
The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally.

Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
2026-09-08 21:59:33 -04:00

196 lines
10 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")
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()