merge(plane-on-kubernetes): groups 1-2 -- cluster profile, no host literals, delivery through the S3 store

Kind proofs: records 01 and 02. Task 2.6 (receiver removal) is paused while the
safety net and the decision engine come first. deployment.md conflicts resolved
in the plain-language style; corrects the RuntimeClass claim (Substrate runs
runsc inside its worker pod).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-03 07:57:13 +02:00
co-authored by Claude Opus 5.5
62 changed files with 3080 additions and 433 deletions
+26 -2
View File
@@ -65,11 +65,12 @@ from pathlib import Path
HERE = Path(__file__).resolve().parent
argv = sys.argv[1:]
context = ""
if argv[:1] == ["--context"]:
argv = argv[2:]
context, argv = argv[1], argv[2:]
verb, actor, space = argv[0], argv[2], argv[argv.index("-a") + 1]
with (HERE / "ax.log").open("a") as log:
log.write(f"{time.monotonic():.4f} egress {verb} {actor}\n")
log.write(f"{time.monotonic():.4f} egress {verb} {actor} {context}\n")
record = HERE / "applied" / f"{actor}.json"
alive = record.exists() and not json.loads(record.read_text()).get("gone")
store = HERE / "policies" / f"{space}_{actor}.json"
@@ -103,3 +104,26 @@ def kubectl_ate(tmp_path, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("OUSAST_KUBECTL_ATE", str(path))
monkeypatch.setenv("OUSAST_EGRESS_SETTLE", "0")
return path
PROFILE_CONTEXT = "kind-test" # the fakes must receive this, never a context a module built (plane-on-kubernetes 1.2)
PROFILE_TOML = (
'exec = "local"\nkube_context = "{context}"\nregistry = "registry.test:5000"\nimages = "images.json"\n'
'memory = "file://{memory}"\natespace = "default"\n[pools.default]\nreplicas = 1\ncpu = "1"\nmemory = "1Gi"\n'
)
@pytest.fixture
def plane_profile(tmp_path, monkeypatch: pytest.MonkeyPatch):
"""A throwaway PlaneProfile under ``tmp_path`` (context ``kind-test``, a file:// memory store, a digest-pinned
images file), selected through ``OUSAST_PLANE_PROFILE`` so every plane module loads it."""
import json
root = tmp_path / "profile"
root.mkdir()
(root / "test.toml").write_text(PROFILE_TOML.format(context=PROFILE_CONTEXT, memory=tmp_path / "memory"), encoding="utf-8")
(root / "images.json").write_text(json.dumps({"runner": "registry.test:5000/ousast-runner@sha256:" + "ab" * 32}), encoding="utf-8")
monkeypatch.setenv("OUSAST_PLANE_PROFILE", str(root / "test.toml"))
for key in ("OUSAST_KUBE_CONTEXT", "OUSAST_MEMORY", "OUSAST_REGISTRY", "OUSAST_IMAGES", "OUSAST_EXEC"):
monkeypatch.delenv(key, raising=False)
return root / "test.toml"
+109
View File
@@ -0,0 +1,109 @@
"""An S3-shaped memory store for the delivery tests: objects in a dict, presigned URLs served by a local HTTP server
that accepts PUT, GET and HEAD by key (``/<bucket>/<key>?X-Amz-Expires=N&X-Amz-Signature=fake``), the way a
presigned S3 URL looks to the runner. Nothing here is imported by the product."""
from __future__ import annotations
import threading
from collections.abc import Mapping
from datetime import timedelta
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import unquote, urlsplit
from openultrasast.plane.memory import MemoryStore
class HttpObjectStore(MemoryStore):
"""``presign_*`` hand out URLs on a local server; ``requests`` records every HTTP call (method, key)."""
def __init__(self, bucket: str = "fake-bucket", host: str = "localhost") -> None:
self.objects: dict[str, bytes] = {}
self.bucket, self.host = bucket, host
self.requests: list[tuple[str, str]] = []
self.presigned: list[tuple[str, str, int]] = []
self.lock = threading.Lock()
self.server = ThreadingHTTPServer(("127.0.0.1", 0), _Handler)
self.server.store = self # type: ignore[attr-defined]
threading.Thread(target=self.server.serve_forever, daemon=True).start()
def close(self) -> None:
self.server.shutdown()
self.server.server_close()
def describe(self) -> str:
return f"fake://{self.bucket}"
def _get(self, key: str) -> tuple[bytes, str | None] | None:
with self.lock:
data = self.objects.get(key)
return None if data is None else (data, "v1")
def _put(self, key: str, data: bytes, labels: Mapping[str, str] | None = None) -> None:
with self.lock:
self.objects[key] = bytes(data)
def _delete(self, key: str) -> None:
with self.lock:
self.objects.pop(key, None)
def _keys(self, prefix: str) -> list[str]:
with self.lock:
return sorted(k for k in self.objects if k.startswith(prefix))
def _url(self, method: str, key: str, expires: timedelta) -> str:
self.presigned.append((method, key, int(expires.total_seconds())))
port = self.server.server_address[1]
return f"http://{self.host}:{port}/{self.bucket}/{key}?X-Amz-Expires={int(expires.total_seconds())}&X-Amz-Signature=fake"
def presign_put(self, key: str, expires: timedelta = timedelta(hours=1)) -> str:
return self._url("PUT", key, expires)
def presign_get(self, key: str, expires: timedelta = timedelta(hours=1)) -> str:
return self._url("GET", key, expires)
class _Handler(BaseHTTPRequestHandler):
def _key(self) -> str | None:
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
path = unquote(urlsplit(self.path).path)
prefix = f"/{store.bucket}/"
return path[len(prefix) :] if path.startswith(prefix) else None
def do_PUT(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
body = self.rfile.read(int(self.headers.get("Content-Length") or 0))
store.requests.append(("PUT", key or self.path))
if key is None:
return self._answer(404)
store._put(key, body)
self._answer(200)
def do_GET(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
store.requests.append(("GET", key or self.path))
got = store._get(key) if key else None
if got is None:
return self._answer(404)
self.send_response(200)
self.send_header("Content-Length", str(len(got[0])))
self.end_headers()
self.wfile.write(got[0])
def do_HEAD(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
store.requests.append(("HEAD", key or self.path))
got = store._get(key) if key else None
self.send_response(404 if got is None else 200)
self.send_header("Content-Length", "0" if got is None else str(len(got[0])))
self.end_headers()
def _answer(self, code: int) -> None:
self.send_response(code)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, format: str, *args: object) -> None: # noqa: A002
pass
+164
View File
@@ -0,0 +1,164 @@
"""plane-on-kubernetes task 2.1 (Req 3.1, 2.1): ``Delivery`` against an S3-shaped fake -- presigned URLs in the start
body, expiry, polling, extraction with the receiver's member checks, declared-only re-put, state seeding, and no URL
in any log line."""
from __future__ import annotations
import io
import json
import logging
import tarfile
import urllib.request
from collections.abc import Iterator
from pathlib import Path
import pytest
from plane_fake_store import HttpObjectStore
from openultrasast.plane.delivery import GRACE_SECONDS, Delivery, DeliveryError, declared, expiry, input_var
@pytest.fixture
def store() -> Iterator[HttpObjectStore]:
fake = HttpObjectStore()
try:
yield fake
finally:
fake.close()
def tar_of(files: dict[str, bytes], *, symlink: str | None = None) -> bytes:
buffer = io.BytesIO()
with tarfile.open(fileobj=buffer, mode="w") as tar:
for name, data in files.items():
info = tarfile.TarInfo(name)
info.size = len(data)
tar.addfile(info, io.BytesIO(data))
if symlink:
info = tarfile.TarInfo(symlink)
info.type = tarfile.SYMTYPE
info.linkname = "/etc/passwd"
tar.addfile(info)
return buffer.getvalue()
def put_via_url(url: str, data: bytes) -> int:
request = urllib.request.Request(url, data=data, method="PUT", headers={"Content-Type": "application/x-tar"})
with urllib.request.urlopen(request, timeout=10) as response: # noqa: S310 - the fake's local URL
return int(response.status)
def test_the_start_body_names_exactly_the_tar_and_the_declared_inputs(store: HttpObjectStore, tmp_path: Path, monkeypatch) -> None:
monkeypatch.setenv("OUSAST_TASK_TIMEOUT", "100")
delivery = Delivery(store, "run-1", tmp_path)
body = delivery.body("verify", {"facts": "repo-facts/facts.json", "pass a": "va/units.jsonl"})
assert set(body) == {"put", "inputs"}
assert body["put"].startswith(f"http://localhost:{store.server.server_address[1]}/fake-bucket/runs/run-1/verify/output.tar?")
assert set(body["inputs"]) == {"FACTS", "PASS_A"} and input_var("pass a") == "PASS_A"
assert "/runs/run-1/repo-facts/facts.json?" in body["inputs"]["FACTS"] and "/runs/run-1/va/units.jsonl?" in body["inputs"]["PASS_A"]
assert {m for m, _, _ in store.presigned} == {"PUT", "GET"}
assert {e for _, _, e in store.presigned} == {100 + GRACE_SECONDS}, "expiry = OUSAST_TASK_TIMEOUT + 600"
assert expiry({"OUSAST_TASK_TIMEOUT": "7200"}).total_seconds() == 7800 and expiry({}).total_seconds() == 7800
assert delivery.host == "localhost", "the hostname the egress policy must allow"
with pytest.raises(DeliveryError, match="not <producer>/<artifact>"):
delivery.input_urls({"x": "no-slash"})
def test_delivered_polls_the_store_and_collect_extracts_republishes_and_marks_done(
store: HttpObjectStore, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
caplog.set_level(logging.DEBUG)
delivery = Delivery(store, "r", tmp_path / "r")
assert delivery.delivered("facts") is False
summary = {"status": "done", "units_done": 1, "units_total": 1}
tar = tar_of({"facts.json": b'{"a": 1}', "summary.json": json.dumps(summary).encode(), "scratch/notes.txt": b"not declared"})
assert put_via_url(delivery.put_url("facts"), tar) == 200, "the runner's PUT to the presigned URL"
assert delivery.delivered("facts") is True
got, size = delivery.collect("facts", ["facts.json", "summary.json", "missing.json"])
assert got == summary and size == len(tar)
assert (tmp_path / "r" / "facts" / "facts.json").read_bytes() == b'{"a": 1}'
assert (tmp_path / "r" / "facts" / "scratch" / "notes.txt").exists(), "extracted locally"
assert store.objects["runs/r/facts/facts.json"] == b'{"a": 1}' and "runs/r/facts/summary.json" in store.objects
assert "runs/r/facts/scratch/notes.txt" not in store.objects and "runs/r/facts/missing.json" not in store.objects, "declared only"
done = json.loads(store.objects["runs/r/facts/done.json"])
assert (
done["status"] == "done"
and done["bytes"] == len(tar)
and done["files"] == 3
and done["published"] == ["facts.json", "summary.json"]
)
# a consumer's presigned GET names one artifact and fetches it
urls = delivery.input_urls({"facts": "facts/facts.json"})
with urllib.request.urlopen(urls["FACTS"], timeout=10) as response: # noqa: S310
assert response.read() == b'{"a": 1}'
assert "X-Amz" not in caplog.text and "fake-bucket" not in caplog.text, "URLs never appear in logs"
delivery.reset("facts") # the next attempt must not complete on this attempt's tar
assert delivery.delivered("facts") is False and "runs/r/facts/done.json" not in store.objects
assert "runs/r/facts/facts.json" in store.objects, "a re-put output stays for its consumers until it is re-published"
@pytest.mark.parametrize("bad", ["../escape.json", "/abs.json"])
def test_a_tar_member_that_escapes_is_rejected_and_nothing_is_written(store: HttpObjectStore, tmp_path: Path, bad: str) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
store._put(delivery.key("t", "output.tar"), tar_of({bad: b"{}", "summary.json": b"{}"}))
with pytest.raises(DeliveryError, match="rejected member"):
delivery.collect("t", ["summary.json"])
assert not (tmp_path / "r" / "t").exists() and not (tmp_path / "escape.json").exists()
assert "runs/r/t/done.json" not in store.objects and "runs/r/t/summary.json" not in store.objects
def test_a_symlink_member_and_a_non_tar_are_rejected(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
store._put(delivery.key("t", "output.tar"), tar_of({"summary.json": b"{}"}, symlink="link"))
with pytest.raises(DeliveryError, match="rejected member link"):
delivery.collect("t", ["summary.json"])
store._put(delivery.key("t", "output.tar"), b"not a tar at all")
with pytest.raises(DeliveryError, match="bad tar"):
delivery.collect("t", ["summary.json"])
with pytest.raises(DeliveryError, match="not in the store"):
delivery.collect("never", [])
def test_state_is_mirrored_and_seeds_a_rerun_elsewhere(store: HttpObjectStore, tmp_path: Path) -> None:
first = Delivery(store, "r", tmp_path / "machine-a")
assert first.load_state() is None
first.save_state({"run": "r", "tasks": {"facts": {"status": "done"}}})
second = Delivery(store, "r", tmp_path / "machine-b") # another results root, the same store
assert second.load_state() == {"run": "r", "tasks": {"facts": {"status": "done"}}}
store._put(second.key("state.json"), b"not json")
assert second.load_state() is None
def test_put_input_stores_a_file_no_task_produces(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
source = tmp_path / "candidates.json"
source.write_bytes(b"[1, 2, 3]")
url = delivery.put_input("candidates.json", source)
assert store.objects["runs/r/inputs/candidates.json"] == b"[1, 2, 3]"
with urllib.request.urlopen(url, timeout=10) as response: # noqa: S310
assert response.read() == b"[1, 2, 3]"
def test_declared_artifacts_are_outputs_plus_what_consumers_name(tmp_path: Path) -> None:
"""A consumer may declare a producer's summary.json (every task writes one) without the producer listing it."""
from openultrasast.plane.manifests import load_manifests
text = (
"apiVersion: ax.io/v1alpha1\nkind: Task\nmetadata: {name: t}\nspec: {command: [repo-facts]}\n---\n"
"apiVersion: openultrasast.io/v1alpha1\nkind: Run\nmetadata: {name: r}\nspec:\n tasks:\n"
" - {name: facts, task: t, outputs: [facts.json]}\n"
" - {name: verify, task: t, inputs: {facts: facts/facts.json, facts_summary: facts/summary.json}, outputs: [units.jsonl]}\n"
" - {name: agree, task: t, inputs: {pass: verify/units.jsonl, pass_summary: verify/summary.json}}\n"
)
(tmp_path / "run.yaml").write_text(text, encoding="utf-8")
run = next(iter(load_manifests([tmp_path / "run.yaml"]).runs.values()))
assert declared(run) == {"facts": ["facts.json", "summary.json"], "verify": ["units.jsonl", "summary.json"], "agree": []}
def test_publish_reputs_only_declared_files_under_the_task_directory(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
(tmp_path / "r" / "seeded").mkdir(parents=True)
(tmp_path / "r" / "seeded" / "facts.json").write_bytes(b"{}")
(tmp_path / "r" / "secret.txt").write_bytes(b"outside")
assert delivery.publish("seeded", ["facts.json", "../secret.txt", "absent.json"]) == ["facts.json"]
assert set(store.objects) == {"runs/r/seeded/facts.json"}
+58 -17
View File
@@ -1,8 +1,10 @@
"""ai-service-plane task 0: the per-task egress policy, its kubectl-ate calls, and the runner's dialled delivery.
Agent Substrate's egress gateway denies by default; :func:`egress.policy_for` must grant exactly the receiver (HTTP
by name), the bound Workspaces' Git hosts and the bound Model's API host (TLS passthrough), and nothing a gateway
would reject or that would intercept TLS: no IP address, no wildcard, no ``https`` rule.
Agent Substrate's egress gateway denies by default; :func:`egress.policy_for` must grant exactly the memory store's
host (where the runner PUTs its tar and GETs its inputs), the bound Workspaces' Git hosts and the bound Model's API
host, all as one TLS passthrough rule on 443 (plane-on-kubernetes 2.3), and nothing a gateway would reject or that
would intercept TLS: no IP address, no wildcard, no ``https`` rule, no ``http`` rule (the receiver path, kept for
group 2 only, is the one exception).
"""
from __future__ import annotations
@@ -70,24 +72,59 @@ def assert_tight(policy: dict) -> None:
ipaddress.ip_address(host)
def test_model_free_task_gets_only_the_receiver(manifests) -> None:
policy = egress.policy_for(manifests.tasks["facts"], [], None, egress.RECEIVER_HOST)
assert policy == {"rules": [{"http": {"hostnames": [egress.RECEIVER_HOST], "ports": {"numbers": [80]}}}]}
STORE = "files.store.example"
def test_model_free_task_gets_only_the_store(manifests) -> None:
policy = egress.policy_for(manifests.tasks["facts"], [], None, STORE)
assert policy == {"rules": [{"tlsPassthrough": {"hostnames": [STORE], "ports": {"numbers": [443]}}}]}
assert_tight(policy)
assert egress.policy_for(manifests.tasks["facts"], [], None, None) == {"rules": []}, "no store, no Git, no Model: nothing"
def test_the_receiver_path_adds_the_one_http_rule_until_it_is_removed(manifests) -> None:
policy = egress.policy_for(manifests.tasks["facts"], [], None, None, receiver_host=egress.RECEIVER_HOST)
assert policy == {"rules": [{"http": {"hostnames": [egress.RECEIVER_HOST], "ports": {"numbers": [80]}}}]}
assert egress.policy_for(manifests.tasks["facts"], [], None, None, receiver_host="127.0.0.1") == {"rules": []}
def test_model_bound_task_with_git_workspaces(manifests) -> None:
task = manifests.tasks["verify"]
policy = egress.policy_for(task, [manifests.workspaces["cases"]], manifests.models["deepseek-flash"], egress.RECEIVER_HOST)
policy = egress.policy_for(task, [manifests.workspaces["cases"]], manifests.models["deepseek-flash"], STORE)
assert policy["rules"] == [
{"http": {"hostnames": [egress.RECEIVER_HOST], "ports": {"numbers": [80]}}},
{"tlsPassthrough": {"hostnames": ["api.deepseek.com", "github.com"], "ports": {"numbers": [443]}}},
], "one github.com for two repos; the ssh and IP-address repos add nothing"
{"tlsPassthrough": {"hostnames": ["api.deepseek.com", STORE, "github.com"], "ports": {"numbers": [443]}}},
], "one rule: the store, one github.com for two repos, the Model host; the ssh and IP-address repos add nothing"
assert_tight(policy)
def test_an_ip_receiver_is_not_granted(manifests) -> None:
assert egress.policy_for(manifests.tasks["facts"], [], None, "127.0.0.1") == {"rules": []}
def test_an_ip_store_endpoint_is_refused_naming_the_variable(manifests) -> None:
with pytest.raises(ValueError, match="S3_ENDPOINT must be a hostname"):
egress.policy_for(manifests.tasks["facts"], [], None, "127.0.0.1")
def test_an_http_git_repo_is_refused(tmp_path: Path) -> None:
path = tmp_path / "m.yaml"
path.write_text(MANIFESTS.replace("https://github.com/example-org/alpha", "http://mirror.example/alpha"), encoding="utf-8")
loaded = load_manifests([path])
with pytest.raises(ValueError, match="http Git repos .*mirror.example.* cannot be reached"):
egress.policy_for(loaded.tasks["verify"], [loaded.workspaces["cases"]], None, STORE)
def test_every_committed_template_renders_the_expected_policy() -> None:
"""For each template under plane/tasks/: the store, the Model's hosts when bound, nothing else; one TLS rule."""
from openultrasast.plane.generate import TEMPLATES
plane = Path("plane")
loaded = load_manifests([plane / "tasks" / f"{name}.yaml" for name in TEMPLATES] + sorted((plane / "models").glob("*.yaml")))
assert set(loaded.tasks) == set(TEMPLATES)
for name, task in loaded.tasks.items():
model = loaded.models.get(task.metadata.annotations.get("openultrasast.io/model") or "")
policy = egress.policy_for(task, [], model, STORE)
expected = sorted({STORE, *(model.egress_hosts if model else ())})
assert policy == {"rules": [{"tlsPassthrough": {"hostnames": expected, "ports": {"numbers": [443]}}}]}, name
assert_tight(policy)
bound = {n for n, t in loaded.tasks.items() if "openultrasast.io/model" in t.metadata.annotations}
assert bound == {"verify", "roles"}, "the model-bound templates, and only they, get the provider host"
def test_deepseek_model_manifest_names_the_endpoint_host() -> None:
@@ -121,7 +158,7 @@ def calls(tmp_path: Path) -> list[str]:
return [" ".join(line.split()[2:4]) for line in (tmp_path / "ax.log").read_text().splitlines()]
POLICY = {"rules": [{"http": {"hostnames": [egress.RECEIVER_HOST], "ports": {"numbers": [80]}}}]}
POLICY = {"rules": [{"tlsPassthrough": {"hostnames": ["files.store.example"], "ports": {"numbers": [443]}}}]}
def test_apply_creates_reads_back_and_delete_confirms_the_cascade(tmp_path: Path, kubectl_ate: Path) -> None:
@@ -134,10 +171,11 @@ def test_apply_creates_reads_back_and_delete_confirms_the_cascade(tmp_path: Path
actor(tmp_path, "t1", gone=True) # ax delete task: the actor, and with it the policy, is gone
assert cli.delete("t1", "default") is None
assert calls(tmp_path) == ["get t1", "create t1", "get t1", "get t1", "get t1", "get t1"]
assert {line.split()[-1] for line in (tmp_path / "ax.log").read_text().splitlines()} == {"kind-test"}, "the given context"
def test_apply_replaces_an_existing_policy(tmp_path: Path, kubectl_ate: Path) -> None:
cli = egress.Egress(str(kubectl_ate))
cli = egress.Egress(str(kubectl_ate), context="kind-test")
actor(tmp_path, "t2")
cli.apply("t2", "default", {"rules": []})
cli.apply("t2", "default", POLICY)
@@ -146,7 +184,7 @@ def test_apply_replaces_an_existing_policy(tmp_path: Path, kubectl_ate: Path) ->
def test_apply_waits_for_the_actor_then_gives_up(tmp_path: Path, kubectl_ate: Path) -> None:
cli = egress.Egress(str(kubectl_ate))
cli = egress.Egress(str(kubectl_ate), context="kind-test")
with pytest.raises(RuntimeError, match="not found"):
cli.apply("missing", "default", POLICY, timeout=0.2)
timer = threading.Timer(0.3, actor, args=(tmp_path, "late"))
@@ -157,10 +195,13 @@ def test_apply_waits_for_the_actor_then_gives_up(tmp_path: Path, kubectl_ate: Pa
def test_receiver_address_modes(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OUSAST_ARTIFACT_HOST", "127.0.0.1")
assert egress.receiver_address(4242) == ("http://127.0.0.1:4242/", None)
assert egress.receiver_address(4242, None) == ("http://127.0.0.1:4242/", None)
monkeypatch.delenv("OUSAST_ARTIFACT_HOST")
monkeypatch.setenv("OUSAST_ARTIFACT_DIAL", "172.19.0.1:80")
assert egress.receiver_address(18090) == (f"http://{egress.RECEIVER_HOST}/", "172.19.0.1:80")
assert egress.receiver_address(18090, "kind-test") == (f"http://{egress.RECEIVER_HOST}/", "172.19.0.1:80")
monkeypatch.delenv("OUSAST_ARTIFACT_DIAL")
with pytest.raises(RuntimeError, match="kube context"):
egress.receiver_address(18090, None) # no context to look the Service up in: never a built one
# --- the runner dials the gateway address with the receiver's name as Host ----------------------------------------
+39
View File
@@ -290,3 +290,42 @@ def test_case_record_carries_sites_in_set_and_cost_and_the_image_is_repinned(tmp
(tmp_path / "runner-image").write_text("localhost:5001/ousast-runner:dev\n")
with pytest.raises(ValueError, match="digest-pinned"):
repin_templates(paths["plane"], tmp_path / "runner-image")
def test_repin_reads_the_profiles_images_file(tmp_path: Path) -> None:
"""plane-on-kubernetes 1.4: ``ops/ax/up.sh`` writes ``kind-images.json`` ({"runner": ...}); repin reads it, the
older one-line pin still reads, and a file without a runner key or without a digest is refused by name."""
paths = _fixture(tmp_path)
digest = "localhost:5001/ousast-runner@sha256:" + "cd" * 32
images = tmp_path / "kind-images.json"
images.write_text(json.dumps({"runner": digest}) + "\n")
assert repin_templates(paths["plane"], images) == digest
assert {t.image for t in load_manifests(sorted((paths["plane"] / "tasks").glob("*.yaml"))).tasks.values()} == {digest}
images.write_text(json.dumps({"engine": digest}))
with pytest.raises(ValueError, match="no runner image"):
repin_templates(paths["plane"], images)
images.write_text(json.dumps({"runner": "localhost:5001/ousast-runner:dev"}))
with pytest.raises(ValueError, match="not digest-pinned"):
repin_templates(paths["plane"], images)
def test_workspaces_go_to_the_profiles_atespace(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""plane-on-kubernetes 1.2: ``generate.ATESPACE`` is a default; ``increment`` takes the profile's atespace."""
paths = _fixture(tmp_path)
(tmp_path / "p.toml").write_text('exec = "local"\nkube_context = "c"\nregistry = "r"\nimages = "i.json"\natespace = "team-a"\n')
monkeypatch.setenv("OUSAST_PLANE_PROFILE", str(tmp_path / "p.toml"))
increment(paths["population"], paths["set"], paths["candidates"], paths["scans"], plane=paths["plane"], run_name="sp", command="t")
manifests = load_manifests(sorted((paths["plane"] / "workspaces").glob("*.yaml")))
assert manifests.workspaces and {w.metadata.atespace for w in manifests.workspaces.values()} == {"team-a"}
increment(
paths["population"],
paths["set"],
paths["candidates"],
paths["scans"],
plane=paths["plane"],
run_name="sp",
command="t",
atespace="z",
)
manifests = load_manifests(sorted((paths["plane"] / "workspaces").glob("*.yaml")))
assert {w.metadata.atespace for w in manifests.workspaces.values()} == {"z"}, "an explicit atespace wins over the profile"
+112
View File
@@ -0,0 +1,112 @@
"""plane-on-kubernetes Req 1.3: no module, manifest or page assumes this host.
The scan covers ``src/``, ``plane/``, ``ops/`` (minus ``ops/ax/up.sh``, which creates the kind cluster and may name
it, and ``ops/k8s/profiles/``, which *are* the configuration) and ``docs/`` for the kind context, the kind registry,
the kind network's gateway, a loopback address with a port, and ``port-forward``. Allowed: ``port-forward`` and the
loopback address inside ``router.py`` (the one module that opens the tunnel), and any line tagged ``ax-tunnel``
(the tunnel is how both profiles reach ax and the router) or ``loopback`` (a process talking to itself). A
digest-pinned ``image:`` line of a committed Task template under ``plane/tasks/`` is the kind profile's own value,
written there by ``ousast plane`` repinning from ``ops/k8s/profiles/kind-images.json``; it is accepted only while it
names that profile's registry, so the templates and the profile cannot drift apart. The patterns are built at runtime
so this file does not match them (as ``test_removed_plane_references.py`` does).
"""
from __future__ import annotations
import re
from pathlib import Path
from openultrasast.plane.profile import load_profile
ROOT = Path(__file__).resolve().parents[1]
SCANNED = ("src", "plane", "ops", "docs")
EXEMPT = (Path("ops/ax/up.sh"), Path("ops/k8s/profiles"))
ROUTER = Path("src/openultrasast/plane/router.py")
PATTERNS = {
"kind context": re.compile("kind-" + "ousast"),
"kind registry": re.compile("localhost:" + "5001"),
"kind gateway": re.compile(r"172\.19\."),
"loopback with port": re.compile(r"127\.0\.0\.1" + ":"),
"port-forward": re.compile("port-" + "forward"),
}
ROUTER_ONLY = {"loopback with port", "port-forward"}
TAGS = re.compile(r"ax-tunnel|loopback")
_IMAGE_LINE = re.compile(r'^\s*image:\s*"?(?P<ref>\S+?)@sha256:[0-9a-f]{64}"?\s*(#.*)?$')
def _files() -> list[Path]:
out = []
for top in SCANNED:
for path in sorted((ROOT / top).rglob("*")):
relative = path.relative_to(ROOT)
if not path.is_file() or "__pycache__" in relative.parts or path.suffix in (".png", ".jpg", ".gz", ".zip"):
continue
if any(relative == e or e in relative.parents for e in EXEMPT):
continue
out.append(path)
return out
def _pinned_template_image(relative: Path, line: str, registry: str) -> bool:
"""A committed template's digest-pinned image in the kind profile's registry: configuration, not a literal."""
if relative.parts[:2] != ("plane", "tasks"):
return False
found = _IMAGE_LINE.match(line)
return bool(found) and found.group("ref").startswith(registry + "/")
def offenders(paths: list[Path], registry: str, root: Path = ROOT) -> list[str]:
found = []
for path in paths:
relative = path.relative_to(root)
try:
text = path.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue
for number, line in enumerate(text.splitlines(), 1):
for label, pattern in PATTERNS.items():
if not pattern.search(line):
continue
if label in ROUTER_ONLY and relative == ROUTER:
continue
if TAGS.search(line) or _pinned_template_image(relative, line, registry):
continue
found.append(f"{relative}:{number}: [{label}] {line.strip()[:120]}")
return found
def test_no_host_literal_outside_the_profile_and_the_kind_bring_up() -> None:
kind = load_profile("kind", environ={})
found = offenders(_files(), kind.registry)
assert found == [], "host-only literals (Req 1.3):\n" + "\n".join(found)
def test_the_scan_sees_a_reinserted_literal(tmp_path: Path) -> None:
"""The instrument is not vacuous: each pattern is caught, a tag or the router exempts only what it says."""
kind = load_profile("kind", environ={})
src = tmp_path / "src"
src.mkdir()
(src / "a.py").write_text(f'ctx = "{"kind-" + "ousast"}"\nreg = "{"localhost:" + "5001"}"\nip = "172.19.0.1"\n', encoding="utf-8")
(src / "b.py").write_text('url = "http://127.0.0.1:8080" # loopback, this process\nargv = ["port-' + 'forward"] # ax-tunnel\n')
(src / "c.py").write_text('argv = ["port-' + 'forward"]\n', encoding="utf-8")
paths = sorted(src.glob("*.py"))
found = [o.split(": ", 1)[1] for o in offenders(paths, kind.registry, root=tmp_path)]
assert found == [
"[kind context] ctx = " + '"kind-' + 'ousast"',
"[kind registry] reg = " + '"localhost:' + '5001"',
'[kind gateway] ip = "172.19.0.1"',
"[port-forward] argv = " + '["port-' + 'forward"]',
]
def test_the_committed_templates_name_the_kind_profiles_registry() -> None:
"""The one exemption is checked, not assumed: every template image is in the kind registry, by digest."""
kind = load_profile("kind", environ={})
images = []
for path in sorted((ROOT / "plane/tasks").glob("*.yaml")):
for line in path.read_text(encoding="utf-8").splitlines():
found = _IMAGE_LINE.match(line)
if found:
images.append(found.group("ref"))
assert found.group("ref").startswith(kind.registry + "/"), f"{path.name}: {line.strip()}"
assert images, "the templates carry digest-pinned images"
+158
View File
@@ -0,0 +1,158 @@
"""plane-on-kubernetes task 1.1: the ``PlaneProfile`` (Req 1.1). Defaults, file-then-environment precedence, every
rejection naming its field, both committed profiles loading, and no committed value that looks like a credential."""
from __future__ import annotations
import json
import re
from pathlib import Path
import pytest
from openultrasast.plane import profile as profile_module
from openultrasast.plane.profile import PlaneProfile, Pool, ProfileError, load_images, load_profile, profiles_dir
ROOT = Path(__file__).resolve().parents[1]
DIGEST = "sha256:" + "ab" * 32
MINIMAL = 'exec = "local"\nkube_context = "kind-test"\nregistry = "registry.test:5000"\nimages = "images.json"\nmemory = "s3://m"\n'
def write_profile(tmp_path: Path, text: str = MINIMAL, name: str = "test") -> Path:
path = tmp_path / f"{name}.toml"
path.write_text(text, encoding="utf-8")
(tmp_path / "images.json").write_text(json.dumps({"runner": f"registry.test:5000/ousast-runner@{DIGEST}"}), encoding="utf-8")
return path
def test_defaults_and_the_images_path_resolve_against_the_profile_file(tmp_path: Path) -> None:
loaded = load_profile(str(write_profile(tmp_path)), environ={})
assert loaded.name == "test" and loaded.exec == "local" and loaded.kube_context == "kind-test"
assert loaded.images == tmp_path / "images.json" and loaded.load_images() == {"runner": f"registry.test:5000/ousast-runner@{DIGEST}"}
assert loaded.atespace == "default" and loaded.router_url == "" and loaded.pools == {}
assert "(tunnel through the kube context)" in loaded.addresses()["router_url"]
def test_the_environment_overrides_the_file_and_names_the_profile(tmp_path: Path) -> None:
path = write_profile(tmp_path, MINIMAL + '[pools.default]\nreplicas = 2\ncpu = "1"\nmemory = "1536Mi"\n')
env = {
"OUSAST_PLANE_PROFILE": str(path),
"OUSAST_KUBE_CONTEXT": "other-cluster",
"OUSAST_MEMORY": "s3://elsewhere/prefix",
"OUSAST_ROUTER_URL": "https://router.test",
"OUSAST_POOL_DEFAULT_REPLICAS": "5",
"OUSAST_POOL_DEFAULT_MEMORY": "2Gi",
"OUSAST_EXEC": "", # an empty variable is not an override
}
loaded = load_profile(environ=env)
assert (loaded.kube_context, loaded.memory, loaded.router_url, loaded.exec) == (
"other-cluster",
"s3://elsewhere/prefix",
"https://router.test",
"local",
)
assert loaded.pools == {"default": Pool(replicas=5, cpu="1", memory="2Gi")}
assert load_profile(str(path), environ={}).kube_context == "kind-test", "the file is unchanged"
@pytest.mark.parametrize(
("text", "field"),
[
(MINIMAL + 'colour = "blue"\n', "unknown keys: colour"),
(MINIMAL + 'aws_secret_key = "x"\n', "aws_secret_key looks like a credential"),
(MINIMAL + 'api_token = "x"\n', "api_token looks like a credential"),
(MINIMAL.replace('exec = "local"', 'exec = "cluster"'), "exec must be one of local, remote"),
(MINIMAL + '[pools.default]\nreplicas = "two"\ncpu = "1"\nmemory = "1Gi"\n', "pools.default.replicas must be an integer"),
(MINIMAL + '[pools.default]\nreplicas = 1\ncpu = "1"\n', "pools.default lacks memory"),
(MINIMAL + '[pools.default]\nreplicas = 1\ncpu = "1"\nmemory = "1Gi"\nnodes = 3\n', "pools.default has unknown keys: nodes"),
(MINIMAL.replace('kube_context = "kind-test"', "kube_context = 3"), "kube_context must be a string"),
],
)
def test_each_rejection_names_the_field(tmp_path: Path, text: str, field: str) -> None:
path = write_profile(tmp_path, text)
with pytest.raises(ProfileError, match=re.escape(field)) as exc:
load_profile(str(path), environ={})
assert str(path) in str(exc.value)
def test_a_credential_shaped_value_is_refused(tmp_path: Path) -> None:
path = write_profile(tmp_path, MINIMAL.replace("s3://m", "sk-" + "a1b2c3d4" * 3))
with pytest.raises(ProfileError, match="value of memory looks like a credential"):
load_profile(str(path), environ={})
def test_an_images_file_without_digest_pins_is_refused(tmp_path: Path) -> None:
path = write_profile(tmp_path)
(tmp_path / "images.json").write_text(json.dumps({"runner": "registry.test:5000/ousast-runner:dev"}), encoding="utf-8")
with pytest.raises(ProfileError, match="runner is not digest-pinned"):
load_profile(str(path), environ={})
(tmp_path / "images.json").write_text(f"registry.test:5000/ousast-runner@{DIGEST}\n", encoding="utf-8")
assert load_images(tmp_path / "images.json") == {"runner": f"registry.test:5000/ousast-runner@{DIGEST}"}, "the one-line form"
(tmp_path / "images.json").unlink()
loaded = load_profile(str(path), environ={}) # a file the release has not published yet: doctor's finding, not a load error
with pytest.raises(ProfileError, match="images file"):
loaded.load_images()
def test_an_environment_override_of_the_wrong_shape_is_refused(tmp_path: Path) -> None:
path = write_profile(tmp_path, MINIMAL + '[pools.default]\nreplicas = 1\ncpu = "1"\nmemory = "1Gi"\n')
with pytest.raises(ProfileError, match="OUSAST_POOL_DEFAULT_REPLICAS must be an integer"):
load_profile(str(path), environ={"OUSAST_POOL_DEFAULT_REPLICAS": "many"})
with pytest.raises(ProfileError, match="names a pool the profile does not declare"):
load_profile(str(path), environ={"OUSAST_POOL_ENGINE_REPLICAS": "1"})
with pytest.raises(ProfileError, match="exec must be one of"):
load_profile(str(path), environ={"OUSAST_EXEC": "both"})
def test_a_missing_profile_names_its_path(tmp_path: Path) -> None:
with pytest.raises(ProfileError, match="no-such.toml"):
load_profile(str(tmp_path / "no-such.toml"), environ={})
with pytest.raises(ProfileError, match="unknown"):
load_profile("unknown", environ={"OUSAST_PLANE_PROFILES": str(tmp_path)})
@pytest.mark.parametrize("name", ["kind", "k3s"])
def test_both_committed_profiles_load_with_the_designs_values(name: str) -> None:
loaded = load_profile(name, environ={})
assert loaded.path == profiles_dir() / f"{name}.toml" and profiles_dir() == ROOT / "ops/k8s/profiles"
assert loaded.exec == {"kind": "local", "k3s": "remote"}[name]
assert loaded.memory == "s3://sast-memory" and loaded.atespace == "default" and loaded.snapshots_bucket == "s3://ax-snapshots"
assert set(loaded.pools) == {"default", "engine"} and loaded.pools["engine"].memory == "4Gi"
assert loaded.router_url == "" and loaded.ax_server == "", "both profiles tunnel through the Kubernetes API"
if name == "kind":
assert loaded.image_pull_secret == "" and loaded.images.name == "kind-images.json" and loaded.images.is_file()
assert set(loaded.load_images()) == {"runner"} and loaded.load_images()["runner"].startswith(loaded.registry + "/")
else:
assert loaded.image_pull_secret == "ghcr-pull" and loaded.registry.startswith("ghcr.io/") and loaded.images.name == "images.json"
assert not loaded.images.is_file(), "the release asset is downloaded, never committed (design section 8)"
def test_no_committed_profile_value_matches_a_credential_pattern() -> None:
patterns = (
re.compile(r"(?i)(api_key|secret_key|access_key|token|password)\s*="), # image_pull_secret names a Secret, holds none
re.compile(r"sk-[A-Za-z0-9]{8,}"),
re.compile(r"AKIA[0-9A-Z]{12,}"),
)
for path in sorted(profiles_dir().glob("*.toml")):
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
code = line.split("#", 1)[0]
for pattern in patterns:
assert not pattern.search(code), f"{path}:{number}: {line.strip()}"
def test_print_for_shell_scripts(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
path = write_profile(tmp_path, MINIMAL + '[pools.default]\nreplicas = 3\ncpu = "1"\nmemory = "1Gi"\n')
fields = ["registry", "kube_context", "images.runner", "pools.default.replicas"]
assert profile_module.main(["--profile", str(path), "--print", *fields]) == 0
assert capsys.readouterr().out.splitlines() == ["registry.test:5000", "kind-test", f"registry.test:5000/ousast-runner@{DIGEST}", "3"]
assert profile_module.main(["--profile", str(path), "--print", "images"]) == 0, "the images file's path itself (up.sh writes it)"
assert capsys.readouterr().out.strip() == str(tmp_path / "images.json")
assert profile_module.main(["--profile", str(path), "--print", "images.engine"]) == 2
assert "no image named 'engine'" in capsys.readouterr().err
assert profile_module.main(["--profile", str(path), "--print", "nothing"]) == 2
def test_profile_is_frozen_and_typed() -> None:
loaded = load_profile("kind", environ={})
assert isinstance(loaded, PlaneProfile)
with pytest.raises(AttributeError):
loaded.exec = "remote" # type: ignore[misc]
+176 -27
View File
@@ -4,9 +4,10 @@ The fake is a Python script the tests write into ``tmp_path`` that follows ax's
internal/controller/reconciler.go, internal/server/server.go): ``apply`` creates the task ``Suspended``; ``resume``
fails with DeadlineExceeded the first time (ax then reports ``Failed``, as while Agent Substrate builds a new
image's golden snapshot) and succeeds after; ``get task`` answers ``Running`` forever -- ax has no Completed phase
-- and on the scripted poll it POSTs a scripted tar to the ``OUSAST_ARTIFACT_URL`` in the applied Task's env, or
turns ``Failed`` with a condition message, or vanishes. Every outcome comes from a scripted delivery, and a
delivery happens only after the reconciler's start request reached the fake router (Req 4.5).
-- and on the scripted poll it PUTs a scripted tar to the presigned ``delivery.put`` URL the start request carried
(plane-on-kubernetes 2.4; or POSTs it to ``OUSAST_ARTIFACT_URL`` in receiver mode), or turns ``Failed`` with a
condition message, or vanishes. Every outcome comes from a scripted delivery, and a delivery happens only after
the reconciler's start request reached the fake router (Req 4.5). The store is ``plane_fake_store.HttpObjectStore``.
"""
from __future__ import annotations
@@ -28,11 +29,13 @@ from pathlib import Path
import pytest
import yaml
from plane_fake_store import HttpObjectStore
from openultrasast.plane import reconciler
from openultrasast.plane.workspaces import workspaces
RECONCILER = Path("src/openultrasast/plane/reconciler.py")
PROFILE_CONTEXT = "kind-test" # conftest's plane_profile fixture: the context every fake must be handed
PIN = "0123456789abcdef0123456789abcdef01234567"
SECRET = "sk-reconciler-test-7a2b-only-in-the-start-request"
@@ -145,11 +148,15 @@ elif argv[:2] == ["get", "task"]:
started = (HERE / "started" / argv[2]).exists() # the runner runs its command only after the start request
if rec["phase"] == "Running" and rec["polls"] >= script.get("polls", 1) and started and not rec.get("sent"):
rec["sent"] = True
body = json.loads((HERE / "started" / argv[2]).read_text() or "{{}}") # the start body the router saw
delivery = body.get("delivery") or {{}}
if script.get("deliver", True) and "phase" not in script and not script.get("vanish"):
files = script.get("files", {{}})
base = env["OUSAST_ARTIFACT_URL"].rstrip("/") + "/inputs/"
base = env.get("OUSAST_ARTIFACT_URL", "").rstrip("/") + "/inputs/"
for name, ref in json.loads(env.get("OUSAST_INPUTS", "{{}}")).items():
fetch = urllib.request.Request(base + ref, headers={{"X-Ousast-Run": env["OUSAST_RUN"], "X-Ousast-Task": task}})
presigned = (delivery.get("inputs") or {{}}).get(name)
fetch = urllib.request.Request(presigned) if presigned else urllib.request.Request(
base + ref, headers={{"X-Ousast-Run": env["OUSAST_RUN"], "X-Ousast-Task": task}})
try:
with urllib.request.urlopen(fetch) as resp:
dest = HERE / "fetched" / task / ref
@@ -170,7 +177,11 @@ elif argv[:2] == ["get", "task"]:
tar.addfile(info, io.BytesIO(data))
headers = {{"Content-Type": "application/x-tar", "X-Ousast-Run": env["OUSAST_RUN"]}}
headers["X-Ousast-Task"] = script.get("as_task", task)
req = urllib.request.Request(env["OUSAST_ARTIFACT_URL"], data=buf.getvalue(), method="POST", headers=headers)
if delivery.get("put"): # the store path: PUT to the presigned URL, no receiver headers
tar_headers = {{"Content-Type": "application/x-tar"}}
req = urllib.request.Request(delivery["put"], data=buf.getvalue(), method="PUT", headers=tar_headers)
else:
req = urllib.request.Request(env["OUSAST_ARTIFACT_URL"], data=buf.getvalue(), method="POST", headers=headers)
try:
with urllib.request.urlopen(req) as resp:
(HERE / "ax.log").open("a").write(f"{{time.monotonic():.4f}} delivered {{task}} {{resp.status}}\\n")
@@ -282,6 +293,7 @@ SCRIPT = {
class Fake:
router: FakeRouter
store: HttpObjectStore
def __init__(self, root: Path) -> None:
self.root = root
@@ -342,7 +354,8 @@ class _RouterHandler(BaseHTTPRequestHandler):
self.server.starts.append({"at": time.monotonic(), "path": self.path, "target": target, "body": body, "code": code})
if code == 202:
(self.server.root / "started").mkdir(exist_ok=True)
(self.server.root / "started" / name).touch()
without_secrets = {k: v for k, v in body.items() if k != "credentials"} # the fake ax reads delivery from it
(self.server.root / "started" / name).write_text(json.dumps(without_secrets))
with (self.server.root / "ax.log").open("a") as log:
log.write(f"{time.monotonic():.4f} started {name} {code}\n")
self.send_response(code)
@@ -355,7 +368,7 @@ class _RouterHandler(BaseHTTPRequestHandler):
@pytest.fixture
def fake(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, kubectl_ate: Path) -> Iterator[Fake]:
def fake(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, kubectl_ate: Path, plane_profile: Path) -> Iterator[Fake]:
monkeypatch.setenv("OUSAST_RESULTS", str(tmp_path / "results"))
monkeypatch.setenv("OUSAST_ARTIFACT_HOST", "127.0.0.1")
monkeypatch.setenv("OUSAST_POLL_SECONDS", "0.02")
@@ -365,12 +378,15 @@ def fake(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, kubectl_ate: Path) ->
fake = Fake(tmp_path)
fake.script(SCRIPT)
fake.router = FakeRouter(tmp_path)
fake.store = HttpObjectStore() # the memory store the reconciler opens first: S3-shaped, presigned URLs served locally
monkeypatch.setattr(reconciler, "open_store", lambda spec=None, environ=None: fake.store)
monkeypatch.setenv("OUSAST_ROUTER_URL", f"http://127.0.0.1:{fake.router.server_address[1]}")
try:
yield fake
finally:
fake.router.shutdown()
fake.router.server_close()
fake.store.close()
def write_run(tmp_path: Path, name: str, tasks: str = CHAIN) -> Path:
@@ -404,13 +420,17 @@ def test_chain_runs_in_order_with_inputs_env_and_model(fake: Fake, tmp_path: Pat
assert env["OUSAST_BUDGET_USD"] == "2.5" and env["OUSAST_BUDGET_CALLS"] == "40"
assert env["OUSAST_INPUT_FACTS"] == "/workspace/.ousast-in/verify/repo-facts/facts.json"
assert json.loads(env["OUSAST_INPUTS"]) == {"FACTS": "repo-facts/facts.json"}
assert env["OUSAST_ARTIFACT_URL"].startswith("http://127.0.0.1:")
assert "OUSAST_ARTIFACT_URL" not in env, "the store path renders no receiver address into the Task"
assert env["OUSAST_MODEL"] == "deepseek-flash"
assert json.loads(env["OUSAST_MODEL_PARAMS"]) == {"cache_hit_per_m": 0.07, "input_per_m": 0.27, "output_per_m": 1.1}
kinds = [(d["kind"], d["metadata"]["name"]) for d in verify["docs"]]
assert kinds == [("Model", "deepseek-flash"), ("Workspace", "case-pin"), ("Task", "chain-verify")], "no inputs Workspace"
assert verify["docs"][-1]["spec"]["workspaces"] == [{"name": "case-pin", "path": "/workspace"}]
assert [n for _, n in fake.events("fetched")] == ["verify FACTS 200", "agree PASS 200"], "inputs come over the receiver's GET"
assert [n for _, n in fake.events("fetched")] == ["verify FACTS 200", "agree PASS 200"], "inputs come by their presigned GET"
assert ("GET", "runs/chain/repo-facts/facts.json") in fake.store.requests and (
"PUT",
"runs/chain/verify/output.tar",
) in fake.store.requests
assert json.loads((tmp_path / "fetched" / "verify" / "repo-facts" / "facts.json").read_text()) == {"a.py": {"functions": ["f"]}}
assert json.loads((tmp_path / "fetched" / "agree" / "verify" / "agreed.json").read_text()) == {"agreed": ["a.py:f"]}
assert "annotations" not in verify["docs"][-1]["metadata"], "ax's ObjectMeta has no annotations"
@@ -569,7 +589,15 @@ def test_start_is_sent_once_after_resume_with_the_models_credential(fake: Fake,
assert [s["target"] for s in starts] == ["default/sig-repo-facts", "default/sig-verify", "default/sig-agree"], "once each"
assert all(s["path"] == "/ousast/v1/start" and s["code"] == 202 for s in starts)
by_task = {s["body"]["task"]: s for s in starts}
assert by_task["sig-verify"]["body"] == {"run": "sig", "task": "sig-verify", "credentials": {"DEEPSEEK_API_KEY": SECRET}}
body = by_task["sig-verify"]["body"]
assert {k: body[k] for k in ("run", "task", "credentials")} == {
"run": "sig",
"task": "sig-verify",
"credentials": {"DEEPSEEK_API_KEY": SECRET},
}
assert set(body["delivery"]) == {"put", "inputs"} and "/runs/sig/verify/output.tar?" in body["delivery"]["put"]
assert set(body["delivery"]["inputs"]) == {"FACTS"} and "/runs/sig/repo-facts/facts.json?" in body["delivery"]["inputs"]["FACTS"]
assert all("X-Amz" not in line for line in (tmp_path / "ax.log").read_text().splitlines()), "no presigned URL in the fake's log"
assert by_task["sig-repo-facts"]["body"]["credentials"] == {} and by_task["sig-agree"]["body"]["credentials"] == {}, "no Model"
accepted = {}
for stamp, words in fake.log(): # the last resume of each task is the one ax accepted
@@ -583,8 +611,10 @@ def test_start_is_sent_once_after_resume_with_the_models_credential(fake: Fake,
assert verify_model["spec"]["secretKey"] == {"name": "deepseek", "key": "DEEPSEEK_API_KEY"}, "the manifest names the key"
assert "DEEPSEEK_API_KEY" not in fake.applied("sig", "verify")["env"], "the rendered Task carries no credential"
rendered = str(reconciler.run_dir("sig") / "verify" / "task.yaml")
assert rendered in files_containing(tmp_path, "OUSAST_ARTIFACT_URL"), "the scan reads the rendered files it vouches for"
assert rendered in files_containing(tmp_path, "OUSAST_INPUTS"), "the scan reads the rendered files it vouches for"
assert files_containing(tmp_path, SECRET) == [], "no rendered task.yaml, state, log or applied record holds the value"
leaked = [p for p in files_containing(tmp_path, "X-Amz-Signature") if "/started/" not in p] # the fake router's own records
assert leaked == [], "nor a presigned URL in any product file (the fake store signs with that name)"
def test_a_model_bound_task_without_its_secret_fails_before_ax_sees_it(fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -631,7 +661,8 @@ def tar_of(files: dict[str, bytes]) -> bytes:
return buf.getvalue()
def test_receiver_rejects_traversal_unknown_tasks_and_wrong_types(tmp_path: Path) -> None:
def test_receiver_rejects_traversal_unknown_tasks_and_wrong_types(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OUSAST_ARTIFACT_HOST", "127.0.0.1") # the direct test address: no Service lookup, no context needed
receiver = reconciler.Receiver("r", tmp_path / "r", 0)
receiver.running.add("t")
headers = {"X-Ousast-Run": "r", "X-Ousast-Task": "t", "Content-Type": "application/x-tar"}
@@ -647,6 +678,7 @@ def test_receiver_rejects_traversal_unknown_tasks_and_wrong_types(tmp_path: Path
def test_delivery_for_a_task_that_is_not_running_is_rejected(fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OUSAST_DELIVERY", "receiver") # the receiver path, kept for group 2 (task 2.6 removes this test)
monkeypatch.setenv("OUSAST_TASK_TIMEOUT", "1") # the rejected delivery is no delivery: the task times out
script = json.loads(json.dumps(SCRIPT))
script["repo-facts"]["as_task"] = "agree" # the runner claims to be a task that has not started
@@ -719,16 +751,31 @@ def test_status_of_an_unknown_run_says_so(fake: Fake) -> None:
# --- doctor, workspaces, size ---------------------------------------------------------------------------------------
def test_doctor_names_the_bring_up_script_when_nothing_is_reachable(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.setenv("PATH", str(tmp_path)) # no kubectl, no curl
def test_doctor_checks_the_profiles_cluster_and_reports_every_address(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, plane_profile: Path
) -> None:
"""Req 1.2: the configured context (never kind by name), the registry's runner reference, the memory store; the
namespaces are not checked behind an unreachable context, so only that line fails for it."""
monkeypatch.setenv("PATH", str(tmp_path)) # no kubectl, no docker
from openultrasast.plane import doctor as doctor_module
monkeypatch.setattr(doctor_module, "_registry_has", lambda image: (False, f"registry.test:5000 unreachable for {image}"))
checks = reconciler.doctor()
assert [name for name, _, _ in checks] == [
"kind cluster",
"agent substrate (ate-system)",
"ax controller (ax-system)",
"runner image in kind registry",
names = [name for name, _, _ in checks]
assert names == [
"profile test",
"cluster kind-test",
"runner image in registry.test:5000",
f"memory store file://{tmp_path / 'memory'}",
]
assert all(not ok and "ops/ax/up.sh" in text for _, ok, text in checks)
by_name = {name: (ok, text) for name, ok, text in checks}
assert by_name["profile test"][0] and "kube_context=kind-test" in by_name["profile test"][1]
for address in ("registry=registry.test:5000", "exec=local", "atespace=default", "router_url=(tunnel"):
assert address in by_name["profile test"][1], address
assert not by_name["cluster kind-test"][0] and "kind-test unreachable" in by_name["cluster kind-test"][1]
assert not by_name["runner image in registry.test:5000"][0]
assert by_name[f"memory store file://{tmp_path / 'memory'}"][0], "a file store opens; an S3 store is verified"
assert not any("kind-" + "ousast" in text for _, _, text in checks), "nothing names the kind cluster"
def test_workspaces_one_manifest_per_case_pin(tmp_path: Path) -> None:
@@ -755,6 +802,23 @@ def test_workspaces_one_manifest_per_case_pin(tmp_path: Path) -> None:
assert loaded["demo-sqli-benign-tip"].pins == {"repo": "d" * 40}
def test_cli_doctor_takes_a_profile_and_fails_on_its_unreachable_context(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, plane_profile: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""plane-on-kubernetes 1.2: ``ousast plane doctor --profile`` drives the checks from that file, nothing built."""
from openultrasast.cli import main
from openultrasast.plane import doctor as doctor_module
monkeypatch.setenv("PATH", str(tmp_path))
monkeypatch.setattr(doctor_module, "_registry_has", lambda image: (True, "present"))
monkeypatch.delenv("OUSAST_PLANE_PROFILE")
assert main(["plane", "doctor", "--profile", str(plane_profile)]) == 1
out = capsys.readouterr().out
assert "[FAIL] cluster kind-test:" in out and "[ok] profile test:" in out and "[ok] runner image in registry.test:5000" in out
assert main(["plane", "doctor", "--profile", str(tmp_path / "missing.toml")]) == 2
assert "missing.toml" in capsys.readouterr().err
def test_rendered_documents_carry_only_ax_fields(tmp_path: Path) -> None:
"""Req 1.1/1.4: whatever ``ax apply`` receives names only fields ax documents; annotations never leave."""
(tmp_path / "repo-facts").mkdir()
@@ -798,9 +862,13 @@ def test_mark_done_sets_the_state_under_the_run_lock(tmp_path: Path) -> None:
assert not (base / "lock").exists(), "the lock is released"
def test_reconciler_is_under_500_lines_and_holds_no_pipeline_logic() -> None:
def test_reconciler_is_under_550_lines_and_holds_no_pipeline_logic() -> None:
text = RECONCILER.read_text(encoding="utf-8")
assert len(text.splitlines()) < 500, "Req 3.4: the reconciler stays under 500 lines"
assert len(text.splitlines()) < 550, (
"Req 3.4: the reconciler stays under 500 lines; plane-on-kubernetes 1.6/2.1 moved the bound to 550 while the "
"store-delivery wiring (Delivery, state mirror and seed, the start-body delivery) sits beside the receiver path "
"that task 2.6 removes -- 2.6 records the count and returns the bound to 500 if it allows"
)
for word in ("prompt(", "openai", "ChatClient", "score(", "system_prompt"):
assert word not in text, f"Req 3.4: no pipeline logic in the reconciler ({word})"
assert "Popen" not in text and "openultrasast.plane.tasks" not in text, "ax is the only executor"
@@ -829,7 +897,7 @@ def test_doctor_treats_an_empty_namespace_as_not_ready(monkeypatch: pytest.Monke
from openultrasast.plane import doctor
monkeypatch.setattr(doctor, "_sh", lambda *a: (True, "No resources found in ax-system namespace."))
ok, text = doctor._pods_ready("ax-system", "kind-ousast")
ok, text = doctor._pods_ready("ax-system", "kind-test")
assert not ok and "no pods" in text
monkeypatch.setattr(doctor, "_sh", lambda *a: (True, "a-1 1/1 Running 0 1m\nb-2 0/1 Completed 0 1m"))
assert doctor._pods_ready("ns", "ctx") == (True, "2 pods ready")
@@ -854,11 +922,13 @@ def test_egress_policy_between_apply_and_resume_and_checked_after_delete(fake: F
order.append(first(prefix, order[-1] + 1))
assert order == sorted(order)
assert not (tmp_path / "policies" / "default_eg-verify.json").exists(), "the policy went with its actor"
contexts = {line.split()[-1] for line in lines if line.startswith("egress ")}
assert contexts == {PROFILE_CONTEXT}, "kubectl-ate received the profile's context, never a built one (1.2)"
written = json.loads((tmp_path / "policies" / "default_eg-verify.written.json").read_text())
tls = {"hostnames": ["api.deepseek.com", "example.invalid"], "ports": {"numbers": [443]}}
assert written["rules"] == [{"tlsPassthrough": tls}], "the Model host and the Git host, nothing else"
tls = {"hostnames": ["api.deepseek.com", "example.invalid", "localhost"], "ports": {"numbers": [443]}}
assert written["rules"] == [{"tlsPassthrough": tls}], "the Model host, the Git host and the store host, nothing else"
facts = json.loads((tmp_path / "policies" / "default_eg-repo-facts.written.json").read_text())
assert facts["rules"] == [{"tlsPassthrough": {"hostnames": ["example.invalid"], "ports": {"numbers": [443]}}}]
assert facts["rules"] == [{"tlsPassthrough": {"hostnames": ["example.invalid", "localhost"], "ports": {"numbers": [443]}}}]
def test_a_refused_egress_policy_fails_the_task_before_resume(fake: Fake, tmp_path: Path, kubectl_ate: Path) -> None:
@@ -943,3 +1013,82 @@ def test_ax_task_names_leave_room_for_the_template_suffix() -> None:
assert other != long, "no collision between tasks sharing a long prefix"
assert reconciler._ax_name("run", "facts", limit=reconciler.TASK_NAME_LIMIT) == "run-facts"
assert re.fullmatch(r"[a-z0-9]([a-z0-9-]*[a-z0-9])?", long)
# --- the store is the state of record (plane-on-kubernetes 2.1, design section 2) -----------------------------------
def test_state_is_mirrored_to_the_store_and_seeds_a_rerun_from_an_empty_results_root(
fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
assert reconciler.run(write_run(tmp_path, "mir"), ax=str(fake.ax)) == "done"
mirrored = json.loads(fake.store.objects["runs/mir/state.json"].decode())
assert mirrored == state_of("mir") and mirrored["status"] == "done", "mirrored after the last change"
applies = len(fake.events("apply"))
monkeypatch.setenv("OUSAST_RESULTS", str(tmp_path / "elsewhere")) # another machine: no local state at all
assert reconciler.run(write_run(tmp_path, "mir"), ax=str(fake.ax)) == "done"
assert statuses("mir") == {"repo-facts": "done", "verify": "done", "agree": "done"}, "seeded from the store"
assert len(fake.events("apply")) == applies, "nothing was submitted again"
def test_a_store_that_cannot_open_stops_the_run_before_ax_and_leaves_no_lock(
fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from openultrasast.plane import memory
from openultrasast.plane.memory import MemoryStoreError
monkeypatch.setattr(reconciler, "open_store", memory.open_store) # the real one, not the fixture's fake
monkeypatch.setenv("OUSAST_MEMORY", "s3://nowhere") # the profile's override; no S3_* in the test environment
for key in ("S3_ENDPOINT", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"):
monkeypatch.delenv(key, raising=False)
with pytest.raises(MemoryStoreError, match="S3_ENDPOINT"):
reconciler.run(write_run(tmp_path, "nostore"), ax=str(fake.ax))
assert fake.events("apply") == [] and not (reconciler.run_dir("nostore") / "lock").exists()
# --- the store path end to end, and the receiver path kept for group 2 (plane-on-kubernetes 2.4) --------------------
def test_the_store_holds_the_tar_the_declared_outputs_and_done_json(fake: Fake, tmp_path: Path) -> None:
"""Completion is the tar in the store: downloaded, extracted under the run dir, declared outputs re-put as single
objects (the consumer's GET names one artifact), done.json with the summary's status and the tar's size."""
assert reconciler.run(write_run(tmp_path, "st"), ax=str(fake.ax)) == "done"
keys = sorted(k for k in fake.store.objects if k.startswith("runs/st/"))
assert keys == [
"runs/st/agree/done.json",
"runs/st/agree/output.tar",
"runs/st/repo-facts/done.json",
"runs/st/repo-facts/facts.json",
"runs/st/repo-facts/output.tar",
"runs/st/state.json",
"runs/st/verify/agreed.json",
"runs/st/verify/done.json",
"runs/st/verify/output.tar",
], "declared outputs only (summary.json is extracted locally, not declared by this Run)"
done = json.loads(fake.store.objects["runs/st/repo-facts/done.json"])
assert done["status"] == "done" and done["bytes"] == len(fake.store.objects["runs/st/repo-facts/output.tar"]) and done["files"] == 2
assert json.loads((reconciler.run_dir("st") / "repo-facts" / "facts.json").read_text()) == {"a.py": {"functions": ["f"]}}
assert fake.store.objects["runs/st/repo-facts/facts.json"] == (reconciler.run_dir("st") / "repo-facts" / "facts.json").read_bytes()
def test_a_tar_the_store_path_cannot_accept_fails_the_task(fake: Fake, tmp_path: Path) -> None:
script = json.loads(json.dumps(SCRIPT))
script["repo-facts"]["files"] = {"../escape.json": "{}", "summary.json": FACTS_SUMMARY}
fake.script(script)
assert reconciler.run(write_run(tmp_path, "esc"), ax=str(fake.ax)) == "failed"
assert "rejected member ../escape.json" in state_of("esc")["tasks"]["repo-facts"]["reason"]
assert not (reconciler.run_dir("esc").parent / "escape.json").exists()
def test_receiver_mode_still_runs_through_the_host_receiver(fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""OUSAST_DELIVERY=receiver (group 2 only): the old path, with its http rule and OUSAST_ARTIFACT_URL, until 2.6."""
monkeypatch.setenv("OUSAST_DELIVERY", "receiver")
assert reconciler.run(write_run(tmp_path, "rcv"), ax=str(fake.ax)) == "done"
assert fake.applied("rcv", "verify")["env"]["OUSAST_ARTIFACT_URL"].startswith("http://127.0.0.1:")
assert [s["body"].get("delivery") for s in fake.router.starts] == [None, None, None], "no presigned URLs in receiver mode"
assert not any(k.startswith("runs/rcv/") and k.endswith("output.tar") for k in fake.store.objects), "nothing PUT to the store"
assert "runs/rcv/state.json" in fake.store.objects, "the state is still mirrored"
written = json.loads((tmp_path / "policies" / "default_rcv-repo-facts.written.json").read_text())
assert written["rules"] == [{"tlsPassthrough": {"hostnames": ["example.invalid"], "ports": {"numbers": [443]}}}], (
"an IP receiver: no http rule"
)
+17 -9
View File
@@ -93,17 +93,17 @@ def alive(pid: int) -> bool:
@pytest.fixture(autouse=True)
def _clean_env(monkeypatch: pytest.MonkeyPatch) -> None:
for key in ("OUSAST_ROUTER_URL", "OUSAST_START_TIMEOUT", "KIND_CLUSTER_NAME", "FAKE_KUBECTL_MODE"):
for key in ("OUSAST_ROUTER_URL", "OUSAST_START_TIMEOUT", "FAKE_KUBECTL_MODE"):
monkeypatch.delenv(key, raising=False)
def test_port_forward_port_is_parsed_and_the_child_is_terminated_on_exit(tmp_path: Path, fake_router: FakeRouter) -> None:
port = int(fake_router.server_address[1])
kubectl = write_kubectl(tmp_path, port)
with open_router(kubectl=str(kubectl)) as router:
with open_router("kind-test", kubectl=str(kubectl)) as router: # the profile's context, as the reconciler passes it
assert router.url == f"http://127.0.0.1:{port}"
seen = json.loads((tmp_path / "kubectl.json").read_text())
assert seen["argv"] == ["--context", "kind-ousast", "-n", "ate-system", "port-forward", "svc/atenet-router", ":80"]
assert seen["argv"] == ["--context", "kind-test", "-n", "ate-system", "port-forward", "svc/atenet-router", ":80"]
assert alive(seen["pid"])
router.start_task("default", "run-t", "run", {})
deadline = time.monotonic() + 5
@@ -123,15 +123,23 @@ def test_a_failing_port_forward_raises_with_kubectls_output(tmp_path: Path, monk
def test_router_url_override_starts_no_port_forward(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, fake_router: FakeRouter) -> None:
monkeypatch.setenv("OUSAST_ROUTER_URL", fake_router.url)
with open_router(kubectl=str(tmp_path / "no-such-kubectl")) as router:
with open_router("kind-test", kubectl=str(tmp_path / "no-such-kubectl")) as router:
assert router.url == fake_router.url
assert not (tmp_path / "kubectl.json").exists()
monkeypatch.delenv("OUSAST_ROUTER_URL")
with open_router("kind-test", url=fake_router.url, kubectl=str(tmp_path / "no-such-kubectl")) as router:
assert router.url == fake_router.url, "the profile's router_url is the same override"
def test_open_router_refuses_to_build_a_context(tmp_path: Path) -> None:
with pytest.raises(StartError, match="kube context"), open_router("", kubectl=str(tmp_path / "no-such-kubectl")):
pass
@pytest.mark.parametrize("fake_router", [[(503, "no actor yet"), (502, "bad gateway"), (202, "started")]], indirect=True)
def test_start_retries_503_and_502_then_succeeds(monkeypatch: pytest.MonkeyPatch, fake_router: FakeRouter) -> None:
monkeypatch.setenv("OUSAST_ROUTER_URL", fake_router.url)
with open_router() as router:
with open_router("kind-test") as router:
router.start_task("default", "chain-verify", "chain", {"DEEPSEEK_API_KEY": SECRET})
assert len(fake_router.requests) == 3
path, headers, body = fake_router.requests[-1]
@@ -142,7 +150,7 @@ def test_start_retries_503_and_502_then_succeeds(monkeypatch: pytest.MonkeyPatch
@pytest.mark.parametrize("fake_router", [[(409, f"this actor runs task 'x' (echo {SECRET})")]], indirect=True)
def test_a_409_is_raised_at_once_with_the_response_text_redacted(monkeypatch: pytest.MonkeyPatch, fake_router: FakeRouter) -> None:
monkeypatch.setenv("OUSAST_ROUTER_URL", fake_router.url)
with open_router() as router, pytest.raises(StartError, match="HTTP 409.*runs task 'x'") as caught:
with open_router("kind-test") as router, pytest.raises(StartError, match="HTTP 409.*runs task 'x'") as caught:
router.start_task("default", "t", "r", {"DEEPSEEK_API_KEY": SECRET})
assert len(fake_router.requests) == 1 and SECRET not in str(caught.value)
assert router.starter("default", "t", "r", {})() is not None, "the starter returns the refusal instead of raising"
@@ -153,7 +161,7 @@ def test_a_restart_after_re_resume_accepts_a_running_command(monkeypatch: pytest
"""First start: 'already started' is a refusal. After a re-resume the actor may not have rebooted; then the
runner's 'already started on this boot' means the command still runs, not a failure."""
monkeypatch.setenv("OUSAST_ROUTER_URL", fake_router.url)
with open_router() as router:
with open_router("kind-test") as router:
start = router.starter("default", "t", "r", {})
assert start() is not None, "a first start that finds the command running is a refusal"
assert start() is None, "a re-sent start after a re-resume is not"
@@ -165,7 +173,7 @@ def test_a_restart_after_re_resume_accepts_a_running_command(monkeypatch: pytest
def test_start_gives_up_after_the_start_timeout(monkeypatch: pytest.MonkeyPatch, fake_router: FakeRouter) -> None:
monkeypatch.setenv("OUSAST_ROUTER_URL", fake_router.url)
monkeypatch.setenv("OUSAST_START_TIMEOUT", "1")
with open_router() as router, pytest.raises(StartError, match="OUSAST_START_TIMEOUT.*workspaces not materialised"):
with open_router("kind-test") as router, pytest.raises(StartError, match="OUSAST_START_TIMEOUT.*workspaces not materialised"):
router.start_task("default", "t", "r", {})
assert len(fake_router.requests) >= 2
@@ -173,7 +181,7 @@ def test_start_gives_up_after_the_start_timeout(monkeypatch: pytest.MonkeyPatch,
def test_connection_errors_are_retried_until_the_timeout(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OUSAST_ROUTER_URL", "http://127.0.0.1:9")
monkeypatch.setenv("OUSAST_START_TIMEOUT", "1")
with open_router() as router, pytest.raises(StartError, match="within 1s"):
with open_router("kind-test") as router, pytest.raises(StartError, match="within 1s"):
router.start_task("default", "t", "r", {})
+120
View File
@@ -23,6 +23,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import pytest
from plane_fake_store import HttpObjectStore
from openultrasast.plane import egress, runner
from openultrasast.plane.manifests import GitSource
@@ -36,6 +37,7 @@ from openultrasast.plane.runner import (
clone_destination,
deliver,
find_cached_checkout,
parse_delivery,
start_health_server,
)
@@ -850,3 +852,121 @@ def test_fetch_dials_the_override_and_sends_the_url_host(tmp_path: Path) -> None
server.server_close()
assert size == 2 and (tmp_path / "x" / "a.json").read_bytes() == b"ok"
assert seen[0]["path"] == "/inputs/p/a.json" and seen[0]["Host"] == "receiver.example" and seen[0]["X-Ousast-Run"] == "r"
# --- store delivery through the start body (plane-on-kubernetes 2.2, design section 3) ---------------------------
@pytest.fixture
def object_store() -> Iterator[HttpObjectStore]:
fake = HttpObjectStore()
try:
yield fake
finally:
fake.close()
def test_the_start_body_may_carry_delivery_targets_and_refuses_unsafe_urls() -> None:
"""``delivery {put, inputs}``: https to a hostname, never an address or http; the answer echoes no URL."""
gate = StartGate()
gate.arm("r", "t")
bad = [
({"put": "http://store.example/b/k?sig=1"}, "delivery.put must be an https URL"),
({"put": "https://10.0.0.7/b/k"}, "delivery.put must name a host, not an address"),
({"put": "https://user:[email protected]/k"}, "delivery.put must be an https URL"),
({"inputs": {"FACTS": "ftp://store.example/k"}}, "delivery.inputs.FACTS must be an https URL"),
({"inputs": {"lower": "https://store.example/k"}}, "delivery.inputs must map"),
({"elsewhere": "x"}, "delivery must be an object"),
("https://store.example/k", "delivery must be an object"),
]
for delivery, message in bad:
status, text = gate.offer(json.dumps({"run": "r", "task": "t", "delivery": delivery}).encode())
assert status == 400 and message in text, (delivery, status, text)
assert "store.example" not in text and "10.0.0.7" not in text, "the answer never echoes a URL"
body = {
"run": "r",
"task": "t",
"delivery": {
"put": "https://store.example/b/t/output.tar?X-Amz-Signature=s",
"inputs": {"FACTS": "https://store.example/b/p/facts.json?X-Amz-Signature=s"},
},
}
assert gate.offer(json.dumps(body).encode()) == (202, "started")
start = gate.wait(threading.Event())
assert start is not None and start.delivery is not None
assert start.delivery.put == body["delivery"]["put"] and start.delivery.inputs == body["delivery"]["inputs"]
assert "X-Amz" not in repr(start) and "store.example" not in repr(start), "a Start never prints a URL"
assert parse_delivery(None) is None and parse_delivery({"put": "http://localhost:1/k"}, insecure=True) is not None
with pytest.raises(ValueError, match="must be an https URL"):
parse_delivery({"put": "http://localhost:1/k"})
def test_output_is_put_to_the_presigned_url_and_inputs_are_fetched_from_theirs(
env: dict[str, str], object_store: HttpObjectStore, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""End to end against the S3-shaped fake: the start carries the URLs, the runner PUTs the tar and GETs the input;
no receiver, no OUSAST_ARTIFACT_URL, no URL in any log line."""
caplog.set_level(logging.DEBUG)
object_store._put("runs/run-1/repo-facts/facts.json", b'{"a.py": ["f"]}')
put_url = object_store.presign_put("runs/run-1/stub/output.tar")
facts_url = object_store.presign_get("runs/run-1/repo-facts/facts.json")
thread, environ, codes = main_in_thread(
tmp_path,
AX_RUNNER_INSECURE_DELIVERY="1",
OUSAST_TASK="stub",
OUSAST_INPUTS=json.dumps({"FACTS": "repo-facts/facts.json"}),
OUSAST_INPUT_FACTS=str(tmp_path / "ws" / "in" / "repo-facts" / "facts.json"),
)
body = {"run": "run-1", "task": "stub", "delivery": {"put": put_url, "inputs": {"FACTS": facts_url}}}
assert post_start(environ["AX_RUNNER_BOUND_PORT"], body) == (202, "started")
thread.join(timeout=30)
assert codes == [0]
assert (tmp_path / "ws" / "in" / "repo-facts" / "facts.json").read_bytes() == b'{"a.py": ["f"]}', "the input came by its URL"
assert ("GET", "runs/run-1/repo-facts/facts.json") in object_store.requests and (
"PUT",
"runs/run-1/stub/output.tar",
) in object_store.requests
with tarfile.open(fileobj=io.BytesIO(object_store.objects["runs/run-1/stub/output.tar"]), mode="r:") as tar:
names = sorted(tar.getnames())
assert "facts.json" in names and "summary.json" in names, "the whole output directory, as a tar, in the store"
marker = json.loads((tmp_path / "state" / "stub.done.json").read_text())
assert marker["delivered"] is True and marker["status"] == "done"
assert "X-Amz" not in caplog.text and "fake-bucket" not in caplog.text, "no presigned URL in any log line"
assert files_containing(tmp_path, "X-Amz-Signature") == [], "nor in any file"
def test_a_failed_put_is_retried_alone_on_the_next_start_with_a_fresh_url(
env: dict[str, str], object_store: HttpObjectStore, tmp_path: Path
) -> None:
"""The marker and retry-only boot are unchanged: a PUT that fails (wrong bucket: 404) ends the task failed with
the URL's host and path only; the next boot's start brings a working URL and only the delivery runs again."""
counter = tmp_path / "runs.txt"
wrong = object_store.presign_put("runs/run-1/stub/output.tar").replace("/fake-bucket/", "/no-such-bucket/")
thread, environ, codes = main_in_thread(
tmp_path, AX_RUNNER_INSECURE_DELIVERY="1", STUB_COUNTER=str(counter), OUSAST_DELIVERY_BACKOFF="0"
)
assert post_start(environ["AX_RUNNER_BOUND_PORT"], {"run": "run-1", "task": "stub", "delivery": {"put": wrong}})[0] == 202
thread.join(timeout=30)
assert codes == [EXIT_BY_STATUS["failed"]]
marker = json.loads((tmp_path / "state" / "stub.done.json").read_text())
assert marker["delivered"] is False and marker["status"] == "failed" and marker["exit"] == 0
assert "HTTP 404" in marker["reason"] and "X-Amz" not in marker["reason"], "the reason names the status, not the signature"
thread, environ, codes = main_in_thread(
tmp_path, AX_RUNNER_INSECURE_DELIVERY="1", STUB_COUNTER=str(counter), OUSAST_DELIVERY_BACKOFF="0"
)
good = object_store.presign_put("runs/run-1/stub/output.tar")
assert post_start(environ["AX_RUNNER_BOUND_PORT"], {"run": "run-1", "task": "stub", "delivery": {"put": good}})[0] == 202
thread.join(timeout=30)
assert codes == [0] and len(counter.read_text().splitlines()) == 1, "only the delivery is retried"
assert "runs/run-1/stub/output.tar" in object_store.objects
assert json.loads((tmp_path / "state" / "stub.done.json").read_text())["delivered"] is True
def test_deliver_puts_and_names_only_the_safe_url_on_failure(object_store: HttpObjectStore) -> None:
url = object_store.presign_put("k/output.tar")
assert runner.deliver(url, b"tar", {"Content-Type": "application/x-tar"}, backoff=0, method="PUT") == 200
assert object_store.objects["k/output.tar"] == b"tar"
with pytest.raises(runner.DeliveryError) as caught:
runner.deliver(url.replace("/fake-bucket/", "/other/"), b"x", {}, attempts=2, backoff=0, method="PUT")
assert "HTTP 404" in str(caught.value) and "X-Amz" not in str(caught.value) and "/other/k/output.tar" in str(caught.value)
assert runner.safe_url("https://h:1/p/q?X-Amz-Signature=s") == "https://h:1/p/q"