G7 증명과 G8 clean-head 승격 준비
This commit is contained in:
parent
94c681d450
commit
5221f79e3f
52 changed files with 6876 additions and 506 deletions
408
scripts/test_public_runtime_watchdog_provenance.py
Normal file
408
scripts/test_public_runtime_watchdog_provenance.py
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parent
|
||||
WATCHDOG = SCRIPTS / "watch-public-runtime.ps1"
|
||||
INSTALLER = SCRIPTS / "install-public-runtime-task.ps1"
|
||||
BOOT = SCRIPTS / "boot-public-runtime.ps1"
|
||||
BOOT_REGISTER = SCRIPTS / "register-boot-task.ps1"
|
||||
HIDDEN_TRIGGER = SCRIPTS / "watch-public-runtime-hidden.vbs"
|
||||
START = SCRIPTS / "start-public-runtime.ps1"
|
||||
REPO_ROOT = SCRIPTS.parent
|
||||
RUNBOOK = REPO_ROOT / "docs" / "ops" / "public-runtime-watchdog.md"
|
||||
LOCAL_DEVELOPMENT = REPO_ROOT / "docs" / "guides" / "local-development.md"
|
||||
WATCHDOG_SOURCE = WATCHDOG.read_text(encoding="utf-8")
|
||||
INSTALLER_SOURCE = INSTALLER.read_text(encoding="utf-8")
|
||||
BOOT_SOURCE = BOOT.read_text(encoding="utf-8")
|
||||
BOOT_REGISTER_SOURCE = BOOT_REGISTER.read_text(encoding="utf-8")
|
||||
HIDDEN_TRIGGER_SOURCE = HIDDEN_TRIGGER.read_text(encoding="utf-8")
|
||||
RUNBOOK_SOURCE = RUNBOOK.read_text(encoding="utf-8")
|
||||
LOCAL_DEVELOPMENT_SOURCE = LOCAL_DEVELOPMENT.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
class PublicRuntimeWatchdogProvenanceTest(unittest.TestCase):
|
||||
def test_watchdog_requires_exact_source_and_script_pins(self) -> None:
|
||||
for expected in (
|
||||
"[string]$StableSourceRoot",
|
||||
"[string]$ExpectedSourceCommit",
|
||||
"[string]$ExpectedSourceTree",
|
||||
"[string]$ExpectedWatchdogSha256",
|
||||
"[string]$ExpectedStartScriptSha256",
|
||||
'symbolic-ref --quiet HEAD',
|
||||
'"status", "--porcelain=v1", "--untracked-files=normal"',
|
||||
'"ls-files", "--error-unmatch"',
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, WATCHDOG_SOURCE)
|
||||
self.assertGreaterEqual(WATCHDOG_SOURCE.count("[Parameter(Mandatory = $true)]"), 5)
|
||||
|
||||
def test_provenance_gate_precedes_health_and_runtime_recovery(self) -> None:
|
||||
gate_comment = WATCHDOG_SOURCE.index(
|
||||
"# health probe, failcount 기록, 프로세스 재기동보다 먼저"
|
||||
)
|
||||
gate = WATCHDOG_SOURCE.index("Assert-StableSourceProvenance", gate_comment)
|
||||
health = WATCHDOG_SOURCE.index("$checks = @(")
|
||||
restart = WATCHDOG_SOURCE.index("& $startScript @startArgs")
|
||||
failcount_write = WATCHDOG_SOURCE.index("Set-FailCount 0", health)
|
||||
self.assertLess(gate, health)
|
||||
self.assertLess(gate, restart)
|
||||
self.assertLess(gate, failcount_write)
|
||||
|
||||
def test_installer_pins_a_detached_clean_release_in_task_action(self) -> None:
|
||||
for expected in (
|
||||
"[Parameter(Mandatory = $true)]",
|
||||
"[string]$StableSourceRoot",
|
||||
"symbolic-ref --quiet HEAD",
|
||||
'"status", "--porcelain=v1", "--untracked-files=normal"',
|
||||
'"-ExpectedSourceCommit $sourceCommit"',
|
||||
'"-ExpectedSourceTree $sourceTree"',
|
||||
'"-ExpectedWatchdogSha256 $watchdogSha256"',
|
||||
'"-ExpectedStartScriptSha256 $startScriptSha256"',
|
||||
"-WorkingDirectory $resolvedSourceRoot",
|
||||
"Watchdog installer is not executing from the pinned stable source root",
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, INSTALLER_SOURCE)
|
||||
dirty_gate = INSTALLER_SOURCE.index("Stable source is not clean")
|
||||
task_registration = INSTALLER_SOURCE.index("Register-ScheduledTask")
|
||||
self.assertLess(dirty_gate, task_registration)
|
||||
|
||||
def test_boot_recovery_uses_the_same_gate_before_docker_or_process_mutation(self) -> None:
|
||||
for expected in (
|
||||
"[string]$StableSourceRoot",
|
||||
"[string]$ExpectedSourceCommit",
|
||||
"[string]$ExpectedSourceTree",
|
||||
"[string]$ExpectedBootScriptSha256",
|
||||
"[string]$ExpectedStartScriptSha256",
|
||||
"symbolic-ref --quiet HEAD",
|
||||
'"status", "--porcelain=v1", "--untracked-files=normal"',
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, BOOT_SOURCE)
|
||||
gate_comment = BOOT_SOURCE.index(
|
||||
"# Docker/DB/process mutation보다 먼저 stable source를 매 실행 재검증한다."
|
||||
)
|
||||
gate = BOOT_SOURCE.index("Assert-StableSourceProvenance", gate_comment)
|
||||
docker_mutation = BOOT_SOURCE.index("docker.exe update")
|
||||
runtime_recovery = BOOT_SOURCE.index("-File $startScript")
|
||||
self.assertLess(gate, docker_mutation)
|
||||
self.assertLess(gate, runtime_recovery)
|
||||
|
||||
def test_boot_task_registration_pins_the_same_detached_release(self) -> None:
|
||||
for expected in (
|
||||
"[string]$StableSourceRoot",
|
||||
"symbolic-ref --quiet HEAD",
|
||||
'"status", "--porcelain=v1", "--untracked-files=normal"',
|
||||
'"-ExpectedSourceCommit $sourceCommit"',
|
||||
'"-ExpectedSourceTree $sourceTree"',
|
||||
'"-ExpectedBootScriptSha256 $bootScriptSha256"',
|
||||
'"-ExpectedStartScriptSha256 $startScriptSha256"',
|
||||
"-WorkingDirectory $resolvedSourceRoot",
|
||||
"Boot task registrar is not executing from the pinned stable source root",
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, BOOT_REGISTER_SOURCE)
|
||||
dirty_gate = BOOT_REGISTER_SOURCE.index("Stable source is not clean")
|
||||
task_registration = BOOT_REGISTER_SOURCE.index("Register-ScheduledTask")
|
||||
self.assertLess(dirty_gate, task_registration)
|
||||
|
||||
def test_hidden_trigger_never_executes_a_workspace_script_directly(self) -> None:
|
||||
for expected in (
|
||||
"VignettePublicRuntimeWatchdog",
|
||||
"-StableSourceRoot",
|
||||
"-ExpectedSourceCommit",
|
||||
"-ExpectedSourceTree",
|
||||
"-ExpectedWatchdogSha256",
|
||||
"-ExpectedStartScriptSha256",
|
||||
"WorkingDirectory",
|
||||
"Pinned watchdog script path does not match its working directory",
|
||||
"Pinned watchdog source root does not match its working directory",
|
||||
"Start-ScheduledTask",
|
||||
"Legacy shared-worktree watchdog action is forbidden",
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, HIDDEN_TRIGGER_SOURCE)
|
||||
self.assertIn(
|
||||
"Join-Path $root 'scripts\\watch-public-runtime.ps1'",
|
||||
HIDDEN_TRIGGER_SOURCE,
|
||||
)
|
||||
self.assertNotIn('objShell.Run "powershell.exe', HIDDEN_TRIGGER_SOURCE)
|
||||
self.assertNotIn("D:\\workspace\\vignette", HIDDEN_TRIGGER_SOURCE)
|
||||
marker_gate = HIDDEN_TRIGGER_SOURCE.index("foreach($marker in $required)")
|
||||
task_trigger = HIDDEN_TRIGGER_SOURCE.rindex("Start-ScheduledTask")
|
||||
self.assertLess(marker_gate, task_trigger)
|
||||
|
||||
def test_active_docs_only_show_the_pinned_release_workflow(self) -> None:
|
||||
stale_commands = (
|
||||
"powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\watch-public-runtime.ps1 -CheckOnly",
|
||||
"powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\install-public-runtime-task.ps1 -RunNow",
|
||||
)
|
||||
for source in (RUNBOOK_SOURCE, LOCAL_DEVELOPMENT_SOURCE):
|
||||
for stale in stale_commands:
|
||||
with self.subTest(document=source[:32], stale=stale):
|
||||
self.assertNotIn(stale, source)
|
||||
for expected in (
|
||||
"detached HEAD",
|
||||
"-StableSourceRoot",
|
||||
"-ExpectedSourceCommit",
|
||||
"-ExpectedSourceTree",
|
||||
"-ExpectedWatchdogSha256",
|
||||
"-ExpectedStartScriptSha256",
|
||||
"--porcelain=v1 --untracked-files=normal",
|
||||
):
|
||||
with self.subTest(expected=expected):
|
||||
self.assertIn(expected, RUNBOOK_SOURCE)
|
||||
self.assertIn("-StableSourceRoot", LOCAL_DEVELOPMENT_SOURCE)
|
||||
self.assertIn("watch-public-runtime-hidden.vbs", LOCAL_DEVELOPMENT_SOURCE)
|
||||
|
||||
@unittest.skipUnless(shutil.which("git.exe"), "git.exe is unavailable")
|
||||
@unittest.skipUnless(shutil.which("powershell.exe"), "Windows PowerShell 5.1 is unavailable")
|
||||
def test_dirty_detached_release_fails_before_health_or_runtime_mutation(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="vignette-watchdog-") as temp:
|
||||
root = Path(temp)
|
||||
scripts = root / "scripts"
|
||||
scripts.mkdir()
|
||||
copied_watchdog = scripts / WATCHDOG.name
|
||||
copied_installer = scripts / INSTALLER.name
|
||||
copied_boot = scripts / BOOT.name
|
||||
copied_boot_register = scripts / BOOT_REGISTER.name
|
||||
copied_start = scripts / START.name
|
||||
shutil.copy2(WATCHDOG, copied_watchdog)
|
||||
shutil.copy2(INSTALLER, copied_installer)
|
||||
shutil.copy2(BOOT, copied_boot)
|
||||
shutil.copy2(BOOT_REGISTER, copied_boot_register)
|
||||
shutil.copy2(START, copied_start)
|
||||
|
||||
self._git(root, "init")
|
||||
self._git(root, "config", "user.name", "Watchdog Contract Test")
|
||||
self._git(root, "config", "user.email", "watchdog-test@example.invalid")
|
||||
self._git(
|
||||
root,
|
||||
"add",
|
||||
"scripts/watch-public-runtime.ps1",
|
||||
"scripts/install-public-runtime-task.ps1",
|
||||
"scripts/boot-public-runtime.ps1",
|
||||
"scripts/register-boot-task.ps1",
|
||||
"scripts/start-public-runtime.ps1",
|
||||
)
|
||||
self._git(root, "commit", "-m", "watchdog fixture")
|
||||
self._git(root, "checkout", "--detach")
|
||||
commit = self._git(root, "rev-parse", "HEAD").stdout.strip()
|
||||
tree = self._git(root, "rev-parse", "HEAD^{tree}").stdout.strip()
|
||||
watchdog_hash = sha256(copied_watchdog)
|
||||
boot_hash = sha256(copied_boot)
|
||||
start_hash = sha256(copied_start)
|
||||
|
||||
with copied_start.open("ab") as stream:
|
||||
stream.write(b"\n# dirty fixture\n")
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
shutil.which("powershell.exe") or "powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(copied_watchdog),
|
||||
"-StableSourceRoot",
|
||||
str(root),
|
||||
"-ExpectedSourceCommit",
|
||||
commit,
|
||||
"-ExpectedSourceTree",
|
||||
tree,
|
||||
"-ExpectedWatchdogSha256",
|
||||
watchdog_hash,
|
||||
"-ExpectedStartScriptSha256",
|
||||
start_hash,
|
||||
"-CheckOnly",
|
||||
"-SkipPublicHealth",
|
||||
"-SkipCloudflaredRestart",
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=30,
|
||||
)
|
||||
detail = f"{completed.stdout}\n{completed.stderr}"
|
||||
self.assertNotEqual(completed.returncode, 0, detail)
|
||||
self.assertIn("Stable source is not clean", detail)
|
||||
self.assertFalse((root / "public-runtime-watchdog.failcount").exists())
|
||||
self.assertFalse((root / "public-runtime-watchdog.log").exists())
|
||||
|
||||
boot_completed = subprocess.run(
|
||||
[
|
||||
shutil.which("powershell.exe") or "powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(copied_boot),
|
||||
"-StableSourceRoot",
|
||||
str(root),
|
||||
"-ExpectedSourceCommit",
|
||||
commit,
|
||||
"-ExpectedSourceTree",
|
||||
tree,
|
||||
"-ExpectedBootScriptSha256",
|
||||
boot_hash,
|
||||
"-ExpectedStartScriptSha256",
|
||||
start_hash,
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=30,
|
||||
)
|
||||
boot_detail = f"{boot_completed.stdout}\n{boot_completed.stderr}"
|
||||
self.assertNotEqual(boot_completed.returncode, 0, boot_detail)
|
||||
self.assertIn("Stable source is not clean", boot_detail)
|
||||
self.assertFalse((root / "boot-public-runtime.log").exists())
|
||||
|
||||
for registrar in (copied_installer, copied_boot_register):
|
||||
register_completed = subprocess.run(
|
||||
[
|
||||
shutil.which("powershell.exe") or "powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(registrar),
|
||||
"-StableSourceRoot",
|
||||
str(root),
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=30,
|
||||
)
|
||||
register_detail = (
|
||||
f"{register_completed.stdout}\n{register_completed.stderr}"
|
||||
)
|
||||
with self.subTest(registrar=registrar.name):
|
||||
self.assertNotEqual(register_completed.returncode, 0, register_detail)
|
||||
self.assertIn("Stable source is not clean", register_detail)
|
||||
|
||||
def test_windows_powershell_51_parser_accepts_scripts(self) -> None:
|
||||
powershell = shutil.which("powershell.exe")
|
||||
if powershell is None:
|
||||
self.skipTest("Windows PowerShell 5.1 is unavailable")
|
||||
for script in (WATCHDOG, INSTALLER, BOOT, BOOT_REGISTER):
|
||||
escaped = str(script.resolve()).replace("'", "''")
|
||||
command = (
|
||||
"$tokens=$null; $errors=$null; "
|
||||
"[System.Management.Automation.Language.Parser]::ParseFile("
|
||||
f"'{escaped}', [ref]$tokens, [ref]$errors) | Out-Null; "
|
||||
"if ($errors.Count -gt 0) { $errors | ForEach-Object { Write-Error $_ }; exit 1 }; "
|
||||
"exit 0"
|
||||
)
|
||||
completed = subprocess.run(
|
||||
[powershell, "-NoProfile", "-Command", command],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
with self.subTest(script=script.name):
|
||||
self.assertEqual(completed.returncode, 0, completed.stderr)
|
||||
|
||||
def test_windows_script_host_accepts_hidden_trigger_syntax(self) -> None:
|
||||
cscript = shutil.which("cscript.exe")
|
||||
if cscript is None:
|
||||
self.skipTest("Windows Script Host is unavailable")
|
||||
completed = subprocess.run(
|
||||
[cscript, "//nologo", str(HIDDEN_TRIGGER), "syntax-only"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
self.assertEqual(completed.returncode, 0, completed.stderr)
|
||||
|
||||
def test_hidden_trigger_command_accepts_only_a_structurally_pinned_task(self) -> None:
|
||||
cscript = shutil.which("cscript.exe")
|
||||
powershell = shutil.which("powershell.exe")
|
||||
if cscript is None or powershell is None:
|
||||
self.skipTest("Windows Script Host or PowerShell 5.1 is unavailable")
|
||||
rendered = subprocess.run(
|
||||
[cscript, "//nologo", str(HIDDEN_TRIGGER), "print-command"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
self.assertEqual(rendered.returncode, 0, rendered.stderr)
|
||||
prefix = (
|
||||
"powershell.exe -NoProfile -ExecutionPolicy Bypass "
|
||||
'-WindowStyle Hidden -Command "'
|
||||
)
|
||||
wrapped = rendered.stdout.strip()
|
||||
self.assertTrue(wrapped.startswith(prefix), wrapped)
|
||||
self.assertTrue(wrapped.endswith('"'), wrapped)
|
||||
command = wrapped[len(prefix) : -1].replace('""', '"')
|
||||
|
||||
root = r"C:\Pinned Vignette Release"
|
||||
arguments = (
|
||||
f'-File "{root}\\scripts\\watch-public-runtime.ps1" '
|
||||
f'-StableSourceRoot "{root}" '
|
||||
f"-ExpectedSourceCommit {'a' * 40} "
|
||||
f"-ExpectedSourceTree {'b' * 40} "
|
||||
f"-ExpectedWatchdogSha256 {'c' * 64} "
|
||||
f"-ExpectedStartScriptSha256 {'d' * 64}"
|
||||
)
|
||||
fixture = (
|
||||
"$script:watchdogTriggered=$false;"
|
||||
"function Get-ScheduledTask { param($TaskName,$ErrorAction) "
|
||||
"$action=[pscustomobject]@{"
|
||||
"Execute='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';"
|
||||
f"WorkingDirectory='{root}';Arguments='{arguments}'"
|
||||
"};[pscustomobject]@{Actions=@($action)}};"
|
||||
"function Start-ScheduledTask { param($TaskName) "
|
||||
"$script:watchdogTriggered=$true };"
|
||||
f"{command};"
|
||||
"if(-not $script:watchdogTriggered){exit 9};exit 0"
|
||||
)
|
||||
checked = subprocess.run(
|
||||
[powershell, "-NoProfile", "-Command", fixture],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
self.assertEqual(checked.returncode, 0, checked.stderr)
|
||||
|
||||
def _git(self, root: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||
completed = subprocess.run(
|
||||
[shutil.which("git.exe") or "git.exe", "-C", str(root), *args],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
self.assertEqual(completed.returncode, 0, completed.stderr)
|
||||
return completed
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue