mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-08 11:56:59 +00:00
f38323c3a1
* fix(docker): make host socket compose opt-in * fix(cookbook): gate container Docker access * fix(docker): gate socket group setup on opt-in * fix(cookbook): gate generated docker exec serve commands * fix(cookbook): narrow generated docker exec forms
340 lines
10 KiB
Python
340 lines
10 KiB
Python
import socket
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from fastapi import HTTPException
|
|
from starlette.requests import Request
|
|
|
|
import routes.cookbook_routes as cookbook_routes
|
|
from routes.cookbook_helpers import ServeRequest, _validate_serve_cmd
|
|
from src.host_docker_access import HOST_DOCKER_ACCESS_HINT
|
|
|
|
|
|
def _model_serve_endpoint():
|
|
router = cookbook_routes.setup_cookbook_routes()
|
|
for route in router.routes:
|
|
if route.path == "/api/model/serve" and "POST" in route.methods:
|
|
return route.endpoint
|
|
raise AssertionError("POST /api/model/serve route not found")
|
|
|
|
|
|
def _admin_request() -> Request:
|
|
request = Request(
|
|
{
|
|
"type": "http",
|
|
"method": "POST",
|
|
"path": "/api/model/serve",
|
|
"headers": [],
|
|
"state": {},
|
|
}
|
|
)
|
|
request.state.current_user = "admin"
|
|
return request
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_container_cli_only_is_rejected(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
|
|
available = await cookbook_routes._binary_available(
|
|
"docker",
|
|
None,
|
|
None,
|
|
in_container=True,
|
|
environ={},
|
|
socket_path=str(tmp_path / "missing.sock"),
|
|
)
|
|
|
|
assert available is False
|
|
message = cookbook_routes._missing_binary_message(
|
|
"docker",
|
|
"local server",
|
|
local_host_docker_blocked=True,
|
|
)
|
|
assert message == HOST_DOCKER_ACCESS_HINT
|
|
assert "docker/host-docker.yml" in message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_container_opt_in_with_unix_socket_is_allowed(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
socket_path = tmp_path / "docker.sock"
|
|
|
|
with socket.socket(socket.AF_UNIX) as unix_socket:
|
|
unix_socket.bind(str(socket_path))
|
|
available = await cookbook_routes._binary_available(
|
|
"docker",
|
|
None,
|
|
None,
|
|
in_container=True,
|
|
environ={"ODYSSEUS_ENABLE_HOST_DOCKER": "true"},
|
|
socket_path=str(socket_path),
|
|
)
|
|
|
|
assert available is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_native_local_docker_still_uses_cli_presence(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
|
|
available = await cookbook_routes._binary_available(
|
|
"docker",
|
|
None,
|
|
None,
|
|
in_container=False,
|
|
environ={},
|
|
socket_path=str(tmp_path / "missing.sock"),
|
|
)
|
|
|
|
assert available is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remote_docker_still_uses_ssh_probe(monkeypatch):
|
|
remote_probe = AsyncMock(return_value=True)
|
|
monkeypatch.setattr(cookbook_routes, "_remote_binary_available", remote_probe)
|
|
monkeypatch.setattr(
|
|
cookbook_routes.shutil,
|
|
"which",
|
|
lambda binary: pytest.fail("remote checks must not inspect the local CLI"),
|
|
)
|
|
|
|
available = await cookbook_routes._binary_available(
|
|
"docker",
|
|
"gpu-server",
|
|
"2222",
|
|
windows=True,
|
|
in_container=True,
|
|
environ={},
|
|
socket_path="/missing/docker.sock",
|
|
)
|
|
|
|
assert available is True
|
|
remote_probe.assert_awaited_once_with(
|
|
"gpu-server",
|
|
"2222",
|
|
"docker",
|
|
windows=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"cmd",
|
|
[
|
|
"docker exec ollama-test ollama-import example/model model 8192 model.gguf",
|
|
"docker exec ollama-rocm ollama show llama3",
|
|
],
|
|
)
|
|
async def test_local_container_serve_returns_host_docker_opt_in_hint(
|
|
monkeypatch,
|
|
tmp_path,
|
|
cmd,
|
|
):
|
|
async def binary_available(binary, remote, ssh_port, **kwargs):
|
|
assert remote is None
|
|
if binary == "tmux":
|
|
return True
|
|
assert cookbook_routes.shutil.which(binary) == "/usr/bin/docker"
|
|
return False
|
|
|
|
monkeypatch.setattr(cookbook_routes, "require_admin", lambda request: None)
|
|
monkeypatch.setattr(cookbook_routes, "_binary_available", binary_available)
|
|
monkeypatch.setattr(cookbook_routes, "running_in_container", lambda: True)
|
|
monkeypatch.setattr(
|
|
cookbook_routes,
|
|
"host_docker_access_enabled",
|
|
lambda: False,
|
|
)
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
monkeypatch.setattr(cookbook_routes, "TMUX_LOG_DIR", tmp_path)
|
|
monkeypatch.setattr(
|
|
cookbook_routes,
|
|
"load_stored_hf_token",
|
|
lambda **kwargs: "",
|
|
)
|
|
|
|
response = await _model_serve_endpoint()(
|
|
_admin_request(),
|
|
ServeRequest(
|
|
repo_id="example/model",
|
|
cmd=cmd,
|
|
),
|
|
)
|
|
|
|
assert response["ok"] is False
|
|
assert response["error"] == HOST_DOCKER_ACCESS_HINT
|
|
assert "cmd binary 'docker' is not allowed" not in response["error"]
|
|
assert "docker/host-docker.yml" in response["error"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_local_container_serve_allows_generated_docker_exec_when_enabled(
|
|
monkeypatch,
|
|
tmp_path,
|
|
):
|
|
checked_binaries = []
|
|
launched_commands = []
|
|
|
|
async def binary_available(binary, remote, ssh_port, **kwargs):
|
|
checked_binaries.append(binary)
|
|
if binary == "docker":
|
|
assert cookbook_routes.running_in_container() is True
|
|
assert cookbook_routes.host_docker_access_enabled() is True
|
|
return True
|
|
|
|
class _Stderr:
|
|
async def read(self):
|
|
return b"mock launch stopped"
|
|
|
|
class _Process:
|
|
returncode = 1
|
|
stderr = _Stderr()
|
|
|
|
async def wait(self):
|
|
return None
|
|
|
|
async def launch(command, **kwargs):
|
|
launched_commands.append(command)
|
|
return _Process()
|
|
|
|
monkeypatch.setattr(cookbook_routes, "require_admin", lambda request: None)
|
|
monkeypatch.setattr(cookbook_routes, "_binary_available", binary_available)
|
|
monkeypatch.setattr(cookbook_routes, "running_in_container", lambda: True)
|
|
monkeypatch.setattr(
|
|
cookbook_routes,
|
|
"host_docker_access_enabled",
|
|
lambda: True,
|
|
)
|
|
monkeypatch.setattr(cookbook_routes, "TMUX_LOG_DIR", tmp_path)
|
|
monkeypatch.setattr(
|
|
cookbook_routes,
|
|
"load_stored_hf_token",
|
|
lambda **kwargs: "",
|
|
)
|
|
monkeypatch.setattr(
|
|
cookbook_routes.asyncio,
|
|
"create_subprocess_shell",
|
|
launch,
|
|
)
|
|
|
|
response = await _model_serve_endpoint()(
|
|
_admin_request(),
|
|
ServeRequest(
|
|
repo_id="llama3",
|
|
cmd="docker exec ollama-rocm ollama show llama3",
|
|
),
|
|
)
|
|
|
|
assert checked_binaries == ["tmux", "docker"]
|
|
assert launched_commands
|
|
assert response["error"] == "mock launch stopped"
|
|
runner = next(tmp_path.glob("serve-*_run.sh")).read_text(encoding="utf-8")
|
|
assert "docker exec ollama-rocm ollama show llama3" in runner
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"cmd",
|
|
[
|
|
"docker run --rm alpine",
|
|
"docker exec random-container ollama show llama3",
|
|
"docker compose up",
|
|
"docker exec ollama-rocm ollama rm llama3",
|
|
"docker exec ollama-test ollama rm llama3",
|
|
"docker exec ollama-rocm ollama pull llama3",
|
|
"docker exec ollama-test ollama show llama3",
|
|
"docker exec ollama-test sh -c 'ollama show llama3'",
|
|
"docker exec ollama-test ollama show llama3; id",
|
|
"docker exec ollama-rocm ollama show llama3 extra",
|
|
"docker exec ollama-rocm ollama show llama3?",
|
|
"docker exec ollama-test ollama-import org/model model many model.gguf",
|
|
"docker exec ollama-test ollama-import org/model model 8192 path/model.gguf",
|
|
"docker exec ollama-rocm ollama show $(id)",
|
|
"docker exec ollama-rocm ollama show llama3 | cat",
|
|
],
|
|
)
|
|
def test_arbitrary_docker_commands_stay_blocked(cmd):
|
|
assert cookbook_routes._is_generated_ollama_docker_exec_cmd(cmd) is False
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
_validate_serve_cmd(cmd)
|
|
|
|
assert exc.value.status_code == 400
|
|
|
|
|
|
def test_generated_ollama_import_shape_is_narrowly_allowed():
|
|
assert cookbook_routes._is_generated_ollama_docker_exec_cmd(
|
|
"docker exec ollama-test ollama-import org/model model 8192 model.gguf"
|
|
)
|
|
assert cookbook_routes._is_generated_ollama_docker_exec_cmd(
|
|
"docker exec ollama-test ollama-import org/model model 8192"
|
|
)
|
|
assert not cookbook_routes._is_generated_ollama_docker_exec_cmd(
|
|
"docker exec ollama-rocm ollama-import org/model model 8192 model.gguf"
|
|
)
|
|
|
|
|
|
def test_generated_ollama_show_shape_is_narrowly_allowed():
|
|
assert cookbook_routes._is_generated_ollama_docker_exec_cmd(
|
|
"docker exec ollama-rocm ollama show llama3:latest"
|
|
)
|
|
|
|
|
|
def test_local_ollama_docker_access_blocked_in_container_cli_only(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
|
|
assert cookbook_routes._local_ollama_docker_access_blocked(
|
|
in_container=True,
|
|
environ={},
|
|
socket_path=str(tmp_path / "missing.sock"),
|
|
) is True
|
|
|
|
|
|
def test_local_ollama_docker_access_not_blocked_for_native_cli(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(cookbook_routes.shutil, "which", lambda binary: "/usr/bin/docker")
|
|
|
|
assert cookbook_routes._local_ollama_docker_access_blocked(
|
|
in_container=False,
|
|
environ={},
|
|
socket_path=str(tmp_path / "missing.sock"),
|
|
) is False
|
|
|
|
|
|
def test_local_ollama_download_probe_omits_docker_commands_when_blocked():
|
|
lines = []
|
|
|
|
cookbook_routes._append_local_ollama_download_command_lines(
|
|
lines,
|
|
"ollama pull llama3:latest",
|
|
docker_fallback_available=False,
|
|
docker_fallback_blocked=True,
|
|
)
|
|
|
|
rendered = "\n".join(lines)
|
|
|
|
assert "command -v docker" not in rendered
|
|
assert "docker ps" not in rendered
|
|
assert "docker exec" not in rendered
|
|
assert "ODYSSEUS_OLLAMA_PULL_CMD" in rendered
|
|
assert "docker/host-docker.yml" in rendered
|
|
assert "exit 127" in rendered
|
|
|
|
|
|
def test_local_ollama_download_probe_keeps_docker_fallback_when_allowed():
|
|
lines = []
|
|
|
|
cookbook_routes._append_local_ollama_download_command_lines(
|
|
lines,
|
|
"ollama pull llama3:latest",
|
|
docker_fallback_available=True,
|
|
docker_fallback_blocked=False,
|
|
)
|
|
|
|
rendered = "\n".join(lines)
|
|
|
|
assert "docker ps" in rendered
|
|
assert "docker exec ${ODYSSEUS_OLLAMA_CONTAINER}" in rendered
|
|
assert "ODYSSEUS_OLLAMA_PULL_CMD" in rendered
|