aureon-os / deployment /google-cloud /build_release_bundle.py
GaryLeckey's picture
Deploy Aureon OS (deploy/selfhost stack: nginx edge + fusion API x2 + console + HNC organ + redis + postgres; paper mode) (part 7)
c626614 verified
Raw History Blame Contribute Delete
26 kB
"""Copy only an explicit reviewed source list into a new immutable release bundle."""
from __future__ import annotations
import argparse
import io
import json
import os
import re
import shutil
import subprocess
import tarfile
import tempfile
import time
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from release_update import (MAX_RELEASE_BYTES, SCHEMA, ReleaseError, confined,
digest, durable_write, manifest_payload_sha256,
read_bytes, source_path)
from operator_runtime_closure import (
OPERATOR_COMPANION_SOURCES,
OPERATOR_PACKAGE_PREFIX,
OperatorRuntimeClosureError,
build_operator_runtime_manifest,
operator_package_runtime_sources,
operator_runtime_requested,
)
FRONTEND_DIST_PREFIX = "aureon-os-upstream/frontend/dist/"
FRONTEND_DIST_INDEX = FRONTEND_DIST_PREFIX + "index.html"
FRONTEND_SOURCE_ROOT = "aureon-os-upstream/frontend"
FRONTEND_BUILD_SCHEMA = "aureon.frontend-reproducible-build.v1"
FRONTEND_BUILD_RECIPE = ("npm ci --ignore-scripts --no-audit --no-fund", "npm run build")
FRONTEND_REQUIRED_ENV = frozenset(
{"VITE_REQUIRE_AUTH", "VITE_SUPABASE_URL", "VITE_SUPABASE_PUBLISHABLE_KEY"}
)
FRONTEND_ALLOWED_ENV = FRONTEND_REQUIRED_ENV | frozenset(
{
"VITE_AUREON_MANIFEST_BASE",
"VITE_NEWS_API_ENDPOINT",
"VITE_NEWS_PROXY_URL",
"VITE_NEXUS_WS_URL",
"VITE_SUPPORT_PAYMENT_URLS",
"VITE_SUPPORT_PROMPT_HOURS",
"VITE_SUPPORT_SNOOZE_HOURS",
}
)
VM_RUNTIME_TRIGGER_SOURCES = frozenset(
{
"deployment/google-cloud/cloud_read_organ.py",
"deployment/google-cloud/cloud_trade_receipt_collector.py",
"deployment/google-cloud/cloud_order_intent_bridge.py",
}
)
TOKEN_BROKER_SOURCES = frozenset(
{
"deployment/google-cloud/aureon-cloud-token-broker.service",
"deployment/google-cloud/vm_token_broker.py",
"deployment/google-cloud/vm_workload_identity.py",
}
)
PUBLIC_BRIDGE_SOURCE = "deployment/google-cloud/public-entry/public_entry.py"
PUBLIC_BRIDGE_UNIT_SOURCE = (
"deployment/google-cloud/public-entry/aureon-public-bridge.service"
)
PUBLIC_BRIDGE_SOURCES = frozenset(
{PUBLIC_BRIDGE_SOURCE, PUBLIC_BRIDGE_UNIT_SOURCE}
)
BRIDGE_CERTIFICATE_RECEIPT_SCHEMA = (
"aureon.gcp.vm-public-bridge-certificate-receipt.v1"
)
def _git(source: Path, *args: str) -> str:
try:
result = subprocess.run(
["git", "-C", str(source), *args], capture_output=True, text=True,
timeout=30, check=False,
)
except (OSError, subprocess.TimeoutExpired) as error:
raise ReleaseError("source_git_identity_unavailable") from error
if result.returncode != 0:
raise ReleaseError("source_git_identity_unavailable")
return result.stdout.strip()
def _git_blob(source: Path, revision: str, relative: str) -> bytes:
"""Read exact committed bytes so bundle hashes ignore checkout EOL policy."""
try:
result = subprocess.run(
["git", "-C", str(source), "cat-file", "blob", f"{revision}:{relative}"],
capture_output=True,
timeout=30,
check=False,
)
except (OSError, subprocess.TimeoutExpired) as error:
raise ReleaseError("committed_source_bytes_unavailable") from error
if result.returncode != 0 or len(result.stdout) > 32 * 1024 * 1024:
raise ReleaseError("committed_source_bytes_unavailable")
return result.stdout
def _bind_git_source(source: Path, revision: str, selected_paths: list[str]) -> tuple[str, str]:
root = _git(source, "rev-parse", "--show-toplevel")
if os.path.normcase(str(Path(root).resolve())) != os.path.normcase(str(source.resolve())):
raise ReleaseError("source_must_be_git_checkout_root")
head = _git(source, "rev-parse", "--verify", "HEAD")
tree = _git(source, "rev-parse", "--verify", "HEAD^{tree}")
if head != revision or not re.fullmatch(r"[0-9a-f]{40}", head) or not re.fullmatch(r"[0-9a-f]{40}", tree):
raise ReleaseError("release_revision_does_not_match_git_head")
if _git(source, "status", "--porcelain=v1", "--untracked-files=no"):
raise ReleaseError("tracked_source_checkout_must_be_clean")
for relative in selected_paths:
# Frontend artifacts are rebuilt from the exact commit below. They are
# deliberately ignored by Git so generated bytes cannot masquerade as
# reviewed source.
if relative.startswith(FRONTEND_DIST_PREFIX):
continue
try:
_git(source, "ls-files", "--error-unmatch", "--", relative)
except ReleaseError as error:
raise ReleaseError("selected_source_not_tracked_at_revision") from error
return head, tree
def _frontend_dist_inventory(source: Path) -> list[str]:
root = source / "aureon-os-upstream/frontend/dist"
if root.is_symlink() or not root.is_dir():
raise ReleaseError("frontend_dist_root_required")
paths: list[str] = []
for path in root.rglob("*"):
if path.is_symlink():
raise ReleaseError("frontend_dist_link_forbidden")
if path.is_file():
paths.append(path.relative_to(source).as_posix())
return sorted(paths)
def _operator_package_git_inventory(source: Path, revision: str) -> list[str]:
"""Read package paths from the immutable commit tree, never the worktree."""
output = _git(
source,
"ls-tree",
"-r",
"--name-only",
revision,
"--",
OPERATOR_PACKAGE_PREFIX.rstrip("/"),
)
return output.splitlines()
def _git_archive(source: Path, revision: str, prefix: str) -> bytes:
try:
result = subprocess.run(
["git", "-C", str(source), "archive", "--format=tar", revision, "--", prefix],
capture_output=True,
timeout=120,
check=False,
)
except (OSError, subprocess.TimeoutExpired) as error:
raise ReleaseError("frontend_commit_archive_unavailable") from error
if result.returncode != 0 or not result.stdout:
raise ReleaseError("frontend_commit_archive_unavailable")
return result.stdout
def _extract_regular_archive(raw: bytes, destination: Path, prefix: str) -> None:
"""Extract a Git archive without permitting links or path traversal."""
try:
with tarfile.open(fileobj=io.BytesIO(raw), mode="r:") as archive:
for member in archive.getmembers():
name = PurePosixPath(member.name)
normalized = str(name)
is_prefix_directory = member.isdir() and (
normalized == prefix or prefix.startswith(normalized + "/")
)
if (
name.is_absolute()
or ".." in name.parts
or (
not normalized.startswith(prefix + "/")
and not is_prefix_directory
)
or member.issym()
or member.islnk()
or member.isdev()
):
raise ReleaseError("frontend_commit_archive_invalid")
target = destination.joinpath(*name.parts)
if member.isdir():
target.mkdir(parents=True, exist_ok=True)
continue
if not member.isfile():
raise ReleaseError("frontend_commit_archive_invalid")
target.parent.mkdir(parents=True, exist_ok=True)
handle = archive.extractfile(member)
if handle is None:
raise ReleaseError("frontend_commit_archive_invalid")
data = handle.read(MAX_RELEASE_BYTES + 1)
if len(data) > MAX_RELEASE_BYTES:
raise ReleaseError("frontend_commit_archive_invalid")
target.write_bytes(data)
except (tarfile.TarError, OSError) as error:
raise ReleaseError("frontend_commit_archive_invalid") from error
def _validate_frontend_environment(value: dict[str, str] | None) -> dict[str, str]:
if not isinstance(value, dict) or set(value) - FRONTEND_ALLOWED_ENV:
raise ReleaseError("frontend_public_build_environment_invalid")
if not FRONTEND_REQUIRED_ENV.issubset(value) or value.get("VITE_REQUIRE_AUTH") != "1":
raise ReleaseError("frontend_authenticated_build_environment_required")
if re.fullmatch(r"https://[a-z0-9-]+\.supabase\.co", value.get("VITE_SUPABASE_URL", "")) is None:
raise ReleaseError("frontend_supabase_url_invalid")
key = value.get("VITE_SUPABASE_PUBLISHABLE_KEY", "")
if not isinstance(key, str) or not 20 <= len(key) <= 4096 or re.search(r"\s", key):
raise ReleaseError("frontend_supabase_publishable_key_invalid")
for name, item in value.items():
if not isinstance(name, str) or not isinstance(item, str) or len(item) > 8192 or "\x00" in item:
raise ReleaseError("frontend_public_build_environment_invalid")
if any(marker in name for marker in ("SECRET", "PASSWORD", "PRIVATE", "SERVICE_ROLE")):
raise ReleaseError("frontend_secret_build_environment_forbidden")
return dict(sorted(value.items()))
def _frontend_toolchain_executables(
*, platform: str | None = None, resolver=None
) -> tuple[str, str]:
lookup = shutil.which if resolver is None else resolver
npm_name = "npm.cmd" if (os.name if platform is None else platform) == "nt" else "npm"
npm = lookup(npm_name)
node = lookup("node")
if npm is None or node is None:
raise ReleaseError("frontend_pinned_builder_unavailable")
return npm, node
def _default_frontend_builder(
source: Path, revision: str, environment: dict[str, str]
) -> tuple[dict[str, bytes], dict[str, str]]:
"""Rebuild dist from a clean Git archive and a lockfile-only install."""
npm, node = _frontend_toolchain_executables()
raw = _git_archive(source, revision, FRONTEND_SOURCE_ROOT)
with tempfile.TemporaryDirectory(prefix="aureon-frontend-build-") as temporary:
root = Path(temporary)
_extract_regular_archive(raw, root, FRONTEND_SOURCE_ROOT)
frontend = root / FRONTEND_SOURCE_ROOT
if not (frontend / "package-lock.json").is_file():
raise ReleaseError("frontend_lockfile_required")
clean_env = {
key: os.environ[key]
for key in ("PATH", "SystemRoot", "SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT")
if key in os.environ
}
clean_env.update(
{
"CI": "1",
"TZ": "UTC",
"NPM_CONFIG_AUDIT": "false",
"NPM_CONFIG_FUND": "false",
"NPM_CONFIG_UPDATE_NOTIFIER": "false",
"npm_config_cache": str(root / "npm-cache"),
**environment,
}
)
for command in (
[npm, "ci", "--ignore-scripts", "--no-audit", "--no-fund"],
[npm, "run", "build"],
):
try:
result = subprocess.run(
command,
cwd=frontend,
env=clean_env,
capture_output=True,
timeout=900,
check=False,
)
except (OSError, subprocess.TimeoutExpired) as error:
raise ReleaseError("frontend_reproducible_build_failed") from error
if result.returncode != 0:
raise ReleaseError("frontend_reproducible_build_failed")
files: dict[str, bytes] = {}
dist = frontend / "dist"
if dist.is_symlink() or not dist.is_dir():
raise ReleaseError("frontend_reproducible_build_failed")
for path in sorted(dist.rglob("*")):
if path.is_symlink():
raise ReleaseError("frontend_dist_link_forbidden")
if path.is_file():
relative = FRONTEND_DIST_PREFIX + path.relative_to(dist).as_posix()
files[relative] = read_bytes(path)
try:
node_version = subprocess.run(
[node, "--version"], capture_output=True, text=True, timeout=10, check=True
).stdout.strip()
npm_version = subprocess.run(
[npm, "--version"], capture_output=True, text=True, timeout=10, check=True
).stdout.strip()
except (OSError, subprocess.SubprocessError) as error:
raise ReleaseError("frontend_pinned_builder_unavailable") from error
return files, {"node": node_version, "npm": npm_version}
def _vm_python_runtime_manifest(
package_tree: list[str], rows: list[dict], *, source_git_tree: str
) -> dict:
try:
required = frozenset(operator_package_runtime_sources(package_tree)) | OPERATOR_COMPANION_SOURCES
except OperatorRuntimeClosureError as error:
raise ReleaseError(str(error)) from error
row_by_path = {row["path"]: row for row in rows}
if not required.issubset(row_by_path):
raise ReleaseError("complete_vm_python_runtime_sources_required")
selected = [row_by_path[path] for path in sorted(required)]
return {
"schema": "aureon.vm-python-runtime-closure.v1",
"source_git_tree": source_git_tree,
"sources": sorted(required),
"files": len(required),
"payload_sha256": manifest_payload_sha256(selected),
}
def _component_manifest(rows: list[dict], required: frozenset[str], reason: str) -> dict:
row_by_path = {row["path"]: row for row in rows}
if not required.issubset(row_by_path):
raise ReleaseError(reason)
selected = [row_by_path[path] for path in sorted(required)]
return {
"sources": sorted(required),
"files": len(required),
"payload_sha256": manifest_payload_sha256(selected),
}
def _parse_receipt_time(value: object) -> float:
if not isinstance(value, str) or len(value) > 64:
raise ReleaseError("bridge_certificate_fingerprint_receipt_invalid")
try:
return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC).timestamp()
except (ValueError, TypeError, OverflowError) as error:
raise ReleaseError("bridge_certificate_fingerprint_receipt_invalid") from error
def _certificate_receipt(
path: Path | None,
expected_sha256: str,
revision: str,
*,
now: float | None = None,
) -> dict:
if path is None or re.fullmatch(r"[0-9a-f]{64}", expected_sha256 or "") is None:
raise ReleaseError("bridge_certificate_fingerprint_receipt_required")
raw = read_bytes(path, 1024 * 1024)
if digest(raw) != expected_sha256:
raise ReleaseError("bridge_certificate_fingerprint_receipt_hash_mismatch")
try:
value = json.loads(raw)
except (ValueError, TypeError, UnicodeError) as error:
raise ReleaseError("bridge_certificate_fingerprint_receipt_invalid") from error
recorded = _parse_receipt_time(value.get("recorded_at"))
not_before = _parse_receipt_time(value.get("not_before"))
not_after = _parse_receipt_time(value.get("not_after"))
current = time.time() if now is None else float(now)
if recorded > current + 300 or current - recorded > 21_600:
raise ReleaseError("bridge_certificate_fingerprint_receipt_not_fresh")
if (
value.get("schema") != BRIDGE_CERTIFICATE_RECEIPT_SCHEMA
or value.get("status") != "passed"
or value.get("project") != "aureon-trading"
or re.fullmatch(r"[a-z0-9-]+-[a-z0-9]+[0-9]-[a-z]", str(value.get("zone") or "")) is None
or re.fullmatch(r"[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?", str(value.get("instance") or "")) is None
or value.get("source_revision") != revision
or value.get("certificate_path") != "/etc/aureon-public/bridge-cert.pem"
or value.get("algorithm") != "x509-der-sha256"
or re.fullmatch(r"[0-9a-f]{64}", str(value.get("certificate_sha256") or "")) is None
or value.get("required_ip_sans") != ["10.40.0.2"]
or value.get("minimum_remaining_validity_seconds") != 86_400
or isinstance(value.get("minimum_remaining_validity_seconds"), bool)
or not_before > recorded
or recorded > not_after
or not_before > current
or not_after - current < 86_400
or value.get("private_key_included") is not False
or value.get("secret_values_recorded") is not False
):
raise ReleaseError("bridge_certificate_fingerprint_receipt_invalid")
return {
"schema": value["schema"],
"sha256": expected_sha256,
"recorded_at": value["recorded_at"],
"project": value["project"],
"zone": value["zone"],
"instance": value["instance"],
"fingerprint_sha256": value["certificate_sha256"],
"required_ip_sans": value["required_ip_sans"],
"not_before": value["not_before"],
"not_after": value["not_after"],
"minimum_remaining_validity_seconds": value[
"minimum_remaining_validity_seconds"
],
}
def build(
source: Path,
selected: list,
destination: Path,
revision: str,
release_id: str,
*,
frontend_build_environment: dict[str, str] | None = None,
frontend_builder=None,
bridge_certificate_receipt: Path | None = None,
bridge_certificate_receipt_sha256: str = "",
) -> dict:
if not re.fullmatch(r"[0-9a-f]{40}", revision) or not re.fullmatch(r"[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8,16}", release_id):
raise ReleaseError("exact_revision_and_dated_release_required")
if not isinstance(selected, list) or not selected or len(selected) > 20000:
raise ReleaseError("explicit_bounded_source_list_required")
if destination.exists() or destination.is_symlink():
raise ReleaseError("bundle_destination_must_be_new")
if destination.resolve().is_relative_to(source.resolve()):
raise ReleaseError("bundle_must_be_outside_source_checkout")
seen, selected_paths, selected_items = set(), [], []
for item in selected:
relative = source_path(item if isinstance(item, str) else item.get("path"))
if relative.casefold() in seen:
raise ReleaseError("duplicate_source_selection")
seen.add(relative.casefold())
selected_paths.append(relative)
selected_items.append((item, relative))
git_revision, git_tree = _bind_git_source(source, revision, selected_paths)
rows, payload, total = [], [], 0
for item, relative in selected_items:
data = (
read_bytes(confined(source, relative))
if relative.startswith(FRONTEND_DIST_PREFIX)
else _git_blob(source, revision, relative)
)
hashed = digest(data)
if isinstance(item, dict) and item.get("sha256") != hashed:
raise ReleaseError("reviewed_source_hash_changed")
total += len(data)
if total > MAX_RELEASE_BYTES:
raise ReleaseError("release_size_limit")
rows.append({"path": relative, "sha256": hashed, "size": len(data)})
payload.append((relative, data))
package_tree = _operator_package_git_inventory(source, revision)
operator_runtime = None
if operator_runtime_requested(selected_paths):
try:
operator_runtime = build_operator_runtime_manifest(
package_tree,
rows,
source_git_tree=git_tree,
)
except OperatorRuntimeClosureError as error:
raise ReleaseError(str(error)) from error
vm_python_runtime = None
if operator_runtime is not None or set(selected_paths) & VM_RUNTIME_TRIGGER_SOURCES:
vm_python_runtime = _vm_python_runtime_manifest(
package_tree, rows, source_git_tree=git_tree
)
selected_dist = sorted(path for path in selected_paths if path.startswith(FRONTEND_DIST_PREFIX))
frontend_dist = None
if selected_dist:
expected_dist = _frontend_dist_inventory(source)
if FRONTEND_DIST_INDEX not in selected_dist or selected_dist != expected_dist:
raise ReleaseError("complete_frontend_dist_inventory_required")
public_environment = _validate_frontend_environment(frontend_build_environment)
frontend_rows = [row for row in rows if row["path"].startswith(FRONTEND_DIST_PREFIX)]
rebuilt, toolchain = (frontend_builder or _default_frontend_builder)(
source, revision, public_environment
)
actual = {
relative: read_bytes(confined(source, relative)) for relative in expected_dist
}
if set(rebuilt) != set(actual) or any(
digest(rebuilt[path]) != digest(actual[path]) for path in actual
):
raise ReleaseError("frontend_dist_not_reproducible_from_commit")
frontend_dist = {
"schema": FRONTEND_BUILD_SCHEMA,
"entrypoint": FRONTEND_DIST_INDEX,
"files": len(frontend_rows),
"sha256": manifest_payload_sha256(frontend_rows),
"source_git_revision": git_revision,
"source_git_tree": git_tree,
"recipe": list(FRONTEND_BUILD_RECIPE),
"environment_keys": sorted(public_environment),
"environment_sha256": digest(
json.dumps(public_environment, sort_keys=True, separators=(",", ":")).encode()
),
"toolchain": toolchain,
}
broker = None
if set(selected_paths) & {"deployment/google-cloud/aureon-cloud-token-broker.service", "deployment/google-cloud/vm_token_broker.py"}:
broker = _component_manifest(
rows, TOKEN_BROKER_SOURCES, "complete_token_broker_sources_required"
)
broker.update(
{
"schema": "aureon.vm-token-broker-release.v1",
"source_git_tree": git_tree,
}
)
public_bridge = None
if set(selected_paths) & PUBLIC_BRIDGE_SOURCES:
certificate_receipt = _certificate_receipt(
bridge_certificate_receipt,
bridge_certificate_receipt_sha256,
revision,
)
public_bridge = _component_manifest(
rows, PUBLIC_BRIDGE_SOURCES, "complete_public_bridge_sources_required"
)
row_by_path = {row["path"]: row for row in rows}
public_bridge.update(
{
"schema": "aureon.vm-public-bridge-release.v1",
"revision": revision,
"source_git_tree": git_tree,
"source_sha256": row_by_path[PUBLIC_BRIDGE_SOURCE]["sha256"],
"unit_sha256": row_by_path[PUBLIC_BRIDGE_UNIT_SOURCE]["sha256"],
"certificate": {
"path": "/etc/aureon-public/bridge-cert.pem",
"algorithm": "x509-der-sha256",
"sha256": certificate_receipt["fingerprint_sha256"],
"receipt": certificate_receipt,
},
}
)
destination.mkdir(parents=True, mode=0o700)
(destination / "files").mkdir()
for relative, data in payload:
durable_write(confined(destination / "files", relative), data, 0o644)
manifest = {"schema": SCHEMA, "revision": revision, "release_id": release_id,
"source_git_revision": git_revision, "source_git_tree": git_tree,
"payload_sha256": manifest_payload_sha256(rows),
"frontend_dist": frontend_dist,
"operator_runtime": operator_runtime,
"vm_python_runtime": vm_python_runtime,
"token_broker": broker,
"public_bridge": public_bridge,
"files": rows, "bytes": total,
"contains_runtime_state": False}
durable_write(destination / "manifest.json", (json.dumps(manifest, sort_keys=True, indent=2) + "\n").encode())
return manifest
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--paths", required=True, type=Path)
parser.add_argument("--destination", required=True, type=Path)
parser.add_argument("--revision", required=True)
parser.add_argument("--release-id", required=True)
parser.add_argument("--frontend-build-env", type=Path)
parser.add_argument("--bridge-certificate-receipt", type=Path)
parser.add_argument("--bridge-certificate-receipt-sha256", default="")
args = parser.parse_args()
selected = json.loads(read_bytes(args.paths, 4 * 1024 * 1024))
frontend_environment = None
if args.frontend_build_env is not None:
frontend_environment = json.loads(
read_bytes(args.frontend_build_env, 64 * 1024)
)
manifest = build(
args.source,
selected,
args.destination,
args.revision,
args.release_id,
frontend_build_environment=frontend_environment,
bridge_certificate_receipt=args.bridge_certificate_receipt,
bridge_certificate_receipt_sha256=args.bridge_certificate_receipt_sha256,
)
print(json.dumps({"files": len(manifest["files"]), "bytes": manifest["bytes"], "revision": manifest["revision"]}))
if __name__ == "__main__":
main()