80 lines
3.2 KiB
Python
80 lines
3.2 KiB
Python
"""Use the real signed builder, durable release service and approval verifier."""
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from governed_unattended_release import GovernedUnattendedReleaseRunner
|
|
from silent_release_hardening import CapabilityAuthorizationError
|
|
from test_silent_release_execution_readiness import (
|
|
COMMIT, trusted_service, signed_shadow_approval,
|
|
)
|
|
|
|
|
|
def bind_simulated_source(service):
|
|
service.source_admission_resolver = lambda source: {
|
|
"admission_id": "simulated-source-admission", "source_commit": source,
|
|
"tree_oid": "3" * 40, "version": "V2R2C5",
|
|
}
|
|
|
|
|
|
def test_real_release_service_and_signed_owner_broker_are_composable(tmp_path):
|
|
service, signer = trusted_service(tmp_path)
|
|
bind_simulated_source(service)
|
|
runner = GovernedUnattendedReleaseRunner(
|
|
admission=object(), environment_mode="AUTHORITY", release_service=service,
|
|
owner_approval_broker=lambda envelope: signed_shadow_approval(
|
|
service, signer, envelope["candidate_id"]
|
|
).to_dict(),
|
|
)
|
|
result = runner.postpublication_rc(
|
|
event_id="isolated-integration", source_commit=COMMIT,
|
|
source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": "3" * 40},
|
|
)
|
|
assert result["phase"] == "APPROVED", result
|
|
assert service.get_release_saga(result["candidate_id"])["state"] == "APPROVED"
|
|
assert service.execution_enabled is False
|
|
assert runner.deploy(result["candidate_id"])["phase"] == "BLOCKED"
|
|
|
|
|
|
def test_real_verifier_rejects_broker_source_substitution(tmp_path):
|
|
service, signer = trusted_service(tmp_path)
|
|
bind_simulated_source(service)
|
|
|
|
broker_calls = []
|
|
|
|
def broker(envelope):
|
|
broker_calls.append(envelope["candidate_id"])
|
|
approval = signed_shadow_approval(service, signer, envelope["candidate_id"]).to_dict()
|
|
approval["source_commit"] = "f" * 40
|
|
return approval
|
|
|
|
runner = GovernedUnattendedReleaseRunner(
|
|
admission=object(), environment_mode="AUTHORITY", release_service=service,
|
|
owner_approval_broker=broker,
|
|
)
|
|
result = runner.postpublication_rc(
|
|
event_id="isolated-substitution", source_commit=COMMIT,
|
|
source_readback={"authenticated": True, "source_commit": COMMIT, "tree_oid": "3" * 40},
|
|
)
|
|
assert result["phase"] == "BLOCKED"
|
|
assert len(broker_calls) == 1
|
|
assert not list(service.approvals_root.glob("*.json"))
|
|
|
|
|
|
@pytest.mark.parametrize("damage", ["missing_verifier", "tampered_attestation"])
|
|
def test_durable_acceptance_reverification_fails_closed(tmp_path, damage):
|
|
service, _ = trusted_service(tmp_path)
|
|
bind_simulated_source(service)
|
|
created = service.create_release_intent({"event_id": "acceptance-recheck", "source_commit": COMMIT})
|
|
candidate_id = created["candidate_id"]
|
|
assert service.verify_release_acceptance(candidate_id)["status"] == "VERIFIED"
|
|
if damage == "missing_verifier":
|
|
service.builder_attestation_verifier = None
|
|
else:
|
|
path = service.acceptance_root / (candidate_id + ".json")
|
|
record = json.loads(path.read_text())
|
|
record["attestation"]["artifact_sha256"] = "f" * 64
|
|
path.write_text(json.dumps(record))
|
|
with pytest.raises(CapabilityAuthorizationError):
|
|
service.verify_release_acceptance(candidate_id)
|