atlas-iac/services/hermes/scm-common/scripts/scm_broker_client.py
2026-08-16 23:12:08 -03:00

96 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Call the credential-isolated Hermes SCM broker over its fixed cluster origin."""
from __future__ import annotations
import json
import urllib.request
from collections.abc import Callable
from gitea_api_policy import PolicyError
BROKER_ORIGIN = "http://hermes-scm-broker.hermes-scm.svc.cluster.local:9081"
MAX_RESPONSE_BYTES = 2 * 1024 * 1024
class RejectRedirectHandler(urllib.request.HTTPRedirectHandler):
"""Keep every broker request on its fixed in-cluster origin."""
def redirect_request(self, req, fp, code, msg, headers, newurl):
raise PolicyError("SCM broker redirects are not allowed")
_OPENER = urllib.request.build_opener(RejectRedirectHandler())
def _open(request: urllib.request.Request, timeout: int):
return _OPENER.open(request, timeout=timeout)
def request(
endpoint: str,
payload: dict[str, object],
*,
opener: Callable[..., object] = _open,
) -> bytes:
"""Send one bounded broker operation without any repository credential."""
if endpoint not in {"/v1/metadata", "/v1/drafts"}:
raise PolicyError("SCM broker operation is outside the client allowlist")
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
if len(body) > 64 * 1024:
raise PolicyError("SCM broker request exceeds the safe size limit")
outgoing = urllib.request.Request(
BROKER_ORIGIN + endpoint,
data=body,
method="POST",
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "hermes-scm-broker-client/1",
},
)
with opener(outgoing, timeout=30) as response: # type: ignore[attr-defined]
status = getattr(response, "status", None)
if status is None and hasattr(response, "getcode"):
status = response.getcode() # type: ignore[attr-defined]
if status != 200:
raise PolicyError("SCM broker returned an unexpected HTTP status")
content_type = response.headers.get_content_type() # type: ignore[attr-defined]
if content_type != "application/json":
raise PolicyError("SCM broker returned an unexpected response type")
result = response.read(MAX_RESPONSE_BYTES + 1) # type: ignore[attr-defined]
if len(result) > MAX_RESPONSE_BYTES:
raise PolicyError("SCM broker response exceeds the safe size limit")
json.loads(result)
return result
def read(path: str, *, opener: Callable[..., object] = _open) -> bytes:
"""Read explicitly allowed Atlas metadata through the broker."""
return request("/v1/metadata", {"path": path}, opener=opener)
def create_draft(
repo: str,
*,
base: str,
head: str,
head_sha: str,
title: str,
body: str,
opener: Callable[..., object] = _open,
) -> bytes:
"""Create one verified draft through the broker for human review."""
return request(
"/v1/drafts",
{
"base": base,
"body": body,
"head": head,
"head_sha": head_sha,
"repo": repo,
"title": title,
},
opener=opener,
)