fix(cd): accept bounded partial SSH route proof

This commit is contained in:
ogt
2026-07-14 10:45:15 +08:00
parent a677199bb6
commit a5030e9fe0
2 changed files with 134 additions and 4 deletions

View File

@@ -3223,7 +3223,8 @@ jobs:
# NetworkPolicy boundary and the target replied. Keep host
# service readiness separate from this security-boundary
# verifier; a stopped sshd must not make a healthy rollout
# look like a policy failure.
# look like a policy failure. This is bounded to the fixed
# targets whose exact CIDRs were verified immediately above.
refused.append(host)
except OSError:
# Endpoint availability is a separate runtime signal. The
@@ -3243,7 +3244,13 @@ jobs:
"broker_ssh_allowlisted_endpoint_unavailable="
+ ",".join(unavailable)
)
raise SystemExit(0 if connected else 1)
# Static verification already proves the exact five-destination
# allowlist. The live probe only needs one positive route signal;
# requiring every host to answer confuses host/service availability
# (for example a booted host with sshd down) with NetworkPolicy
# correctness. Arbitrary OSError values remain non-evidence, so an
# all-timeout/unreachable result still fails closed.
raise SystemExit(0 if connected or refused else 1)
PY
}
verify_ssh_denied awoooi-api api || {
@@ -3253,7 +3260,7 @@ jobs:
echo "❌ signal worker can still establish host SSH connections"; exit 1;
}
verify_broker_ssh_allowed || {
echo "❌ execution broker cannot reach any allowlisted SSH endpoint"; exit 1;
echo "❌ execution broker has no live route evidence for allowlisted SSH endpoints"; exit 1;
}
echo "executor_boundary_stage=live_socket_boundary_verified"

View File

@@ -1,10 +1,24 @@
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
import textwrap
from pathlib import Path
from typing import Any
import yaml
_BROKER_PROBE_HOSTS = (
"192.168.0.110",
"192.168.0.112",
"192.168.0.120",
"192.168.0.121",
"192.168.0.188",
)
def _network_policies() -> dict[str, dict[str, Any]]:
repo_root = Path(__file__).resolve().parents[3]
@@ -24,6 +38,64 @@ def _ports(policy: dict[str, Any]) -> set[int]:
}
def _broker_probe_script() -> str:
repo_root = Path(__file__).resolve().parents[3]
workflow = (repo_root / ".gitea/workflows/cd.yaml").read_text()
function = re.search(
r"verify_broker_ssh_allowed\(\) \{.*?<<'PY'\n"
r"(?P<script>.*?)\n\s+PY\n\s+\}",
workflow,
re.DOTALL,
)
assert function is not None
return textwrap.dedent(function.group("script"))
def _run_broker_probe(
tmp_path: Path,
*,
outcomes: dict[str, str],
) -> subprocess.CompletedProcess[str]:
fake_socket = tmp_path / "socket.py"
fake_socket.write_text(
textwrap.dedent(
"""
import json
import os
_OUTCOMES = json.loads(os.environ["AWOOOI_TEST_SOCKET_OUTCOMES"])
class _Connection:
def close(self):
return None
def create_connection(address, timeout):
del timeout
outcome = _OUTCOMES.get(address[0], "unreachable")
if outcome == "connected":
return _Connection()
if outcome == "refused":
raise ConnectionRefusedError(address[0])
raise TimeoutError(address[0])
"""
),
encoding="utf-8",
)
env = os.environ.copy()
env["PYTHONPATH"] = str(tmp_path)
env["AWOOOI_TEST_SOCKET_OUTCOMES"] = json.dumps(outcomes)
return subprocess.run(
[sys.executable, "-", *_BROKER_PROBE_HOSTS],
input=_broker_probe_script(),
text=True,
capture_output=True,
check=False,
env=env,
)
def test_common_egress_does_not_grant_ssh() -> None:
policies = _network_policies()
@@ -121,6 +193,57 @@ def test_cd_applies_rolls_back_and_verifies_network_boundary() -> None:
assert "except ConnectionRefusedError:" in workflow
assert "broker_ssh_refused_but_egress_permitted=" in workflow
assert "broker_ssh_allowlisted_endpoint_unavailable=" in workflow
assert "raise SystemExit(0 if connected else 1)" in workflow
assert "raise SystemExit(0 if connected or refused else 1)" in workflow
assert "len(permitted) == len(sys.argv) - 1" not in workflow
assert "ssh-mcp-key known_hosts 更新失敗,停止部署" in workflow
def test_broker_probe_accepts_partial_live_route_evidence(tmp_path: Path) -> None:
result = _run_broker_probe(
tmp_path,
outcomes={
"192.168.0.110": "connected",
"192.168.0.112": "unreachable",
"192.168.0.120": "connected",
"192.168.0.121": "connected",
"192.168.0.188": "connected",
},
)
assert result.returncode == 0
assert result.stdout.splitlines() == [
"broker_ssh_connected="
"192.168.0.110,192.168.0.120,192.168.0.121,192.168.0.188",
"broker_ssh_refused_but_egress_permitted=",
"broker_ssh_allowlisted_endpoint_unavailable=192.168.0.112",
]
def test_broker_probe_accepts_refusal_as_bounded_route_evidence(
tmp_path: Path,
) -> None:
result = _run_broker_probe(
tmp_path,
outcomes={"192.168.0.112": "refused"},
)
assert result.returncode == 0
assert result.stdout.splitlines() == [
"broker_ssh_connected=",
"broker_ssh_refused_but_egress_permitted=192.168.0.112",
"broker_ssh_allowlisted_endpoint_unavailable="
"192.168.0.110,192.168.0.120,192.168.0.121,192.168.0.188",
]
def test_broker_probe_rejects_arbitrary_socket_errors(tmp_path: Path) -> None:
result = _run_broker_probe(tmp_path, outcomes={})
assert result.returncode == 1
assert result.stdout.splitlines() == [
"broker_ssh_connected=",
"broker_ssh_refused_but_egress_permitted=",
"broker_ssh_allowlisted_endpoint_unavailable="
"192.168.0.110,192.168.0.112,192.168.0.120,"
"192.168.0.121,192.168.0.188",
]