Add versioned ephemeral custody lifecycle
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
985cef2572
commit
30e6edc236
17 changed files with 2855 additions and 6 deletions
248
tests/test_custody_contract.py
Normal file
248
tests/test_custody_contract.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import custody_contract as module
|
||||
|
||||
|
||||
NOW = datetime(2026, 8, 22, 22, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
def projection_contract() -> dict:
|
||||
return {
|
||||
"interface": module.CONTRACT_INTERFACE,
|
||||
"version": 1,
|
||||
"workplan_id": module.WORKPLAN_ID,
|
||||
"engagement_id": "WH-ENG-20260822-AUDIT-E2-03",
|
||||
"status": "approved",
|
||||
"engagement_contract_sha256": "1" * 64,
|
||||
"target": {
|
||||
"id": "audit-core",
|
||||
"namespace": "audit-core",
|
||||
"deployment": "audit-core",
|
||||
"container": "audit-core",
|
||||
"sender_external_secret": "audit-core-senders",
|
||||
"revision": "2" * 40,
|
||||
"image_digest": "sha256:" + "3" * 64,
|
||||
"contract_sha256": "4" * 64,
|
||||
},
|
||||
"runner": {
|
||||
"namespace": "whitehat",
|
||||
"service_account": "whitehat-runner",
|
||||
"secret_name": "whitehat-e2-audit-credentials",
|
||||
"mount_root": "/var/run/secrets/whitehat",
|
||||
"manifest_sha256": "5" * 64,
|
||||
},
|
||||
"window": {
|
||||
"starts_at": "2026-08-22T22:00:00Z",
|
||||
"projection_cutoff": "2026-08-22T22:03:00Z",
|
||||
"expires_at": "2026-08-22T22:15:00Z",
|
||||
},
|
||||
"authority": {
|
||||
"remote": "railiance01",
|
||||
"registry_path": "platform/workloads/audit-core/senders",
|
||||
"registry_field": "senders.json",
|
||||
"kv_mount": "platform",
|
||||
"kv_prefix": "engagements/WH-ENG-20260822-AUDIT-E2-03/audit-core",
|
||||
"eso_service_account": "external-secrets",
|
||||
"eso_namespace": "external-secrets",
|
||||
},
|
||||
"identities": [
|
||||
{
|
||||
"handle": "token-a",
|
||||
"role": "attacker",
|
||||
"sender_name": "whitehat-e2-a-20260822-03",
|
||||
"tenant": "tenant:trial:whitehat-a-20260822-03",
|
||||
"mount_path": "/var/run/secrets/whitehat/token-a",
|
||||
"may_read": True,
|
||||
"may_write": True,
|
||||
},
|
||||
{
|
||||
"handle": "token-b",
|
||||
"role": "owner",
|
||||
"sender_name": "whitehat-e2-b-20260822-03",
|
||||
"tenant": "tenant:trial:whitehat-b-20260822-03",
|
||||
"mount_path": "/var/run/secrets/whitehat/token-b",
|
||||
"may_read": True,
|
||||
"may_write": True,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def broker_receipt(contract: dict) -> dict:
|
||||
return {
|
||||
"interface": module.BROKER_INTERFACE,
|
||||
"version": 1,
|
||||
"workplan_id": module.WORKPLAN_ID,
|
||||
"owner": module.BROKER_OWNER,
|
||||
"reviewer": "whitehat-owner",
|
||||
"decision": "approve",
|
||||
"created_at": "2026-08-22T21:50:00Z",
|
||||
"engagement_id": contract["engagement_id"],
|
||||
"target_id": contract["target"]["id"],
|
||||
"projection_contract_digest": module.contract_digest(contract),
|
||||
"projection_receipt_interface": module.PROJECTION_INTERFACE,
|
||||
"interface_artifacts": module.interface_artifacts(),
|
||||
"required_roles": ["attacker", "owner"],
|
||||
"mount_paths": sorted(item["mount_path"] for item in contract["identities"]),
|
||||
"adapter": {
|
||||
"repo": "whitehat-security",
|
||||
"revision": "6" * 40,
|
||||
"path": "src/whitehat_security/platform_custody.py",
|
||||
"sha256": "7" * 64,
|
||||
"tests_passed": True,
|
||||
},
|
||||
"cleanup_request_supported": True,
|
||||
"secret_values_observed": False,
|
||||
}
|
||||
|
||||
|
||||
def projection_receipt(contract: dict) -> dict:
|
||||
base = {
|
||||
"interface": module.PROJECTION_INTERFACE,
|
||||
"version": 1,
|
||||
"workplan_id": module.WORKPLAN_ID,
|
||||
"state": "projected",
|
||||
"lease_id": "custody:" + "8" * 32,
|
||||
"engagement_id": contract["engagement_id"],
|
||||
"target": {
|
||||
"id": contract["target"]["id"],
|
||||
"revision": contract["target"]["revision"],
|
||||
"image_digest": contract["target"]["image_digest"],
|
||||
},
|
||||
"projection_contract_digest": module.contract_digest(contract),
|
||||
"broker_receipt_digest": module.digest(broker_receipt(contract)),
|
||||
"projected_at": "2026-08-22T22:02:00Z",
|
||||
"expires_at": contract["window"]["expires_at"],
|
||||
"identities": sorted(
|
||||
(
|
||||
{
|
||||
"handle": item["handle"],
|
||||
"role": item["role"],
|
||||
"sender_name": item["sender_name"],
|
||||
"mount_path": item["mount_path"],
|
||||
}
|
||||
for item in contract["identities"]
|
||||
),
|
||||
key=lambda item: item["handle"],
|
||||
),
|
||||
"resources": {
|
||||
"names": module.resource_names(contract),
|
||||
"uids": {"store": "uid-store", "external_secret": "uid-es", "mounted_secret": "uid-secret"},
|
||||
},
|
||||
"cleanup_authority": "railiance-platform",
|
||||
"secret_values_observed": False,
|
||||
}
|
||||
return {**base, "receipt_id": "sha256:" + module.digest(base)}
|
||||
|
||||
|
||||
class CustodyContractTests(unittest.TestCase):
|
||||
def test_contract_validates_and_derives_hash_only_resource_names(self) -> None:
|
||||
contract = projection_contract()
|
||||
self.assertIs(contract, module.validate_projection_contract(contract))
|
||||
names = module.resource_names(contract)
|
||||
self.assertTrue(all(name.startswith("custody-") for key, name in names.items() if key != "external_secret"))
|
||||
self.assertNotIn("E2-03", json.dumps(names))
|
||||
self.assertEqual(64, len(module.contract_digest(contract)))
|
||||
|
||||
def test_contract_rejects_stale_prefix_duplicate_tenant_and_long_window(self) -> None:
|
||||
contract = projection_contract()
|
||||
contract["authority"]["kv_prefix"] = "engagements/WH-ENG-20260822-AUDIT-E2-02/audit-core"
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_projection_contract(contract)
|
||||
contract = projection_contract()
|
||||
contract["identities"][1]["tenant"] = contract["identities"][0]["tenant"]
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_projection_contract(contract)
|
||||
contract = projection_contract()
|
||||
contract["window"]["expires_at"] = "2026-08-22T22:16:00Z"
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_projection_contract(contract)
|
||||
|
||||
def test_broker_receipt_is_bound_to_contract_and_current_artifacts(self) -> None:
|
||||
contract = projection_contract()
|
||||
receipt = broker_receipt(contract)
|
||||
self.assertIs(receipt, module.validate_broker_receipt(receipt, contract, now=NOW))
|
||||
stale = copy.deepcopy(receipt)
|
||||
stale["projection_contract_digest"] = "0" * 64
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_broker_receipt(stale, contract, now=NOW)
|
||||
stale = copy.deepcopy(receipt)
|
||||
stale["interface_artifacts"][next(iter(stale["interface_artifacts"]))] = "0" * 64
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_broker_receipt(stale, contract, now=NOW)
|
||||
|
||||
def test_broker_message_requires_whitehat_origin_and_exact_subject(self) -> None:
|
||||
contract = projection_contract()
|
||||
receipt = broker_receipt(contract)
|
||||
message = {
|
||||
"from_agent": module.BROKER_OWNER,
|
||||
"to_agent": "railiance-platform",
|
||||
"subject": module.broker_subject(receipt),
|
||||
"body": module.canonical_json(receipt),
|
||||
}
|
||||
self.assertEqual(receipt, module.parse_broker_message(message, contract, now=NOW))
|
||||
message["from_agent"] = "coding-agent"
|
||||
self.assertIsNone(module.parse_broker_message(message, contract, now=NOW))
|
||||
malformed = {
|
||||
"from_agent": module.BROKER_OWNER,
|
||||
"to_agent": "railiance-platform",
|
||||
"subject": module.BROKER_SUBJECT_PREFIX + "/malformed",
|
||||
"body": "{}",
|
||||
}
|
||||
self.assertIsNone(module.parse_broker_message(malformed, contract, now=NOW))
|
||||
|
||||
def test_projection_receipt_canonical_id_and_scope_are_enforced(self) -> None:
|
||||
contract = projection_contract()
|
||||
receipt = projection_receipt(contract)
|
||||
self.assertIs(receipt, module.validate_projection_receipt(receipt, contract))
|
||||
changed = copy.deepcopy(receipt)
|
||||
changed["resources"]["uids"]["mounted_secret"] = "replacement"
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_projection_receipt(changed, contract)
|
||||
value_bearing = copy.deepcopy(receipt)
|
||||
value_bearing["token"] = "never-allowed"
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_projection_receipt(value_bearing, contract)
|
||||
|
||||
def test_cleanup_receipt_is_bound_to_projection_lease(self) -> None:
|
||||
contract = projection_contract()
|
||||
projection = projection_receipt(contract)
|
||||
cleanup = {
|
||||
"interface": module.CLEANUP_INTERFACE,
|
||||
"version": 1,
|
||||
"workplan_id": module.WORKPLAN_ID,
|
||||
"state": "cleaned",
|
||||
"lease_id": projection["lease_id"],
|
||||
"engagement_id": contract["engagement_id"],
|
||||
"projection_receipt_id": projection["receipt_id"],
|
||||
"cleaned_at": "2026-08-22T22:14:00Z",
|
||||
"removed_resources": ["custody-resource"],
|
||||
"target_ready": True,
|
||||
"secret_values_observed": False,
|
||||
}
|
||||
self.assertIs(cleanup, module.validate_cleanup_receipt(cleanup, projection, contract))
|
||||
cleanup["lease_id"] = "custody:" + "0" * 32
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.validate_cleanup_receipt(cleanup, projection, contract)
|
||||
|
||||
def test_all_published_schemas_are_valid_json(self) -> None:
|
||||
for relative in module.INTERFACE_ARTIFACTS:
|
||||
value = json.loads((ROOT / relative).read_text(encoding="utf-8"))
|
||||
self.assertEqual("https://json-schema.org/draft/2020-12/schema", value["$schema"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
186
tests/test_custody_projection.py
Normal file
186
tests/test_custody_projection.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from io import StringIO
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
TESTS = ROOT / "tests"
|
||||
for path in (SCRIPTS, TESTS):
|
||||
if str(path) not in sys.path:
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
from test_custody_contract import broker_receipt, projection_contract, projection_receipt
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location(
|
||||
"custody_projection", SCRIPTS / "custody-projection.py"
|
||||
)
|
||||
assert SPEC and SPEC.loader
|
||||
module = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(module)
|
||||
|
||||
|
||||
NOW = datetime(2026, 8, 22, 22, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
class CustodyProjectionTests(unittest.TestCase):
|
||||
def test_policy_and_manifest_are_derived_from_one_contract(self) -> None:
|
||||
contract = projection_contract()
|
||||
policy = module.build_policy(contract)
|
||||
manifest = json.loads(module.build_manifest(contract, "custody:" + "9" * 32))
|
||||
rendered = json.dumps(manifest, sort_keys=True)
|
||||
self.assertEqual(4, policy.count('capabilities = ["read"]'))
|
||||
self.assertIn(contract["engagement_id"], policy)
|
||||
self.assertIn(contract["engagement_id"], rendered)
|
||||
self.assertNotIn("WH-ENG-20260822-AUDIT-E2-01", policy + rendered)
|
||||
self.assertEqual(
|
||||
["token-a", "token-b"],
|
||||
[item["secretKey"] for item in manifest["items"][1]["spec"]["data"]],
|
||||
)
|
||||
|
||||
def test_identity_overlay_is_exact_and_removable(self) -> None:
|
||||
contract = projection_contract()
|
||||
existing = [{"name": "production", "tokens": ["not-inspected"]}]
|
||||
updated = module.add_temporary_identities(
|
||||
existing, contract, {"token-a": "a", "token-b": "b"}
|
||||
)
|
||||
self.assertEqual(3, len(updated))
|
||||
temporary = updated[1:]
|
||||
self.assertTrue(all(item["expires_at"] == contract["window"]["expires_at"] for item in temporary))
|
||||
restored, removed = module.remove_temporary_identities(updated, contract)
|
||||
self.assertEqual(existing, restored)
|
||||
self.assertEqual(sorted(module.identity_names(contract)), removed)
|
||||
|
||||
def test_broker_gate_runs_before_operator_or_token_generation(self) -> None:
|
||||
contract = projection_contract()
|
||||
operator = object()
|
||||
with mock.patch.object(
|
||||
module,
|
||||
"current_broker_receipt",
|
||||
side_effect=module.ContractError("not connected"),
|
||||
), mock.patch.object(module.secrets, "token_urlsafe") as token_urlsafe:
|
||||
with self.assertRaises(module.ContractError):
|
||||
module.project(operator, contract, now=NOW)
|
||||
token_urlsafe.assert_not_called()
|
||||
|
||||
def test_cli_broker_gate_runs_before_platform_authority_is_opened(self) -> None:
|
||||
contract = projection_contract()
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "contract.json"
|
||||
path.write_text(json.dumps(contract), encoding="utf-8")
|
||||
argv = [
|
||||
"custody-projection.py",
|
||||
"project",
|
||||
"--contract",
|
||||
str(path),
|
||||
"--confirm",
|
||||
f"{contract['engagement_id']}:attended",
|
||||
]
|
||||
with mock.patch.object(sys, "argv", argv), mock.patch.object(
|
||||
module,
|
||||
"current_broker_receipt",
|
||||
side_effect=module.ContractError("broker missing"),
|
||||
), mock.patch.object(module, "Operator") as operator, redirect_stderr(StringIO()):
|
||||
self.assertEqual(1, module.main())
|
||||
operator.assert_not_called()
|
||||
|
||||
def test_validate_and_render_cli_require_no_authority(self) -> None:
|
||||
contract = projection_contract()
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "contract.json"
|
||||
path.write_text(json.dumps(contract), encoding="utf-8")
|
||||
for command in ("validate", "render"):
|
||||
with mock.patch.object(
|
||||
sys, "argv", ["custody-projection.py", command, "--contract", str(path)]
|
||||
), mock.patch.object(module, "Operator") as operator, redirect_stdout(StringIO()):
|
||||
self.assertEqual(0, module.main())
|
||||
operator.assert_not_called()
|
||||
|
||||
def test_transaction_rolls_back_after_every_mutation_boundary(self) -> None:
|
||||
for failed_index in range(4):
|
||||
events: list[str] = []
|
||||
steps = []
|
||||
for index in range(4):
|
||||
def step(index=index) -> None:
|
||||
events.append(f"step-{index}")
|
||||
if index == failed_index:
|
||||
raise module.ProcedureError(f"fail-{index}")
|
||||
steps.append(step)
|
||||
with self.assertRaisesRegex(module.ProcedureError, f"fail-{failed_index}"):
|
||||
module.transactional(steps, lambda: events.append("rollback"))
|
||||
self.assertEqual("rollback", events[-1])
|
||||
self.assertEqual(1, events.count("rollback"))
|
||||
self.assertNotIn(f"step-{failed_index + 1}", events)
|
||||
|
||||
def test_cleanup_failure_is_reported_without_original_output(self) -> None:
|
||||
def fail() -> None:
|
||||
raise module.ProcedureError("projection-stage")
|
||||
|
||||
def cleanup_fail() -> None:
|
||||
raise module.ProcedureError("provider-secret-output")
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
module.ProcedureError, "cleanup could not be proven"
|
||||
) as caught:
|
||||
module.transactional([fail], cleanup_fail)
|
||||
self.assertNotIn("provider-secret-output", str(caught.exception))
|
||||
|
||||
def test_receipt_write_is_mode_0600_and_canonical_receipt_validates(self) -> None:
|
||||
contract = projection_contract()
|
||||
receipt = projection_receipt(contract)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "projection.json"
|
||||
module.write_receipt(path, receipt)
|
||||
self.assertEqual(0o600, stat.S_IMODE(path.stat().st_mode))
|
||||
self.assertEqual(receipt, json.loads(path.read_text(encoding="utf-8")))
|
||||
self.assertIs(receipt, module.validate_projection_receipt(receipt, contract))
|
||||
|
||||
def test_status_cannot_infer_absence_after_connectivity_failure(self) -> None:
|
||||
class Disconnected:
|
||||
def registry(self):
|
||||
raise module.ProcedureError("registry connectivity failed")
|
||||
|
||||
with self.assertRaisesRegex(module.ProcedureError, "connectivity"):
|
||||
module.projection_status(
|
||||
Disconnected(), projection_contract(), projection_receipt(projection_contract())
|
||||
)
|
||||
|
||||
def test_cleanup_refuses_recreated_resource_uid(self) -> None:
|
||||
contract = projection_contract()
|
||||
receipt = projection_receipt(contract)
|
||||
|
||||
class RecreatedResource:
|
||||
def kubectl(self, args, *, label, **kwargs):
|
||||
if "clustersecretstore" in args:
|
||||
value = "replacement-store-uid"
|
||||
else:
|
||||
value = ""
|
||||
return subprocess.CompletedProcess(args, 0, stdout=value, stderr="")
|
||||
|
||||
with self.assertRaisesRegex(module.ProcedureError, "refusing deletion"):
|
||||
module.verify_receipt_resource_scope(RecreatedResource(), contract, receipt)
|
||||
|
||||
def test_expiry_state_is_fail_closed(self) -> None:
|
||||
contract = projection_contract()
|
||||
self.assertEqual("before-window", module.time_state(contract, datetime(2026, 8, 22, 21, 59, tzinfo=UTC)))
|
||||
self.assertEqual("projection-window-open", module.time_state(contract, NOW))
|
||||
self.assertEqual("projection-cutoff-passed", module.time_state(contract, datetime(2026, 8, 22, 22, 4, tzinfo=UTC)))
|
||||
with self.assertRaisesRegex(module.ProcedureError, "before receipt expiry"):
|
||||
module.assert_expired_cleanup(contract, NOW)
|
||||
module.assert_expired_cleanup(contract, datetime(2026, 8, 22, 22, 16, tzinfo=UTC))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
49
tests/test_remote_exec.py
Normal file
49
tests/test_remote_exec.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import remote_exec as module
|
||||
|
||||
|
||||
class RemoteExecTests(unittest.TestCase):
|
||||
def test_shell_hostile_argv_is_one_quoted_remote_command(self) -> None:
|
||||
template = 'go-template={{range $k, $_ := .data}}{{$k}}{{"\\n"}}{{end}}'
|
||||
completed = subprocess.CompletedProcess([], 0, stdout="token-a\ntoken-b\n", stderr="")
|
||||
runner = mock.Mock(return_value=completed)
|
||||
module.run_remote(
|
||||
"railiance01",
|
||||
["kubectl", "get", "secret", "example", "-o", template, "literal;$(false)", "a'b"],
|
||||
label="hostile argv",
|
||||
runner=runner,
|
||||
)
|
||||
command = runner.call_args.args[0]
|
||||
self.assertEqual(["ssh", "-o", "BatchMode=yes", "railiance01"], command[:4])
|
||||
self.assertIn("'go-template={{range $k, $_ := .data}}", command[4])
|
||||
self.assertIn("'literal;$(false)'", command[4])
|
||||
self.assertIn("'a'\"'\"'b'", command[4])
|
||||
|
||||
def test_invalid_host_and_nul_fail_closed(self) -> None:
|
||||
with self.assertRaises(module.RemoteExecutionError):
|
||||
module.run_remote("bad host", ["true"], label="invalid")
|
||||
with self.assertRaises(module.RemoteExecutionError):
|
||||
module.remote_command(["bad\0value"])
|
||||
|
||||
def test_error_does_not_include_remote_output(self) -> None:
|
||||
runner = mock.Mock(
|
||||
return_value=subprocess.CompletedProcess([], 9, stdout="sensitive", stderr="also-sensitive")
|
||||
)
|
||||
with self.assertRaisesRegex(module.RemoteExecutionError, "exit 9") as caught:
|
||||
module.run_remote("railiance01", ["false"], label="safe failure", runner=runner)
|
||||
self.assertNotIn("sensitive", str(caught.exception))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
89
tests/test_wp0025_broker_readiness.py
Normal file
89
tests/test_wp0025_broker_readiness.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
TESTS = ROOT / "tests"
|
||||
for path in (SCRIPTS, TESTS):
|
||||
if str(path) not in sys.path:
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
from test_custody_contract import projection_contract
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location(
|
||||
"wp0025_broker_readiness", SCRIPTS / "wp0025-broker-readiness.py"
|
||||
)
|
||||
assert SPEC and SPEC.loader
|
||||
module = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(module)
|
||||
|
||||
|
||||
class BrokerReadinessTests(unittest.TestCase):
|
||||
def test_show_is_direct_and_authorizes_no_live_mutation(self) -> None:
|
||||
result = module.show(projection_contract())
|
||||
self.assertEqual("whitehat-security", result["owner"])
|
||||
self.assertIn("approve", result["approve_command"])
|
||||
self.assertFalse(result["live_mutation_authorized"])
|
||||
self.assertFalse(result["secret_values_observed"])
|
||||
|
||||
def test_adapter_verification_pins_revision_file_and_focused_test(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
adapter = root / module.DEFAULT_ADAPTER
|
||||
test = root / module.DEFAULT_TEST
|
||||
adapter.parent.mkdir(parents=True)
|
||||
test.parent.mkdir(parents=True)
|
||||
adapter.write_text("class PlatformCustodyBroker: pass\n", encoding="utf-8")
|
||||
test.write_text("def test_adapter(): assert True\n", encoding="utf-8")
|
||||
|
||||
def runner(command, **kwargs):
|
||||
if command[:2] == ["git", "rev-parse"]:
|
||||
return subprocess.CompletedProcess(command, 0, stdout="a" * 40 + "\n", stderr="")
|
||||
return subprocess.CompletedProcess(command, 0, stdout="1 passed\n", stderr="")
|
||||
|
||||
result = module.verify_adapter(root, runner=runner)
|
||||
self.assertTrue(result["passed"])
|
||||
self.assertEqual("a" * 40, result["adapter"]["revision"])
|
||||
self.assertEqual(64, len(result["adapter"]["sha256"]))
|
||||
self.assertTrue(result["adapter"]["tests_passed"])
|
||||
|
||||
def test_missing_adapter_fails_without_running_commands(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
result = module.verify_adapter(
|
||||
Path(directory),
|
||||
runner=lambda *args, **kwargs: self.fail("runner must not execute"),
|
||||
)
|
||||
self.assertFalse(result["passed"])
|
||||
self.assertFalse(result["secret_values_observed"])
|
||||
|
||||
def test_approval_round_trip_and_change_request(self) -> None:
|
||||
contract = projection_contract()
|
||||
verification = {
|
||||
"passed": True,
|
||||
"adapter": {
|
||||
"repo": "whitehat-security",
|
||||
"revision": "a" * 40,
|
||||
"path": str(module.DEFAULT_ADAPTER),
|
||||
"sha256": "b" * 64,
|
||||
"tests_passed": True,
|
||||
},
|
||||
"secret_values_observed": False,
|
||||
}
|
||||
receipt = module.build_approval(contract, "whitehat-owner", verification)
|
||||
self.assertEqual("approve", receipt["decision"])
|
||||
self.assertTrue(receipt["cleanup_request_supported"])
|
||||
change = module.build_change_request(contract, "whitehat-owner", "Bind cleanup acknowledgement.")
|
||||
self.assertEqual("request-changes", change["decision"])
|
||||
with self.assertRaises(module.ReadinessError):
|
||||
module.build_change_request(contract, "whitehat-owner", "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue