Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/actions/setup-php-linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ The candidate PHP archive is asset 672923785,
`846f69bcf56fc25f672ab888415c12d40122b684c8fb9f1de87b7ddb64fa4272`.
The original installer is asset 713206915, 3,406 bytes, SHA-256
`7bc5bfea71696b0927367eb887e2710d44c6f17216fbd47add5a4cb181543e99`.
The reviewed snapshot of those exact bytes is committed under
`qualification/vendor/php-ubuntu-713206915/install.sh`. Setup seals that regular
source file directly and records its repository path and digest in the installer
receipt. It makes no installer download request; replacement, truncation, links
and changes during sealing refuse before installation. The effect verifier
derives its expected script identity from that same sealed original, so a later
pathname replacement cannot change the execution input. Canonical terminal
qualification independently revalidates the committed source after product
commands. The archive and PHAR
acquisition and verification paths remain unchanged. The configured setup
receipt is version 5; prior version-4 evidence remains bound to its original
helper and does not qualify this changed source path.
The two literal PHAR identities and publisher evidence are in
`qualification/php-linux-tools.json`. General selected PHPUnit's captured
publisher digest authority; its detached signature remains unverified.
Expand All @@ -37,7 +49,7 @@ The recipe requires the existing authenticated shared CA output, exact digest
and an empty additional-root directory. The existing `bootstrap-patched-pip`
action's `clients: python` export can supply that predecessor without an APT
route. Network reads use an exclusive Python TLS context and no proxy/auth.
GitHub asset reads use literal asset IDs and closed redirects; PHAR reads use
Archive reads use literal GitHub asset IDs and closed redirects; PHAR reads use
literal URLs with no redirects. No tag discovery, retry or fallback exists.

Every installer/archive/tool stream must match its independent size and digest
Expand Down
35 changes: 33 additions & 2 deletions scripts/php_linux_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pathlib import Path
import re
import ssl
import stat
import subprocess
import time
from typing import BinaryIO
Expand Down Expand Up @@ -246,6 +247,30 @@ def hold_verified(source: BinaryIO, identity: dict, final_url: str):
yield held


def installer_source_origin(identity: dict) -> dict:
asset_identity(identity, "install.sh", MAX_INSTALLER)
return {"kind": "repository", "path":
f"qualification/vendor/php-ubuntu-{identity['id']}/install.sh",
"digest": identity["digest"]}


