mirror of
https://github.com/smittix/intercept.git
synced 2026-06-18 18:39:47 -07:00
feat: allowlisted generic agent proxy route
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -496,6 +496,39 @@ def proxy_mode_stream(agent_id: int, mode: str):
|
||||
return response
|
||||
|
||||
|
||||
# Endpoint prefixes that may be reached through the generic proxy. Extend this
|
||||
# list instead of writing new per-endpoint proxy routes.
|
||||
PROXY_ALLOWED_PREFIXES = ("wifi/v2/",)
|
||||
|
||||
|
||||
@controller_bp.route("/agents/<int:agent_id>/proxy/<path:subpath>", methods=["GET"])
|
||||
def proxy_passthrough(agent_id: int, subpath: str):
|
||||
"""Forward an allowlisted GET to a remote agent.
|
||||
|
||||
Keeps remote agents at feature parity with local mode without a
|
||||
hand-written proxy per endpoint.
|
||||
"""
|
||||
if not subpath.startswith(PROXY_ALLOWED_PREFIXES):
|
||||
return api_error("Endpoint not allowed through agent proxy", 403)
|
||||
|
||||
agent = get_agent(agent_id)
|
||||
if not agent:
|
||||
return api_error("Agent not found", 404)
|
||||
|
||||
try:
|
||||
client = create_client_from_agent(agent)
|
||||
result = client.get(f"/{subpath}", params=request.args.to_dict())
|
||||
return jsonify(
|
||||
{
|
||||
"status": "success",
|
||||
"agent_id": agent_id,
|
||||
"result": result,
|
||||
}
|
||||
)
|
||||
except (AgentHTTPError, AgentConnectionError) as e:
|
||||
return api_error(f"Agent error: {e}", 502)
|
||||
|
||||
|
||||
@controller_bp.route("/agents/<int:agent_id>/wifi/v2/clients", methods=["GET"])
|
||||
def proxy_wifi_clients(agent_id: int):
|
||||
"""Get the WiFi client list from a remote agent."""
|
||||
|
||||
@@ -552,3 +552,38 @@ class TestSSEStream:
|
||||
# Full SSE testing requires more complex setup
|
||||
response = client.get("/controller/stream/all")
|
||||
assert response.mimetype == "text/event-stream"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Generic Proxy Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestGenericProxy:
|
||||
"""Tests for the allowlisted agent passthrough proxy."""
|
||||
|
||||
def _mock_agent(self):
|
||||
return {"id": 1, "name": "node-1", "url": "http://10.0.0.2:5000", "api_key": None}
|
||||
|
||||
def test_proxies_allowlisted_get(self, client):
|
||||
with (
|
||||
patch("routes.controller.get_agent", return_value=self._mock_agent()),
|
||||
patch("routes.controller.create_client_from_agent") as mock_create,
|
||||
):
|
||||
mock_create.return_value.get.return_value = [{"mac": "AA:BB"}]
|
||||
resp = client.get("/controller/agents/1/proxy/wifi/v2/clients?bssid=AA:BB")
|
||||
assert resp.status_code == 200
|
||||
data = resp.get_json()
|
||||
assert data["status"] == "success"
|
||||
assert data["result"] == [{"mac": "AA:BB"}]
|
||||
mock_create.return_value.get.assert_called_once_with("/wifi/v2/clients", params={"bssid": "AA:BB"})
|
||||
|
||||
def test_rejects_non_allowlisted_path(self, client):
|
||||
with patch("routes.controller.get_agent", return_value=self._mock_agent()):
|
||||
resp = client.get("/controller/agents/1/proxy/settings/secrets")
|
||||
assert resp.status_code == 403
|
||||
|
||||
def test_unknown_agent_404(self, client):
|
||||
with patch("routes.controller.get_agent", return_value=None):
|
||||
resp = client.get("/controller/agents/99/proxy/wifi/v2/clients")
|
||||
assert resp.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user