50 lines
2.0 KiB
Python
50 lines
2.0 KiB
Python
import socket
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from starlette.requests import Request
|
|
|
|
from backend.app.core.network_security import UnsafeConnectorUrl, validate_connector_url
|
|
|
|
|
|
def test_connector_url_allows_private_lan_hosts(monkeypatch):
|
|
monkeypatch.setattr(socket, "getaddrinfo", lambda *_args, **_kwargs: (_ for _ in ()).throw(socket.gaierror()))
|
|
assert validate_connector_url("http://198.18.1.10:9000/") == "http://198.18.1.10:9000"
|
|
assert validate_connector_url("https://sonarr.internal/api") == "https://sonarr.internal/api"
|
|
|
|
|
|
def test_connector_url_blocks_unsafe_targets():
|
|
blocked = [
|
|
"file:///etc/passwd",
|
|
"http://localhost:8080",
|
|
"http://127.0.0.1:8080",
|
|
"http://169.254.169.254/latest/meta-data",
|
|
"http://user:pass@host.local",
|
|
"http://0.0.0.0:80",
|
|
]
|
|
for value in blocked:
|
|
try:
|
|
validate_connector_url(value)
|
|
except UnsafeConnectorUrl:
|
|
continue
|
|
raise AssertionError(f"unsafe connector URL accepted: {value}")
|
|
|
|
|
|
def test_connector_url_blocks_dns_names_resolving_to_loopback(monkeypatch):
|
|
monkeypatch.setattr(socket, "getaddrinfo", lambda *_args, **_kwargs: [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("127.0.0.1", 80))])
|
|
with pytest.raises(UnsafeConnectorUrl):
|
|
validate_connector_url("http://connector.internal")
|
|
|
|
|
|
def _request(headers: list[tuple[bytes, bytes]] | None = None, client_host: str = "198.18.10.20") -> Request:
|
|
return Request({"type": "http", "method": "POST", "path": "/api/actions/plan", "headers": headers or [], "client": (client_host, 12345)})
|
|
|
|
|
|
def test_human_boundary_rejects_anonymous_and_legacy_tokens(monkeypatch):
|
|
from backend.app.core import security
|
|
monkeypatch.setenv("APPOPS_REQUIRE_OPERATOR_AUTH", "false")
|
|
for headers in [[], [(b"x-appops-operator-token", b"legacy-token")]]:
|
|
with pytest.raises(HTTPException) as denied:
|
|
security.require_operator_request(_request(headers))
|
|
assert denied.value.status_code == 401
|