mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-11 18:13:38 +00:00
195 lines
7.5 KiB
Python
Executable file
195 lines
7.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Tests for live runtime release proof collection and verification."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import threading
|
|
import unittest
|
|
from datetime import datetime, timezone
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from unittest import mock
|
|
|
|
from live_runtime_proof import collect_receipt, evaluate_live_runtime, seal_receipt, verify_receipt
|
|
|
|
|
|
class _ProofHandler(BaseHTTPRequestHandler):
|
|
authorization = "Bearer proof-token"
|
|
|
|
def do_GET(self) -> None: # noqa: N802 - stdlib handler API
|
|
if self.headers.get("Authorization") != self.authorization:
|
|
self.send_response(401)
|
|
self.end_headers()
|
|
return
|
|
if self.path == "/api/version":
|
|
self._json(
|
|
{
|
|
"version": "6.2.0-rc.8",
|
|
"build": "proof-build",
|
|
"channel": "rc",
|
|
"isSourceBuild": False,
|
|
"isDevelopment": False,
|
|
}
|
|
)
|
|
return
|
|
if self.path == "/api/recovery/postures?page=1&limit=200":
|
|
self._json(
|
|
{
|
|
"data": [
|
|
{
|
|
"subjectResourceId": "vm-100",
|
|
"state": "protected",
|
|
"lastSuccessfulPointAt": "2026-08-04T10:00:00Z",
|
|
},
|
|
{
|
|
"subjectResourceId": "vm-101",
|
|
"state": "attention",
|
|
"lastSuccessfulPointAt": "2026-08-03T10:00:00Z",
|
|
},
|
|
],
|
|
"meta": {"page": 1, "limit": 200, "total": 2, "totalPages": 1},
|
|
}
|
|
)
|
|
return
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def _json(self, payload: object) -> None:
|
|
encoded = json.dumps(payload).encode("utf-8")
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(encoded)))
|
|
self.end_headers()
|
|
self.wfile.write(encoded)
|
|
|
|
def log_message(self, format: str, *args: object) -> None:
|
|
return
|
|
|
|
|
|
class LiveRuntimeProofTest(unittest.TestCase):
|
|
def test_evaluation_rejects_unknown_with_successful_point(self) -> None:
|
|
observed, failures = evaluate_live_runtime(
|
|
expected_version="6.2.0-rc.8",
|
|
observed_version="6.2.0-rc.8",
|
|
postures=[
|
|
{
|
|
"subjectResourceId": "vm-100",
|
|
"state": "unknown",
|
|
"lastSuccessfulPointAt": "2026-08-04T10:00:00Z",
|
|
}
|
|
],
|
|
minimum_postures=1,
|
|
minimum_successful_postures=1,
|
|
)
|
|
self.assertEqual(observed["unknownWithSuccessfulPointCount"], 1)
|
|
self.assertTrue(any("remain unknown" in failure for failure in failures))
|
|
|
|
def test_evaluation_rejects_version_mismatch_and_empty_dataset(self) -> None:
|
|
_, failures = evaluate_live_runtime(
|
|
expected_version="6.2.0-rc.8",
|
|
observed_version="6.2.0-rc.7",
|
|
postures=[],
|
|
minimum_postures=1,
|
|
minimum_successful_postures=1,
|
|
)
|
|
self.assertEqual(len(failures), 3)
|
|
self.assertTrue(any("does not match" in failure for failure in failures))
|
|
|
|
def test_collects_passing_receipt_from_running_target(self) -> None:
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), _ProofHandler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
self.addCleanup(server.server_close)
|
|
self.addCleanup(server.shutdown)
|
|
|
|
with mock.patch.dict(
|
|
"os.environ", {"PULSE_PROOF_TEST_AUTH": _ProofHandler.authorization}
|
|
):
|
|
receipt = collect_receipt(
|
|
argparse.Namespace(
|
|
base_url=f"http://127.0.0.1:{server.server_port}",
|
|
expected_version="6.2.0-rc.8",
|
|
authorization_env="PULSE_PROOF_TEST_AUTH",
|
|
cookie_env="",
|
|
insecure=False,
|
|
timeout_seconds=2.0,
|
|
minimum_postures=2,
|
|
minimum_successful_postures=2,
|
|
)
|
|
)
|
|
|
|
self.assertEqual(receipt["result"], "passed")
|
|
self.assertEqual(receipt["observed"]["stateCounts"], {"attention": 1, "protected": 1})
|
|
self.assertEqual(receipt["observed"]["unknownWithSuccessfulPointCount"], 0)
|
|
self.assertEqual(len(receipt["observed"]["postureResponseSha256"]), 1)
|
|
|
|
def test_verifier_rejects_tampering(self) -> None:
|
|
receipt = self._passing_receipt()
|
|
receipt["observed"]["unknownWithSuccessfulPointCount"] = 3
|
|
errors = verify_receipt(
|
|
receipt,
|
|
expected_version="6.2.0-rc.8",
|
|
expected_origin="https://pulse.example.test",
|
|
max_age_seconds=3600,
|
|
now=datetime(2026, 8, 4, 12, 30, tzinfo=timezone.utc),
|
|
)
|
|
self.assertIn("receiptSha256 does not match receipt content", errors)
|
|
self.assertIn("receipt reports unknown postures with successful restore points", errors)
|
|
|
|
def test_verifier_rejects_wrong_target_and_stale_receipt(self) -> None:
|
|
receipt = self._passing_receipt()
|
|
errors = verify_receipt(
|
|
receipt,
|
|
expected_version="6.2.0-rc.8",
|
|
expected_origin="https://other.example.test",
|
|
max_age_seconds=60,
|
|
now=datetime(2026, 8, 4, 12, 30, tzinfo=timezone.utc),
|
|
)
|
|
self.assertIn("receipt target origin does not match verifier expectation", errors)
|
|
self.assertTrue(any("exceeds maximum" in error for error in errors))
|
|
|
|
def test_verifier_accepts_fresh_sealed_receipt(self) -> None:
|
|
errors = verify_receipt(
|
|
self._passing_receipt(),
|
|
expected_version="v6.2.0-rc.8",
|
|
expected_origin="https://pulse.example.test/",
|
|
max_age_seconds=3600,
|
|
now=datetime(2026, 8, 4, 12, 30, tzinfo=timezone.utc),
|
|
)
|
|
self.assertEqual(errors, [])
|
|
|
|
@staticmethod
|
|
def _passing_receipt() -> dict[str, object]:
|
|
return seal_receipt(
|
|
{
|
|
"schemaVersion": 1,
|
|
"proofType": "pulse-live-runtime",
|
|
"assertion": "successful-restore-points-have-evaluated-posture",
|
|
"result": "passed",
|
|
"collectedAt": "2026-08-04T12:00:00Z",
|
|
"target": {"origin": "https://pulse.example.test", "tlsVerified": True},
|
|
"expected": {
|
|
"version": "6.2.0-rc.8",
|
|
"minimumPostures": 1,
|
|
"minimumSuccessfulPostures": 1,
|
|
},
|
|
"observed": {
|
|
"version": "6.2.0-rc.8",
|
|
"postureTotal": 2,
|
|
"stateCounts": {"attention": 1, "protected": 1},
|
|
"successfulPostureCount": 2,
|
|
"unknownWithSuccessfulPointCount": 0,
|
|
"unknownWithSuccessfulPointResourceIds": [],
|
|
"isSourceBuild": False,
|
|
"isDevelopment": False,
|
|
"versionResponseSha256": "a" * 64,
|
|
"postureResponseSha256": ["b" * 64],
|
|
},
|
|
"failures": [],
|
|
}
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|