mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
186 lines
8.2 KiB
Python
186 lines
8.2 KiB
Python
"""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 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.
|
|||
"""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import ipaddress
|
||
|
|
import json
|
||
|
|
import threading
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
from openultrasast.plane import egress
|
|||
from openultrasast.plane.manifests import ManifestError, load_manifests, parse_manifest
|
|||
|
|
|
||
|
|
MANIFESTS = """
|
||
|
|
apiVersion: ax.io/v1alpha1
|
||
|
|
kind: Model
|
||
|
|
metadata:
|
||
|
|
name: deepseek-flash
|
||
|
|
annotations: {openultrasast.io/provider-extension: "true", openultrasast.io/egress-hosts: api.deepseek.com}
|
||
|
|
spec: {provider: deepseek, model: deepseek-flash, secretKey: {name: deepseek, key: DEEPSEEK_API_KEY}}
|
||
|
|
---
|
||
|
|
apiVersion: ax.io/v1alpha1
|
||
|
|
kind: Workspace
|
||
|
|
metadata: {name: cases}
|
||
|
|
spec:
|
||
|
|
git:
|
||
|
|
- {name: a, repo: https://github.com/example-org/alpha}
|
||
|
|
- {name: b, repo: https://github.com/example-org/beta.git}
|
||
|
|
- {name: c, repo: "[email protected]:group/repo.git"}
|
||
|
|
- {name: d, repo: https://10.0.0.7/mirror/repo.git}
|
||
|
|
---
|
||
|
|
apiVersion: ax.io/v1alpha1
|
||
|
|
kind: Task
|
||
|
|
metadata: {name: verify, annotations: {openultrasast.io/model: deepseek-flash}}
|
||
|
|
spec: {command: [verify], workspaces: [{name: cases, path: /workspace}]}
|
||
|
|
---
|
||
|
|
apiVersion: ax.io/v1alpha1
|
||
|
|
kind: Task
|
||
|
|
metadata: {name: facts}
|
||
|
|
spec: {command: [repo-facts]}
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def manifests(tmp_path: Path):
|
||
|
|
path = tmp_path / "m.yaml"
|
||
|
|
path.write_text(MANIFESTS, encoding="utf-8")
|
||
|
|
return load_manifests([path])
|
||
|
|
|
||
|
|
|
||
|
|
def hostnames(policy: dict) -> list[str]:
|
||
|
|
return [h for rule in policy["rules"] for body in rule.values() for h in body["hostnames"]]
|
||
|
|
|
||
|
|
|
||
|
|
def assert_tight(policy: dict) -> None:
|
||
|
|
for rule in policy["rules"]:
|
||
assert set(rule) == {"tlsPassthrough"}, f"only TLS passthrough rules, got {rule}"
|
|||
body = next(iter(rule.values()))
|
|||
|
|
assert "all" not in body["ports"], "never every port"
|
||
|
|
for host in hostnames(policy):
|
||
|
|
assert "*" not in host, f"no wildcard: {host}"
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
ipaddress.ip_address(host)
|
||
|
|
|
||
|
|
|
||
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_model_bound_task_with_git_workspaces(manifests) -> None:
|
|||
|
|
task = manifests.tasks["verify"]
|
||
policy = egress.policy_for(task, [manifests.workspaces["cases"]], manifests.models["deepseek-flash"], STORE)
|
|||
assert policy["rules"] == [
|
|||
{"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_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:
|
||
|
|
from openultrasast.model.endpoint import DEEPSEEK_BASE_URL
|
||
|
|
|
||
|
|
model = load_manifests([Path("plane/models/deepseek-flash.yaml")]).models["deepseek-flash"]
|
||
|
|
assert model.egress_hosts == (DEEPSEEK_BASE_URL.removeprefix("https://"),)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("hosts", ["*.deepseek.com", "10.1.2.3", "https://api.deepseek.com", "api.deepseek.com:443", "API.deepseek.com"])
|
||
|
|
def test_egress_hosts_annotation_is_validated(hosts: str) -> None:
|
||
|
|
doc = {
|
||
|
|
"apiVersion": "ax.io/v1alpha1",
|
||
|
|
"kind": "Model",
|
||
|
|
"metadata": {"name": "m", "annotations": {"openultrasast.io/provider-extension": "true", "openultrasast.io/egress-hosts": hosts}},
|
||
|
|
"spec": {"provider": "deepseek", "model": "x"},
|
||
|
|
}
|
||
|
|
with pytest.raises(ManifestError, match="egress-hosts"):
|
||
|
|
parse_manifest(doc)
|
||
|
|
|
||
|
|
|
||
|
|
# --- kubectl-ate ---------------------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def actor(tmp_path: Path, name: str, gone: bool = False) -> None:
|
||
|
|
(tmp_path / "applied").mkdir(exist_ok=True)
|
||
|
|
(tmp_path / "applied" / f"{name}.json").write_text(json.dumps({"gone": gone}), encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
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": [{"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:
|
||
|
|
cli = egress.Egress(str(kubectl_ate), context="kind-test")
|
||
|
|
actor(tmp_path, "t1")
|
||
|
|
cli.apply("t1", "default", POLICY)
|
||
|
|
stored = cli.get("t1", "default")
|
||
|
|
assert stored is not None and egress.rules_of(stored) == egress.rules_of(POLICY)
|
||
|
|
assert cli.delete("t1", "default") == "egress policy for default/t1 outlived its actor"
|
||
|
|
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), context="kind-test")
|
|||
actor(tmp_path, "t2")
|
|||
|
|
cli.apply("t2", "default", {"rules": []})
|
||
|
|
cli.apply("t2", "default", POLICY)
|
||
|
|
assert calls(tmp_path) == ["get t2", "create t2", "get t2", "get t2", "update t2", "get t2"]
|
||
|
|
assert json.loads((tmp_path / "policies" / "default_t2.json").read_text())["metadata"]["version"] == 2
|
||
|
|
|
||
|
|
|
||
|
|
def test_apply_waits_for_the_actor_then_gives_up(tmp_path: Path, kubectl_ate: Path) -> None:
|
||
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"))
|
||
|
|
timer.start()
|
||
|
|
cli.apply("late", "default", POLICY, timeout=10)
|
||
|
|
timer.join()
|