G7 증명과 G8 clean-head 승격 준비

This commit is contained in:
Yun Chan 2026-08-09 18:22:03 +09:00
parent 94c681d450
commit 5221f79e3f
52 changed files with 6876 additions and 506 deletions

View file

@ -3,6 +3,7 @@
from __future__ import annotations
import importlib.util
import hashlib
import json
import sys
import unittest
@ -11,6 +12,11 @@ from typing import Sequence
RUNNER_PATH = Path(__file__).with_name("capture-g7-topology-evidence.py")
REPO_ROOT = RUNNER_PATH.resolve().parents[1]
def file_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def load_runner():
@ -27,6 +33,16 @@ API_ID = "a" * 64
CADDY_ID = "c" * 64
API_DIGEST = "sha256:" + "1" * 64
CADDY_DIGEST = "sha256:" + "2" * 64
API_EXE_DIGEST = "3" * 64
CLOUDFLARED_EXE_DIGEST = "4" * 64
GIT_SHA = "5" * 40
GIT_TREE_SHA = "8" * 40
PSUTIL_VERSION = "6.1.1"
SCRIPT_DIGESTS = {
"runner": file_sha256(REPO_ROOT / "scripts/run-g7-external-proof-window.py"),
"collector": file_sha256(REPO_ROOT / "scripts/capture-g7-topology-evidence.py"),
"checker": file_sha256(REPO_ROOT / "scripts/check-g7-external-proof.py"),
}
def inspect_item(
@ -114,9 +130,17 @@ class FixtureRunner:
self,
*,
inspect_outputs: list[str] | None = None,
git_outputs: list[str] | None = None,
git_branch: str = "HEAD\n",
git_status: str = "",
git_tree_sha: str = GIT_TREE_SHA,
unavailable: set[str] | None = None,
) -> None:
self.inspect_outputs = list(inspect_outputs or [stable_inspect()])
self.git_outputs = list(git_outputs or [GIT_SHA + "\n"])
self.git_branch = git_branch
self.git_status = git_status
self.git_tree_sha = git_tree_sha
self.unavailable = unavailable or set()
self.calls: list[tuple[str, ...]] = []
@ -134,6 +158,21 @@ class FixtureRunner:
return stats_output()
if command == ("ss", "-tinm"):
return SS_OUTPUT
if command and command[0] == "git":
operation = command[3:]
if operation == ("rev-parse", "--abbrev-ref", "HEAD"):
return self.git_branch
if operation == (
"status",
"--porcelain=v1",
"--untracked-files=no",
):
return self.git_status
if operation == ("rev-parse", "--verify", "HEAD^{tree}"):
return self.git_tree_sha + "\n"
if len(self.git_outputs) > 1:
return self.git_outputs.pop(0)
return self.git_outputs[0]
raise AssertionError(f"unexpected command: {command!r}")
@ -170,6 +209,106 @@ class FixtureSource:
return list(self.directories[path])
class FixtureWindowsSource:
def __init__(
self,
module,
*,
identity_overrides: dict[str, list[dict[str, object]]] | None = None,
tcp_overrides: dict[str, object] | None = None,
psutil_version: str = PSUTIL_VERSION,
) -> None:
self.module = module
self.identity_overrides = identity_overrides or {}
self.tcp_overrides = tcp_overrides or {}
self.psutil_version = psutil_version
self.metric_calls = {"api": 0, "cloudflared": 0}
def dependency_version(self, name: str) -> str:
if name != "psutil":
raise AssertionError(f"unexpected dependency: {name}")
return self.psutil_version
def _base_identity(self, role: str, git_sha: str) -> dict[str, object]:
if role == "api":
return {
"role": role,
"pid": 301,
"started_at": "2026-08-07T00:00:00Z",
"executable_name": "python.exe",
"executable_sha256": API_EXE_DIGEST,
"command_line_sha256": "6" * 64,
"git_sha": git_sha,
"cwd": r"D:\workspace\vignette\apps\api",
}
return {
"role": role,
"pid": 302,
"started_at": "2026-08-07T00:00:00Z",
"executable_name": "cloudflared.exe",
"executable_sha256": CLOUDFLARED_EXE_DIGEST,
"command_line_sha256": "7" * 64,
"git_sha": git_sha,
"cwd": r"D:\workspace\vignette",
}
def read_identity(self, *, pid: int, role: str, git_sha: str):
values = self._base_identity(role, git_sha)
overrides = self.identity_overrides.get(role, [])
if overrides:
values.update(overrides[0])
if len(overrides) > 1:
overrides.pop(0)
self.assert_pid(role, pid)
return self.module.WindowsProcessIdentity(**values)
def assert_pid(self, role: str, pid: int) -> None:
expected = 301 if role == "api" else 302
if pid != expected:
raise AssertionError(f"unexpected {role} pid: {pid}")
def read_metrics(self, *, pid: int, role: str) -> dict[str, int | float]:
self.assert_pid(role, pid)
self.metric_calls[role] += 1
factor = 2 if role == "api" else 1
return {
"rss_bytes": 100_000 * factor,
"peak_rss_bytes": 120_000 * factor,
"cpu_time_seconds": float(self.metric_calls[role] * factor),
"cpu_percent": 1.5 * factor,
"handles": 20 * factor,
"threads": 4 * factor,
}
def read_tcp_metrics(
self,
*,
api_pid: int,
cloudflared_pid: int,
api_listen_port: int,
) -> dict[str, object]:
self.assert_pid("api", api_pid)
self.assert_pid("cloudflared", cloudflared_pid)
payload: dict[str, object] = {
"processes": {
"api": {"connections": 2, "established": 1, "listeners": 1},
"cloudflared": {
"connections": 4,
"established": 4,
"listeners": 0,
},
},
"host_tcp": {"connections": 20},
"api_listener": {
"port": api_listen_port,
"owned_listener_count": 1,
"conflicting_listener_count": 0,
},
}
payload.update(self.tcp_overrides)
return payload
class G7TopologyEvidenceTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
@ -191,6 +330,31 @@ class G7TopologyEvidenceTests(unittest.TestCase):
values.update(overrides)
return self.runner_module.CaptureConfig(**values)
def windows_config(self, **overrides):
values = {
"public_host": "api.example.test",
"repo_root": r"D:\workspace\vignette",
"git_sha": GIT_SHA,
"git_tree_sha": GIT_TREE_SHA,
"runner_script_sha256": SCRIPT_DIGESTS["runner"],
"collector_script_sha256": SCRIPT_DIGESTS["collector"],
"checker_script_sha256": SCRIPT_DIGESTS["checker"],
"psutil_version": PSUTIL_VERSION,
"api_pid": 301,
"api_executable_name": "python.exe",
"api_executable_sha256": API_EXE_DIGEST,
"api_cwd": r"D:\workspace\vignette\apps\api",
"api_listen_port": 8001,
"cloudflared_pid": 302,
"cloudflared_executable_name": "cloudflared.exe",
"cloudflared_executable_sha256": CLOUDFLARED_EXE_DIGEST,
"cloudflared_cwd": r"D:\workspace\vignette",
"samples": 2,
"interval_seconds": 1.0,
}
values.update(overrides)
return self.runner_module.WindowsHostConfig(**values)
def test_parsers_emit_numeric_metadata_without_endpoints(self) -> None:
source = FixtureSource()
targets = self.runner_module.pin_targets(self.config(), FixtureRunner(), source)
@ -231,6 +395,7 @@ class G7TopologyEvidenceTests(unittest.TestCase):
)
self.assertEqual("passed", evidence["status"])
self.assertEqual("linux_compose", evidence["topology_mode"])
self.assertEqual(1, evidence["samples_completed"])
self.assertEqual(API_ID, evidence["targets"]["api"]["container_id"])
self.assertEqual(API_DIGEST, evidence["targets"]["api"]["image_digest"])
@ -328,6 +493,201 @@ class G7TopologyEvidenceTests(unittest.TestCase):
self.runner_module.validate_config(config)
self.assertEqual(failure, str(caught.exception))
def test_windows_capture_pins_processes_listener_and_high_water(self) -> None:
source = FixtureWindowsSource(self.runner_module)
evidence = self.runner_module.capture_windows_evidence(
self.windows_config(),
FixtureRunner(),
source,
sleep=lambda _seconds: None,
)
self.assertEqual("passed", evidence["status"])
self.assertEqual("windows_host", evidence["topology_mode"])
self.assertEqual({"api", "cloudflared"}, set(evidence["targets"]))
self.assertEqual(
{
"detached_head": True,
"tracked_clean": True,
"git_sha": GIT_SHA,
"git_tree_sha": GIT_TREE_SHA,
"script_sha256": SCRIPT_DIGESTS,
"runtime_dependencies": {"psutil": PSUTIL_VERSION},
},
evidence["source_provenance"],
)
self.assertEqual(GIT_SHA, evidence["targets"]["api"]["git_sha"])
self.assertEqual(
API_EXE_DIGEST,
evidence["targets"]["api"]["executable_sha256"],
)
self.assertEqual(
r"D:\workspace\vignette\apps\api",
evidence["targets"]["api"]["cwd"],
)
summary = evidence["summary"]
self.assertEqual(4.0, summary["processes"]["api"]["cpu_time_seconds_max"])
self.assertEqual(1, summary["api_listener"]["owned_listener_count_min"])
self.assertEqual(4, summary["processes"]["cloudflared"]["tcp_established_max"])
serialized = json.dumps(evidence)
self.assertNotIn("127.0.0.1", serialized)
def test_windows_identity_and_git_drift_fail_closed(self) -> None:
cases = (
(
FixtureRunner(),
FixtureWindowsSource(
self.runner_module,
identity_overrides={
"api": [{"started_at": "2999-01-01T00:00:00Z"}]
},
),
"process_started_at_future:api",
),
(
FixtureRunner(),
FixtureWindowsSource(
self.runner_module,
identity_overrides={
"api": [
{},
{"command_line_sha256": "8" * 64},
]
},
),
"process_command_line_drift:api",
),
(
FixtureRunner(git_outputs=[GIT_SHA + "\n", "9" * 40 + "\n"]),
FixtureWindowsSource(self.runner_module),
"git_sha_drift",
),
)
for runner, source, failure in cases:
with self.subTest(failure=failure):
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
self.runner_module.capture_windows_evidence(
self.windows_config(samples=1),
runner,
source,
sleep=lambda _seconds: None,
)
self.assertEqual(failure, str(caught.exception))
def test_windows_source_and_toolchain_provenance_fail_closed(self) -> None:
cases = (
(
FixtureRunner(git_branch="master\n"),
FixtureWindowsSource(self.runner_module),
self.windows_config(samples=1),
"git_head_not_detached",
),
(
FixtureRunner(git_status=" M scripts/check-g7-external-proof.py\n"),
FixtureWindowsSource(self.runner_module),
self.windows_config(samples=1),
"git_tracked_worktree_dirty",
),
(
FixtureRunner(git_tree_sha="9" * 40),
FixtureWindowsSource(self.runner_module),
self.windows_config(samples=1),
"git_tree_sha_drift",
),
(
FixtureRunner(),
FixtureWindowsSource(self.runner_module),
self.windows_config(samples=1, checker_script_sha256="9" * 64),
"evidence_script_sha256_drift:checker",
),
(
FixtureRunner(),
FixtureWindowsSource(self.runner_module, psutil_version="6.1.0"),
self.windows_config(samples=1),
"psutil_version_drift",
),
)
for runner, source, config, failure in cases:
with self.subTest(failure=failure):
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
self.runner_module.capture_windows_evidence(
config,
runner,
source,
sleep=lambda _seconds: None,
)
self.assertEqual(failure, str(caught.exception))
def test_windows_listener_and_tunnel_connection_are_required(self) -> None:
cases = (
(
{
"api_listener": {
"port": 8001,
"owned_listener_count": 0,
"conflicting_listener_count": 0,
}
},
"api_listener_not_owned",
),
(
{
"processes": {
"api": {
"connections": 2,
"established": 1,
"listeners": 1,
},
"cloudflared": {
"connections": 0,
"established": 0,
"listeners": 0,
},
}
},
"cloudflared_tunnel_connection_missing",
),
)
for overrides, failure in cases:
with self.subTest(failure=failure):
source = FixtureWindowsSource(
self.runner_module,
tcp_overrides=overrides,
)
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
self.runner_module.capture_windows_evidence(
self.windows_config(samples=1),
FixtureRunner(),
source,
sleep=lambda _seconds: None,
)
self.assertEqual(failure, str(caught.exception))
def test_windows_config_rejects_shared_pid_and_outside_cwd(self) -> None:
cases = (
(
self.windows_config(cloudflared_pid=301),
"process_targets_not_distinct",
),
(
self.windows_config(api_cwd=r"C:\elsewhere"),
"api_cwd_outside_repo",
),
(
self.windows_config(git_tree_sha="not-a-tree"),
"git_tree_sha_invalid",
),
(
self.windows_config(psutil_version="6.1.0"),
"psutil_version_invalid",
),
)
for config, failure in cases:
with self.subTest(failure=failure):
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
self.runner_module.validate_windows_config(config)
self.assertEqual(failure, str(caught.exception))
if __name__ == "__main__":
unittest.main()