328 lines
13 KiB
Python
328 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Exercise the fail-closed artifact execution policy gate."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
|
|
BLOCKED_SHA256 = "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
|
|
|
|
|
def run(command: list[str], expected_decision: str) -> dict[str, object]:
|
|
result = subprocess.run(command, check=False, capture_output=True, text=True)
|
|
line = result.stdout.strip().splitlines()[-1]
|
|
document = json.loads(line)
|
|
if document.get("decision") != expected_decision:
|
|
raise RuntimeError(f"unexpected decision: {document}")
|
|
if expected_decision == "DENY" and result.returncode == 0:
|
|
raise RuntimeError("denied input returned success")
|
|
if expected_decision != "DENY" and result.returncode != 0:
|
|
raise RuntimeError(f"eligible input failed: {result.stderr}")
|
|
if document.get("execution_authorized") is not False:
|
|
raise RuntimeError("policy gate must never grant execution authority")
|
|
return document
|
|
|
|
|
|
def without_option(command: list[str], option: str) -> list[str]:
|
|
index = command.index(option)
|
|
return command[:index] + command[index + 2 :]
|
|
|
|
|
|
def write_manifest(path: Path, artifact: Path, eligible: bool) -> str:
|
|
source_commit = "1" * 40
|
|
path.write_text(
|
|
json.dumps(
|
|
{
|
|
"artifact": {
|
|
"filename": artifact.name,
|
|
"id": "execution-policy-test",
|
|
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
|
"size": artifact.stat().st_size,
|
|
"target": "test",
|
|
"version": "1",
|
|
},
|
|
"execution": {
|
|
"authorized": False,
|
|
"executed": False,
|
|
"execution_eligible": eligible,
|
|
"transferred": False,
|
|
},
|
|
"schema_version": 1,
|
|
"source": {
|
|
"commit": source_commit,
|
|
"dirty": False,
|
|
"repository": "private-gitea-test",
|
|
},
|
|
},
|
|
indent=2,
|
|
sort_keys=True,
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
return source_commit
|
|
|
|
|
|
def write_runtime_profile(
|
|
path: Path,
|
|
artifact: Path,
|
|
source_commit: str,
|
|
*,
|
|
decision: str = "READY_FOR_HARDENED_RUNTIME_DEPLOYMENT",
|
|
firmware: str = "9.60",
|
|
effect: str = "PAYLOAD_PROCESS_LOCAL",
|
|
) -> None:
|
|
path.write_text(
|
|
json.dumps(
|
|
{
|
|
"artifact": {
|
|
"built": True,
|
|
"filename": artifact.name,
|
|
"id": "execution-policy-test",
|
|
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
|
"size": artifact.stat().st_size,
|
|
"source_commit": source_commit,
|
|
},
|
|
"budgets": {
|
|
"automatic_retry": False,
|
|
"filesystem_write_budget": "controlled_artifact_directory_only",
|
|
"maximum_runtime_ms": 2000,
|
|
"payload_network_access": "none",
|
|
"persistent_write_budget": "controlled_artifact_removable",
|
|
},
|
|
"decision": decision,
|
|
"deployment": {
|
|
"installed": False,
|
|
"ready_for_installation": True,
|
|
"rollback_prepared": True,
|
|
},
|
|
"effects": [{"classification": effect, "id": "test_effect"}],
|
|
"execution_authorized": False,
|
|
"expected_volatile_effects": (
|
|
["test_effect"]
|
|
if effect == "EXPECTED_VOLATILE_RUNTIME_EFFECT"
|
|
else []
|
|
),
|
|
"execution": {
|
|
"authorized": False,
|
|
"executed": False,
|
|
"execution_eligible": True,
|
|
"transferred": False,
|
|
},
|
|
"firmware": {
|
|
"device_attested": False,
|
|
"evidence": "jens_explicitly_confirmed_exact_9.60",
|
|
"exact": firmware,
|
|
},
|
|
"hard_blockers": [],
|
|
"payload_manager": {
|
|
"base_commit": "cfbc70f30f419b09bf2b52283f7409e2d3117ee1",
|
|
"hardened_commit": "e23d94ff91233aa770e2342800c1467875bdef44",
|
|
"installed": False,
|
|
"release": "v0.3.1-chimera-controlled-phase07",
|
|
"reproducible": True,
|
|
"sha256": (
|
|
"8fecf8241a46246eddbd21e8bb4d875f5d76f1f4f4c6a11384df1f131aa5e5b1"
|
|
),
|
|
"size": 99560,
|
|
},
|
|
"loader": {
|
|
"base_commit": "699e8bcff03e91e8d6ca6eba281af25c5a58d8c2",
|
|
"hardened_commit": "197623058f509eddde18868dafcb92fdcac66464",
|
|
"installed": False,
|
|
"release": "v0.23-chimera-phase07",
|
|
"reproducible": True,
|
|
"sha256": (
|
|
"63e810982471eb40cae3a20aa9df9a0a02892f420e429874fae4e99aa400b561"
|
|
),
|
|
"size": 397000,
|
|
},
|
|
"profile": "controlled-ps5-runtime",
|
|
"schema_version": 1,
|
|
"sdk": {
|
|
"commit": "d2e2e585740362976a39fdd5ccf390f199a7bc37",
|
|
"release": "v0.41",
|
|
},
|
|
},
|
|
indent=2,
|
|
sort_keys=True,
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
root = args.root.resolve()
|
|
denylist_path = root / "manifests/artifact-denylist.json"
|
|
denylist = json.loads(denylist_path.read_text(encoding="utf-8"))
|
|
if [entry["sha256"] for entry in denylist["entries"]] != [BLOCKED_SHA256]:
|
|
raise RuntimeError("permanent denylist hash changed or is absent")
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
temporary = Path(directory)
|
|
artifact = temporary / "test.elf"
|
|
artifact.write_bytes(b"chimera-execution-policy-test\n")
|
|
manifest = temporary / "manifest.json"
|
|
runtime_profile = temporary / "runtime-profile.json"
|
|
command = [
|
|
sys.executable,
|
|
str(root / "tools/check_artifact_execution_policy.py"),
|
|
"--manifest",
|
|
str(manifest),
|
|
"--denylist",
|
|
str(denylist_path),
|
|
"--artifact",
|
|
str(artifact),
|
|
"--runtime-profile",
|
|
str(runtime_profile),
|
|
"--firmware",
|
|
"9.60",
|
|
]
|
|
|
|
source_commit = write_manifest(manifest, artifact, eligible=False)
|
|
write_runtime_profile(
|
|
runtime_profile,
|
|
artifact,
|
|
source_commit,
|
|
decision="BLOCKED_VERSION_OR_UNBOUNDED_EFFECT",
|
|
)
|
|
denied = run(command, "DENY")
|
|
if "MANIFEST_EXECUTION_INELIGIBLE" not in denied["reason_codes"]:
|
|
raise RuntimeError("execution-ineligible manifest was not refused")
|
|
|
|
source_commit = write_manifest(manifest, artifact, eligible=True)
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
run(command, "PASS_STATIC_DEPLOYMENT_ELIGIBILITY_GATE")
|
|
|
|
no_artifact = without_option(command, "--artifact")
|
|
denied = run(no_artifact, "DENY")
|
|
if "ARTIFACT_BYTES_NOT_SUPPLIED" not in denied["reason_codes"]:
|
|
raise RuntimeError("missing artifact bytes did not fail closed")
|
|
|
|
no_profile = without_option(command, "--runtime-profile")
|
|
denied = run(no_profile, "DENY")
|
|
if "CONTROLLED_RUNTIME_PROFILE_REQUIRED" not in denied["reason_codes"]:
|
|
raise RuntimeError("missing controlled runtime profile did not fail closed")
|
|
|
|
write_runtime_profile(
|
|
runtime_profile,
|
|
artifact,
|
|
source_commit,
|
|
effect="UNBOUNDED_OR_UNKNOWN",
|
|
)
|
|
denied = run(command, "DENY")
|
|
if "RUNTIME_PROFILE_HARD_EFFECT" not in denied["reason_codes"]:
|
|
raise RuntimeError("unbounded runtime effect did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
|
document["expected_volatile_effects"] = ["not_the_classified_effect"]
|
|
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if (
|
|
"RUNTIME_PROFILE_VOLATILE_EFFECTS_MISMATCH"
|
|
not in denied["reason_codes"]
|
|
):
|
|
raise RuntimeError("volatile-effect mismatch did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
|
del document["expected_volatile_effects"]
|
|
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
|
raise RuntimeError("missing volatile-effect declaration did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
|
document["execution_authorized"] = True
|
|
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if (
|
|
"RUNTIME_PROFILE_EXECUTION_STATE_INVALID"
|
|
not in denied["reason_codes"]
|
|
):
|
|
raise RuntimeError("runtime authorization widening did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
|
document["payload_manager"]["hardened_commit"] = "0" * 40
|
|
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if "PAYLOAD_MANAGER_IDENTITY_MISMATCH" not in denied["reason_codes"]:
|
|
raise RuntimeError("Payload Manager identity mismatch did not fail closed")
|
|
|
|
write_runtime_profile(
|
|
runtime_profile,
|
|
artifact,
|
|
source_commit,
|
|
effect="NOT_A_CLASSIFICATION",
|
|
)
|
|
denied = run(command, "DENY")
|
|
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
|
raise RuntimeError("unknown effect classification did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
|
document["profile"] = "controlled-ps5-lifecycle-v1"
|
|
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
|
raise RuntimeError("wrong runtime profile name did not fail closed")
|
|
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
document = json.loads(manifest.read_text(encoding="utf-8"))
|
|
document["execution"]["transferred"] = True
|
|
manifest.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(command, "DENY")
|
|
if "MANIFEST_EXECUTION_STATE_INVALID" not in denied["reason_codes"]:
|
|
raise RuntimeError("manifest transfer claim did not fail closed")
|
|
|
|
source_commit = write_manifest(manifest, artifact, eligible=True)
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
mismatched_firmware = command.copy()
|
|
mismatched_firmware[-1] = "9.40"
|
|
denied = run(mismatched_firmware, "DENY")
|
|
if "FIRMWARE_MISMATCH" not in denied["reason_codes"]:
|
|
raise RuntimeError("firmware mismatch did not fail closed")
|
|
|
|
document = json.loads(manifest.read_text(encoding="utf-8"))
|
|
document["artifact"]["sha256"] = BLOCKED_SHA256
|
|
manifest.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(no_artifact, "DENY")
|
|
if "ARTIFACT_PERMANENTLY_DENYLISTED" not in denied["reason_codes"]:
|
|
raise RuntimeError("denylisted hash was not refused")
|
|
|
|
del document["execution"]["execution_eligible"]
|
|
manifest.write_text(json.dumps(document), encoding="utf-8")
|
|
denied = run(no_artifact, "DENY")
|
|
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
|
raise RuntimeError("missing eligibility did not fail closed")
|
|
|
|
artifact.write_bytes(b"changed\n")
|
|
source_commit = write_manifest(manifest, artifact, eligible=True)
|
|
write_runtime_profile(runtime_profile, artifact, source_commit)
|
|
artifact.write_bytes(b"changed-again\n")
|
|
denied = run(command, "DENY")
|
|
if "ARTIFACT_DIGEST_MISMATCH" not in denied["reason_codes"]:
|
|
raise RuntimeError("changed bytes were not refused")
|
|
|
|
print("artifact execution policy gate passed all refusal tests")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|