@contextmanager
def hold_source_installer(identity: dict):
"""Seal the committed upstream snapshot without a rolling release request."""
origin = installer_source_origin(identity)
descriptor = os.open(ROOT / origin["path"], os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
with os.fdopen(descriptor, "rb") as source:
before = os.fstat(source.fileno())
if not stat.S_ISREG(before.st_mode) or before.st_size != identity["size"]:
raise ValueError("Committed installer is not the bounded regular input")
with hold_bytes(source, identity, origin) as held:
after = os.fstat(source.fileno())
fields = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns")
if any(getattr(before, key) != getattr(after, key) for key in fields):
raise ValueError("Committed installer changed while sealing")
yield held


def replace_once(source: str, before: str, after: str) -> str:
if source.count(before) != 1:
raise ValueError("Pinned upstream patch location is absent or ambiguous")
Expand Down Expand Up @@ -342,7 +367,7 @@ def observed_host() -> dict:

def run_held_installer(script: HeldAsset, archive: HeldAsset, arguments: tuple[str, str, str],
environment: dict[str, str], catalog: dict, execute=subprocess.run, host=None,
no_apt=False, normalized=None):
no_apt=False, normalized=None, *, original_installer: HeldAsset):
"""Effect primitive only; no shipped CLI/action currently invokes this.

The proposed consumer must first execve into this closed environment and
Expand All @@ -355,7 +380,13 @@ def run_held_installer(script: HeldAsset, archive: HeldAsset, arguments: tuple[s
raise ValueError("Exact PHP/build/thread-safety arguments required")
selected = select(catalog, **(observed_host() if host is None else host),
php=arguments[0], build=arguments[1], thread_safety=arguments[2])
original = (ROOT / "qualification/vendor/php-ubuntu-713206915/install.sh").read_bytes()
if not isinstance(original_installer, HeldAsset):
raise ValueError("A held original installer is required")
original_installer.assert_sealed()
if (original_installer.identity != catalog["installer"]
or original_installer.effective != installer_source_origin(catalog["installer"])):
raise ValueError("Original installer is not the held committed source")
original = os.pread(original_installer.descriptor, original_installer.identity["size"], 0)
derived = no_apt_installer_source(original, catalog) if no_apt else installer_source(original, catalog)
derived_identity = dict(catalog["installer"], name="bound-install.sh", size=len(derived),
digest=SHA256_PREFIX + sha256(derived))
Expand Down
3 changes: 2 additions & 1 deletion scripts/php_linux_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ def validate_configured(configured):
def validate_source_receipts(report, state, plan, catalog):
original = (bootstrap.ROOT / "qualification/vendor/php-ubuntu-713206915/install.sh").read_bytes()
patched = bootstrap.no_apt_installer_source(original, catalog)
if report["installer"] != dict(catalog["installer"], derived_sha256=bootstrap.sha256(patched)):
if report["installer"] != dict(catalog["installer"], derived_sha256=bootstrap.sha256(patched),
source=bootstrap.installer_source_origin(catalog["installer"])):
raise ValueError("Installer receipt differs from its literal/derived identity")
received = report["archive"]
bootstrap.exact_keys(received, {*plan["archive"]["asset"], "effective"})
Expand Down
12 changes: 7 additions & 5 deletions scripts/setup_php_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
RECEIPT_NAME = "receipt.json"
RECEIPT_SCHEMA = "durable-workflow.php-linux-setup-receipt/v2"
NORMALIZED_RECEIPT_SCHEMA = "durable-workflow.php-linux-setup-receipt/v3"
CONFIGURED_RECEIPT_SCHEMA = "durable-workflow.php-linux-setup-receipt/v4"
CONFIGURED_RECEIPT_SCHEMA = "durable-workflow.php-linux-setup-receipt/v5"
MAX_RECEIPT = 60 * 1024
HOST_ARCHIVES = {'26.04': 672923785, '24.04': 672921837}
CA_SHA256 = "50f79f98dac192b20dbf73f35c56f0b0c557ab267ff5199d1117cc1180b70785"
Expand Down Expand Up @@ -159,8 +159,8 @@ def run_setup(state: Path, request: dict, node: dict, environment: dict, *,
"preinstalled_authority": False, "tools": []}
try:
with ExitStack() as stack:
report["stage"] = "installer-read"
original = stack.enter_context(asset_fetch(catalog["installer"], context))
report["stage"] = "installer-source-read"
original = stack.enter_context(bootstrap.hold_source_installer(catalog["installer"]))
report["stage"] = "archive-read"
held_archive = stack.enter_context(asset_fetch(plan["archive"]["asset"], context))
report["stage"] = "tool-reads"
Expand All @@ -179,13 +179,15 @@ def run_setup(state: Path, request: dict, node: dict, environment: dict, *,
raise ValueError('Strict root-input inspection differs from the complete derivation')
report['normalization'] = normalized.proof
report['normalization_manifest'] = normalization.write_manifest(state, normalized)
report.update(installer=dict(original.identity, derived_sha256=bootstrap.sha256(patched)),
report.update(installer=dict(original.identity, derived_sha256=bootstrap.sha256(patched),
source=original.effective),
archive=dict(held_archive.identity, effective=held_archive.effective))
# Always install the bound tuple before any product PHP probe.
# An ambient preinstalled PHP binary is never an archive receipt.
report["stage"] = "bound-installer"
install_php(script, held_archive, (request["inputs"]["php-version"], "release", "nts"),
environment, catalog, no_apt=True, normalized=normalized)
environment, catalog, no_apt=True, normalized=normalized,
original_installer=original)
bind_installed_files(report, members, plan, verify_php)
report['stage'] = 'bound-cli-configuration'
config_document, config_record, config_plan = configure_cli(state, members, files, environment,
Expand Down
Loading
Loading