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
24 changes: 21 additions & 3 deletions scripts/ci/activity_acceptance_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

from __future__ import annotations

import hashlib
import json
import os
import stat
from pathlib import Path

RESULT_NAME = "result.json"
RESULT_LIMIT = 128 * 1024


class Output:
Expand Down Expand Up @@ -83,9 +85,25 @@ def finish(self, report):
except ValueError:
report["output_failure"] = "identity_changed"
report["complete"] = False
encoded = json.dumps(report, indent=2) + "\n"
if len(encoded.encode()) > 128 * 1024:
raise ValueError("terminal result exceeded its bound")
encoded = (json.dumps(report, indent=2) + "\n").encode()
if len(encoded) > RESULT_LIMIT:
report["complete"] = False
receipt = {
"complete": False, "output_failure": "terminal_result_exceeded_bound",
"oversized_report_bytes": len(encoded), "oversized_report_sha256": hashlib.sha256(encoded).hexdigest(),
}
if "output_failure" in report:
receipt["output_boundary_failure"] = str(report["output_failure"])[:1024]
for name in ("scenario", "artifact_source", "source_commit", "started_at", "finished_at",
"runtime_id", "consumer_id", "network_id"):
value = report.get(name)
if isinstance(value, str) and len(value) <= 1024:
receipt[name] = value
cleanup = json.dumps(report.get("cleanup"), sort_keys=True).encode()
receipt["cleanup"] = report.get("cleanup") if len(cleanup) <= RESULT_LIMIT // 2 else {
"retained": False, "bytes": len(cleanup), "sha256": hashlib.sha256(cleanup).hexdigest(),
}
encoded = (json.dumps(receipt, sort_keys=True, separators=(",", ":")) + "\n").encode()
self._write(self.files[RESULT_NAME], encoded)

def close(self):
Expand Down
11 changes: 10 additions & 1 deletion scripts/ci/published-activity-acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ def packet_file(data):
return {"base64": base64.b64encode(data).decode(), "sha256": hashlib.sha256(data).hexdigest()}


def retain_consumer_ready(files, report, ready):
# The complete environment inventory can exceed the terminal receipt bound.
# Keep its bytes independently and bind them rather than duplicating them.
data = json.dumps({key: value for key, value in ready.items() if key != "sdk_inventory"}, indent=2).encode()
name = "consumer-ready.json"
files.write(name, data)
report["consumer_ready"] = {"file": name, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}


def bind_bootstrap(captured):
source = captured["bootstrap.py"].decode()
for name, filename in (("CAPTURED_VERIFIER", VERIFIER_FILENAME), ("CAPTURED_ENTRY", "entry.py")):
Expand Down Expand Up @@ -525,7 +534,7 @@ def run_case(owned, selected, images, packet, bootstrap, report):
name = "identity" if SCENARIO == "activity-identity" else SCENARIO
owned.files.write(name + ".log", result["case_output"])
owned.files.write(name + ".xml", result["junit"])
report["consumer_ready"] = {key: value for key, value in ready.items() if key != "sdk_inventory"}
retain_consumer_ready(owned.files, report, ready)
session.send({"release": True})
code = session.finish()
report["consumer_exit"] = code
Expand Down
42 changes: 42 additions & 0 deletions tests/test_activity_acceptance_authority.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,45 @@ def test_replaced_result_cannot_become_a_successful_terminal_receipt(tmp_path):
assert (output.path / "result.json").read_text() == "foreign"
finally:
output.close()


def test_oversized_report_retains_bounded_nonpass_and_exact_cleanup_ids(tmp_path):
output = files.Output(tmp_path / "result")
cleanup = {"a" * 64: {"absent": True, "oom_killed": False}, "remaining-owned-labels": {"absent": True}}
report = {"complete": True, "scenario": "pagination", "runtime_id": "a" * 64,
"consumer_id": "b" * 64, "network_id": "c" * 64, "cleanup": cleanup,
"large_inventory": "x" * (files.RESULT_LIMIT + 1)}
try:
output.finish(report)
data = (output.path / "result.json").read_bytes()
receipt = json.loads(data)
assert len(data) <= files.RESULT_LIMIT
assert receipt["complete"] is report["complete"] is False
assert receipt["output_failure"] == "terminal_result_exceeded_bound"
assert receipt["runtime_id"] == "a" * 64
assert receipt["consumer_id"] == "b" * 64
assert receipt["network_id"] == "c" * 64
assert receipt["cleanup"] == cleanup
assert receipt["oversized_report_bytes"] > files.RESULT_LIMIT
finally:
output.close()


def test_oversized_unbounded_cleanup_is_explicitly_unqualified_and_cannot_overwrite_foreign_path(tmp_path):
output = files.Output(tmp_path / "result")
original = tmp_path / "retained"
output.path.rename(original)
output.path.mkdir()
(output.path / "result.json").write_text("foreign")
report = {"complete": True, "cleanup": {"untrusted": "x" * (files.RESULT_LIMIT * 2)}}
try:
output.finish(report)
data = (original / "result.json").read_bytes()
receipt = json.loads(data)
assert len(data) <= files.RESULT_LIMIT
assert receipt["complete"] is False
assert receipt["output_boundary_failure"] == "identity_changed"
assert receipt["cleanup"]["retained"] is False
assert (output.path / "result.json").read_text() == "foreign"
finally:
output.close()
21 changes: 21 additions & 0 deletions tests/test_published_activity_acceptance.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import hashlib
import importlib.util
import json
from copy import deepcopy
Expand All @@ -17,6 +18,26 @@
runtime = runner.runtime


def test_large_readiness_inventory_is_retained_and_hash_bound_without_terminal_duplication(tmp_path):
ready = {"distributions": "fixed locked wheels", "environment_inventory": {"entry": "x" * 200_000},
"sdk_inventory": {"already": "retained separately"}}
report = {"complete": True}
files = runner.output_files.Output(tmp_path / "result")
try:
runner.retain_consumer_ready(files, report, ready)
files.finish(report)
data = (files.path / "consumer-ready.json").read_bytes()
assert json.loads(data) == {key: value for key, value in ready.items() if key != "sdk_inventory"}
assert report["consumer_ready"] == {
"file": "consumer-ready.json", "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest(),
}
result = (files.path / "result.json").read_bytes()
assert len(result) < runner.output_files.RESULT_LIMIT
assert json.loads(result)["complete"] is True
finally:
files.close()


def configuration(role="runtime"):
image = {
"Id": "sha256:" + "f" * 64,
Expand Down
Loading