fix(hermes): reject invalid release revisions early

This commit is contained in:
jenkins 2026-08-24 01:19:58 -03:00
parent 33de8e38fe
commit 3c06ae867b
3 changed files with 124 additions and 3 deletions

View File

@ -14,6 +14,10 @@ from pathlib import Path
JENKINS_ORIGIN = "https://ci.bstein.dev"
JENKINS_BUILD_URL = f"{JENKINS_ORIGIN}/buildByToken/buildWithParameters"
SCM_BROKER_ORIGIN = "http://hermes-scm-broker.hermes-scm.svc.cluster.local:9081"
SCM_METADATA_URL = f"{SCM_BROKER_ORIGIN}/v1/metadata"
SCM_REPOSITORY = "titan-iac"
MAX_SCM_RESPONSE_BYTES = 512 * 1024
JOBS = {
"agent": {
"job": "hermes-agent-image",
@ -58,12 +62,45 @@ def _open_without_redirect(request: urllib.request.Request, timeout: int):
raise
def _verify_reviewed_revision_exists(revision: str) -> None:
"""Fail before queuing when the exact reviewed commit is absent from SCM."""
path = f"/api/v1/repos/atlas/{SCM_REPOSITORY}/git/commits/{revision}"
payload = json.dumps({"path": path}, separators=(",", ":")).encode("utf-8")
request = urllib.request.Request(
SCM_METADATA_URL,
data=payload,
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "hermes-image-release/1",
},
method="POST",
)
opener = urllib.request.build_opener(_NoRedirect())
with opener.open(request, timeout=20) as response:
if int(response.status) != 200:
raise RuntimeError("SCM did not confirm the reviewed revision")
content_type = response.headers.get_content_type()
if content_type != "application/json":
raise RuntimeError("SCM returned an unexpected revision response")
body = response.read(MAX_SCM_RESPONSE_BYTES + 1)
if len(body) > MAX_SCM_RESPONSE_BYTES:
raise RuntimeError("SCM revision response exceeded the safe size limit")
try:
result = json.loads(body)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("SCM returned malformed revision evidence") from exc
if not isinstance(result, dict) or result.get("sha") != revision:
raise RuntimeError("SCM did not confirm the exact reviewed revision")
def trigger_build(
revision: str,
*,
component: str = "agent",
token_file: Path = TOKEN_FILE,
opener=_open_without_redirect,
revision_verifier=None,
) -> dict[str, str | int]:
"""Post one allow-listed job's fixed parameters using the release token."""
revision = revision.strip()
@ -75,6 +112,8 @@ def trigger_build(
token = token_file.read_text(encoding="utf-8").strip()
if not token:
raise RuntimeError("Jenkins image-build token is empty")
if revision_verifier is not None:
revision_verifier(revision)
fields = {
"job": job["job"],
"token": token,
@ -132,7 +171,11 @@ def main() -> int:
parser.add_argument("revision", help="reviewed full commit contained by main")
args = parser.parse_args()
try:
result = trigger_build(args.revision, component=args.component)
result = trigger_build(
args.revision,
component=args.component,
revision_verifier=_verify_reviewed_revision_exists,
)
except (OSError, ValueError, RuntimeError, urllib.error.URLError) as exc:
print(json.dumps({"error": str(exc)}, sort_keys=True))
return 1

View File

@ -190,6 +190,60 @@ def test_trigger_redirect_wrapper_accepts_only_plugin_303(
module._open_without_redirect(request, 9)
def test_trigger_confirms_exact_revision_through_credential_isolated_scm(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Revision preflight is bounded, fixed-origin, and exact-SHA fail closed."""
module = _load(TRIGGER, "trigger_revision_coverage")
revision = "7" * 40
captured = {}
class Headers(dict):
def get_content_type(self):
return self.get("Content-Type", "application/octet-stream")
class Opener:
def __init__(self, status=200, body=b"", content_type="application/json"):
self.status = status
self.body = body
self.content_type = content_type
def open(self, request, timeout):
captured["request"] = request
captured["timeout"] = timeout
response = Response(
self.status,
self.body,
Headers({"Content-Type": self.content_type}),
)
return response
opener = Opener(body=json.dumps({"sha": revision}).encode())
monkeypatch.setattr(module.urllib.request, "build_opener", lambda *_args: opener)
module._verify_reviewed_revision_exists(revision)
request = captured["request"]
assert request.full_url == module.SCM_METADATA_URL
assert captured["timeout"] == 20
assert json.loads(request.data)["path"].endswith(f"/git/commits/{revision}")
cases = (
(Opener(status=503, body=b"{}"), "did not confirm"),
(Opener(body=b"{}", content_type="text/plain"), "unexpected"),
(Opener(body=b"x" * (module.MAX_SCM_RESPONSE_BYTES + 1)), "safe size"),
(Opener(body=b"not-json"), "malformed"),
(Opener(body=json.dumps({"sha": "8" * 40}).encode()), "exact"),
(Opener(body=b"[]"), "exact"),
)
for failing_opener, message in cases:
monkeypatch.setattr(
module.urllib.request,
"build_opener",
lambda *_args, value=failing_opener: value,
)
with pytest.raises(RuntimeError, match=message):
module._verify_reviewed_revision_exists(revision)
def test_trigger_main_reports_safe_success_and_errors(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
@ -200,7 +254,7 @@ def test_trigger_main_reports_safe_success_and_errors(
monkeypatch.setattr(
module,
"trigger_build",
lambda value, component="agent": {
lambda value, component="agent", **_kwargs: {
"component": component,
"job": module.JOBS[component]["job"],
"source_revision": value,
@ -212,7 +266,9 @@ def test_trigger_main_reports_safe_success_and_errors(
monkeypatch.setattr(
module,
"trigger_build",
lambda _value, component="agent": (_ for _ in ()).throw(OSError("closed")),
lambda _value, component="agent", **_kwargs: (
_ for _ in ()
).throw(OSError("closed")),
)
assert module.main() == 1
assert json.loads(capsys.readouterr().out) == {"error": "closed"}

View File

@ -322,6 +322,28 @@ def test_trigger_returns_a_token_free_exact_follow_command(tmp_path: Path) -> No
assert "never-print-me" not in json.dumps(result)
def test_trigger_cli_verifies_the_exact_revision_before_queueing(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
"""The CLI never spends a Jenkins build slot on a nonexistent commit."""
module = _load(TRIGGER, "release_status_trigger_revision")
revision = "8" * 40
seen = []
def trigger(value, *, component, revision_verifier):
revision_verifier(value)
seen.append((value, component))
return {"source_revision": value, "component": component}
monkeypatch.setattr(module, "trigger_build", trigger)
monkeypatch.setattr(module, "_verify_reviewed_revision_exists", seen.append)
monkeypatch.setattr(sys, "argv", ["trigger", "--component", "stt", revision])
assert module.main() == 0
assert seen == [revision, (revision, "stt")]
assert json.loads(capsys.readouterr().out)["source_revision"] == revision
def test_runtime_bundle_and_guidance_require_live_convergence() -> None:
"""The helper ships to Worker and merged code alone is explicitly insufficient."""
kustomization = (ROOT / "services/hermes/kustomization.yaml").read_text()