Initial curated ServiceMap source from 7a0a61232d05ba9097432abef6f7f4102a64d0b8
This commit is contained in:
commit
105251b9c8
96 files changed
+23587
No files matched your search
@@ -0,0 +1,244 @@
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from app import docker_provider
|
||||
|
||||
|
||||
def test_broker_mode_never_falls_back_to_a_socket_or_request_provided_host(monkeypatch) -> None:
|
||||
monkeypatch.setenv("SERVICEMAP_DOCKER_PROVIDER", "broker")
|
||||
monkeypatch.delenv("SERVICEMAP_DOCKER_BROKER_URL", raising=False)
|
||||
monkeypatch.delenv("SERVICEMAP_DOCKER_BROKER_TOKEN_FILE", raising=False)
|
||||
socket_used = False
|
||||
|
||||
def socket_client(*args, **kwargs):
|
||||
nonlocal socket_used
|
||||
socket_used = True
|
||||
raise AssertionError("broker mode must not create a socket client")
|
||||
|
||||
monkeypatch.setattr(docker_provider.docker, "DockerClient", socket_client)
|
||||
|
||||
with pytest.raises(docker_provider.DockerProviderError, match="Broker mode requires"):
|
||||
docker_provider.docker_client_for_inventory("https://attacker.invalid")
|
||||
|
||||
assert socket_used is False
|
||||
|
||||
|
||||
def test_broker_destination_is_deployment_configuration_only(tmp_path, monkeypatch) -> None:
|
||||
token_file = tmp_path / "broker-token"
|
||||
token_file.write_text("test-token", encoding="utf-8")
|
||||
monkeypatch.setenv("SERVICEMAP_DOCKER_PROVIDER", "broker")
|
||||
monkeypatch.setenv("SERVICEMAP_DOCKER_BROKER_URL", "https://broker.internal")
|
||||
monkeypatch.setenv("SERVICEMAP_DOCKER_BROKER_TOKEN_FILE", str(token_file))
|
||||
|
||||
client = docker_provider.docker_client_for_inventory("https://attacker.invalid")
|
||||
|
||||
assert isinstance(client, docker_provider.BrokerDockerClient)
|
||||
assert client.base_url == "https://broker.internal"
|
||||
client.close()
|
||||
|
||||
|
||||
def _mocked_broker_client(
|
||||
monkeypatch, handler, *, base_url="https://broker.internal", token="opaque-token"
|
||||
):
|
||||
original_client = httpx.Client
|
||||
transport = httpx.MockTransport(handler)
|
||||
options = {}
|
||||
|
||||
def client_factory(**kwargs):
|
||||
options.update(kwargs)
|
||||
return original_client(transport=transport, **kwargs)
|
||||
|
||||
monkeypatch.setattr(docker_provider.httpx, "Client", client_factory)
|
||||
client = docker_provider.BrokerDockerClient(base_url, token)
|
||||
return client, options
|
||||
|
||||
|
||||
def test_inventory_uses_authenticated_fixed_get_and_health_check_has_no_token(monkeypatch) -> None:
|
||||
requests = []
|
||||
|
||||
def handler(request):
|
||||
requests.append(request)
|
||||
if request.url.path == "/healthz":
|
||||
return httpx.Response(200)
|
||||
return httpx.Response(200, json={"items": []})
|
||||
|
||||
client, options = _mocked_broker_client(monkeypatch, handler)
|
||||
try:
|
||||
assert client.ping() is True
|
||||
assert client.containers.list() == []
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert [(request.method, request.url.path) for request in requests] == [
|
||||
("GET", "/healthz"),
|
||||
("GET", "/v1/containers"),
|
||||
]
|
||||
assert "authorization" not in requests[0].headers
|
||||
assert requests[1].headers["authorization"] == "Bearer opaque-token"
|
||||
assert options["follow_redirects"] is False
|
||||
assert options["trust_env"] is False
|
||||
assert 0 < options["timeout"].connect <= 5
|
||||
assert 0 < options["timeout"].read <= 5
|
||||
|
||||
|
||||
def test_broker_rejects_other_authenticated_paths_without_requesting_them(monkeypatch) -> None:
|
||||
requests = []
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch,
|
||||
lambda request: requests.append(request) or httpx.Response(200, json={"items": []}),
|
||||
)
|
||||
try:
|
||||
with pytest.raises(docker_provider.DockerProviderError, match="unsupported"):
|
||||
client._get("/v1/containers/other")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert requests == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"base_url",
|
||||
[
|
||||
"ftp://broker.internal",
|
||||
"https://user:password@broker.internal",
|
||||
"https://broker.internal?token=secret",
|
||||
"https://broker.internal/#fragment",
|
||||
"https:///missing-host",
|
||||
],
|
||||
)
|
||||
def test_broker_rejects_unsafe_destinations(base_url) -> None:
|
||||
with pytest.raises(docker_provider.DockerProviderError):
|
||||
docker_provider.BrokerDockerClient(base_url, "opaque-token")
|
||||
|
||||
|
||||
def test_broker_rejects_header_injection_in_token() -> None:
|
||||
with pytest.raises(docker_provider.DockerProviderError):
|
||||
docker_provider.BrokerDockerClient("https://broker.internal", "token\r\nX-Leak: yes")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
{},
|
||||
{"items": "not-a-list"},
|
||||
{"items": [{"containerRef": "one"}, "malformed-row"]},
|
||||
{"items": [{"containerRef": "one", "name": "one", "state": "running"}]},
|
||||
],
|
||||
)
|
||||
def test_broker_rejects_partial_or_malformed_inventory(payload, monkeypatch) -> None:
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch,
|
||||
lambda request: httpx.Response(200, json=payload),
|
||||
)
|
||||
try:
|
||||
with pytest.raises(docker_provider.DockerProviderError, match="invalid"):
|
||||
client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def test_broker_rejects_non_json_inventory_response(monkeypatch) -> None:
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch,
|
||||
lambda request: httpx.Response(
|
||||
200, content=b'{"items":[]}', headers={"content-type": "text/plain"}
|
||||
),
|
||||
)
|
||||
try:
|
||||
with pytest.raises(docker_provider.DockerProviderError, match="invalid"):
|
||||
client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def test_broker_rejects_oversized_inventory_response(monkeypatch) -> None:
|
||||
monkeypatch.setattr(docker_provider, "_BROKER_MAX_RESPONSE_BYTES", 4)
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch,
|
||||
lambda request: httpx.Response(200, content=b'{"items":[]}'),
|
||||
)
|
||||
try:
|
||||
with pytest.raises(docker_provider.DockerProviderError, match="invalid"):
|
||||
client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def test_broker_container_preserves_discovery_fields_without_exposing_environment(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
item = {
|
||||
"containerRef": "abcdef1234567890",
|
||||
"name": "/example-service",
|
||||
"image": "example/service:1.0",
|
||||
"state": "running",
|
||||
"health": "healthy",
|
||||
"healthcheckPresent": True,
|
||||
"labels": {"servicemap.enabled": "true"},
|
||||
"networks": ["default"],
|
||||
"ports": {
|
||||
"8000/tcp": [{"hostIp": "0.0.0.0", "hostPort": "1253"}],
|
||||
"5432/tcp": None,
|
||||
},
|
||||
}
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch,
|
||||
lambda request: httpx.Response(200, json={"items": [item]}),
|
||||
)
|
||||
try:
|
||||
[container] = client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert container.id == item["containerRef"]
|
||||
assert container.name == "example-service"
|
||||
assert container.image.tags == [item["image"]]
|
||||
assert container.attrs["State"] == {"Status": "running", "Health": {"Status": "healthy"}}
|
||||
assert container.attrs["Config"]["Labels"] == item["labels"]
|
||||
assert container.attrs["Config"]["Healthcheck"] == {"BrokerReported": True}
|
||||
assert container.attrs["Config"]["Env"] == []
|
||||
assert container.attrs["NetworkSettings"]["Networks"] == {"default": {}}
|
||||
assert container.attrs["NetworkSettings"]["Ports"] == {
|
||||
"8000/tcp": [{"HostIp": "0.0.0.0", "HostPort": "1253"}],
|
||||
"5432/tcp": [],
|
||||
}
|
||||
|
||||
|
||||
def test_broker_health_status_proves_healthcheck_when_old_schema_omits_flag(monkeypatch) -> None:
|
||||
item = {
|
||||
"containerRef": "abcdef1234567890",
|
||||
"name": "example-service",
|
||||
"image": "example/service:1.0",
|
||||
"state": "running",
|
||||
"health": "healthy",
|
||||
"labels": {},
|
||||
"networks": [],
|
||||
"ports": {},
|
||||
}
|
||||
client, _ = _mocked_broker_client(
|
||||
monkeypatch, lambda request: httpx.Response(200, json={"items": [item]})
|
||||
)
|
||||
try:
|
||||
[container] = client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert container.attrs["Config"]["Healthcheck"] == {"BrokerReported": True}
|
||||
|
||||
|
||||
def test_broker_error_messages_do_not_expose_destination_or_credential(monkeypatch) -> None:
|
||||
def handler(request):
|
||||
raise httpx.ConnectError(
|
||||
"failed with opaque-token at https://broker.internal", request=request
|
||||
)
|
||||
|
||||
client, _ = _mocked_broker_client(monkeypatch, handler)
|
||||
try:
|
||||
with pytest.raises(docker_provider.DockerProviderError) as caught:
|
||||
client.containers.list()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
assert "Docker broker request failed" in str(caught.value)
|
||||
assert "opaque-token" not in str(caught.value)
|
||||
assert "broker.internal" not in str(caught.value)
|
||||
Reference in new issue
Block a user