Skip to content

Commit 50e7782

Browse files
authored
Get the MCP to work with the security proxy (#15734)
1 parent 007e9af commit 50e7782

2 files changed

Lines changed: 40 additions & 100 deletions

File tree

Framework/Core/scripts/hyperloop-perf-server/hl_common.py

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,62 +12,20 @@
1212

1313
from __future__ import annotations
1414

15-
import json
1615
import os
17-
import socket
18-
import time
16+
import sys
1917

2018
import httpx
2119

22-
# security-proxy (see ~/src/ali-bot/security-proxy): a localhost credential proxy
23-
# that binds a RANDOM port and mints a per-service, daily-rotating gate token,
24-
# both handed out over a per-user UNIX socket. Replaces the old fixed
25-
# localhost:8888 + static-bearer ccdb-proxy. Every alimonitor.cern.ch artefact is
26-
# routed through the "/alimonitor/" route (upstream = alimonitor.cern.ch root), so a
27-
# single "alimonitor" gate token covers train-workdir / hyperloop / alihyperloop-data.
28-
_AGENT_SOCK = os.path.expanduser(
29-
os.environ.get("SECURITY_PROXY_AGENT_SOCK", "~/.security-proxy/agent.sock")
30-
)
31-
_PROXY_SERVICE = os.environ.get("SECURITY_PROXY_SERVICE", "alimonitor")
32-
_creds_cache: dict[str, tuple[int, str, float]] = {}
20+
# The security-proxy client is shared with the sibling MCP servers; it lives one
21+
# directory up so all of them import the same copy (it used to be duplicated, and
22+
# the copies drifted). See security_proxy_client.__doc__.
23+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
24+
import security_proxy_client as _spc # noqa: E402
3325

34-
35-
def _proxy_creds(service: str) -> tuple[int, str]:
36-
"""Return (port, gate_token) for ``service`` from the security-proxy agent socket.
37-
38-
Cached ~5 min; the proxy accepts the current and previous token, so a slightly
39-
stale cached token still works across the daily rotation. Raises with a clear
40-
hint if the proxy isn't running.
41-
"""
42-
now = time.time()
43-
hit = _creds_cache.get(service)
44-
if hit and now - hit[2] < 300:
45-
return hit[0], hit[1]
46-
try:
47-
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
48-
s.settimeout(5.0)
49-
s.connect(_AGENT_SOCK)
50-
s.sendall((service + "\n").encode())
51-
buf = b""
52-
while not buf.endswith(b"\n"):
53-
chunk = s.recv(4096)
54-
if not chunk:
55-
break
56-
buf += chunk
57-
s.close()
58-
data = json.loads(buf.decode())
59-
except (OSError, ValueError) as exc:
60-
raise RuntimeError(
61-
f"security-proxy agent not reachable at {_AGENT_SOCK} ({exc}); "
62-
"is the proxy running? (see ~/src/ali-bot/security-proxy)"
63-
) from exc
64-
if "error" in data:
65-
raise RuntimeError(
66-
f"security-proxy: {data['error']}; known services: {data.get('services', [])}"
67-
)
68-
port, token = int(data["port"]), data.get("token", "")
69-
_creds_cache[service] = (port, token, now)
70-
return port, token
26+
_AGENT_SOCK = _spc.AGENT_SOCK
27+
_PROXY_SERVICE = _spc.DEFAULT_SERVICE
28+
_proxy_creds = _spc.proxy_creds
7129

7230

7331
async def fetch_bytes(url: str, proxy_token: str = "", token: str = "") -> bytes:
@@ -76,16 +34,18 @@ async def fetch_bytes(url: str, proxy_token: str = "", token: str = "") -> bytes
7634
``alimonitor.cern.ch/<path>`` is rewritten to
7735
``http://127.0.0.1:<port>/alimonitor/<path>``: the random port and a per-service,
7836
daily-rotating gate token come from the security-proxy agent socket
79-
(``~/.security-proxy/agent.sock``; override with ``SECURITY_PROXY_AGENT_SOCK``),
37+
(resolved by ``security_proxy_client``; override with ``SECURITY_PROXY_AGENT_SOCK``),
8038
and the token is sent as ``Authorization: Bearer``. ``Accept-Encoding: identity``
8139
is required (otherwise the proxy returns a gzip Content-Length mismatch). Retries
8240
transient protocol/read errors up to 3×.
8341
8442
Args:
8543
url: Direct artefact URL, a local path, or a ``file://`` URL.
86-
proxy_token: Accepted for backward compatibility but ignored — the gate token
87-
is minted from the agent socket.
88-
token: Ditto (ignored).
44+
proxy_token: Gate token to use when ``url`` ALREADY points at the security-proxy
45+
(``http://127.0.0.1:<port>/<service>/...``), which carries no
46+
``alimonitor.cern.ch`` host to trigger the rewrite above. Ignored
47+
for alimonitor URLs, where the token is minted from the agent socket.
48+
token: Fallback for ``proxy_token``.
8949
"""
9050
# Local file (a path or a file:// URL) — read directly, no HTTP. Lets a
9151
# locally-generated side-car (igprof-demangle-symbols output) be attached
@@ -103,6 +63,18 @@ async def fetch_bytes(url: str, proxy_token: str = "", token: str = "") -> bytes
10363
fetch_url = f"http://127.0.0.1:{port}/{_PROXY_SERVICE}/{path}"
10464
if gate:
10565
headers["Authorization"] = f"Bearer {gate}"
66+
elif url.startswith(("http://127.0.0.1:", "http://localhost:")):
67+
# Already a security-proxy URL (pasted from a browser, or built by a caller
68+
# that resolved the random port itself). The rewrite above does not fire, but
69+
# the proxy still demands the gate token — without it every route answers 401.
70+
gate = proxy_token or token
71+
if not gate:
72+
try:
73+
gate = _proxy_creds(_PROXY_SERVICE)[1]
74+
except RuntimeError:
75+
gate = ""
76+
if gate:
77+
headers["Authorization"] = f"Bearer {gate}"
10678

10779
async with httpx.AsyncClient(verify=False) as client:
10880
for attempt in range(3):

Framework/Core/scripts/hyperloop-server/hyperloop_server.py

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
2525
Credentials come from the security-proxy (see ~/src/ali-bot/security-proxy): the
2626
random port and the per-service "alimonitor" gate token are read from its agent
27-
socket (~/.security-proxy/agent.sock; override with SECURITY_PROXY_AGENT_SOCK).
27+
socket (/usr/local/var/run/security-proxy/agent/agent.sock, falling back to the
28+
legacy ~/.security-proxy/agent.sock; override with SECURITY_PROXY_AGENT_SOCK).
2829
"""
2930

3031
from __future__ import annotations
@@ -35,9 +36,7 @@
3536
import json
3637
import os
3738
import re
38-
import socket
3939
import sys
40-
import time
4140

4241
import httpx
4342
from mcp.server.fastmcp import FastMCP
@@ -49,53 +48,26 @@
4948
# Everything is routed through the single "/alimonitor/" route (upstream =
5049
# alimonitor.cern.ch root), so one "alimonitor" token covers both the
5150
# alihyperloop-data API and the train-workdir artefacts.
52-
_AGENT_SOCK = os.path.expanduser(
53-
os.environ.get("SECURITY_PROXY_AGENT_SOCK", "~/.security-proxy/agent.sock")
54-
)
55-
_PROXY_SERVICE = os.environ.get("SECURITY_PROXY_SERVICE", "alimonitor")
56-
_creds_cache: dict[str, tuple[int, str, float]] = {}
51+
# The security-proxy client is shared with the sibling MCP servers; it lives one
52+
# directory up so all of them import the same copy (it used to be duplicated, and
53+
# the copies drifted). See security_proxy_client.__doc__.
54+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
55+
import security_proxy_client as _spc # noqa: E402
56+
57+
_AGENT_SOCK = _spc.AGENT_SOCK
58+
_PROXY_SERVICE = _spc.DEFAULT_SERVICE
5759

5860

5961
def _proxy_creds() -> tuple[int, str]:
6062
"""(port, gate_token) for the alimonitor service from the security-proxy agent
6163
socket; cached ~5 min (the proxy accepts current+previous token, so a stale
6264
cached token survives the daily rotation)."""
63-
svc = _PROXY_SERVICE
64-
now = time.time()
65-
hit = _creds_cache.get(svc)
66-
if hit and now - hit[2] < 300:
67-
return hit[0], hit[1]
68-
try:
69-
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
70-
s.settimeout(5.0)
71-
s.connect(_AGENT_SOCK)
72-
s.sendall((svc + "\n").encode())
73-
buf = b""
74-
while not buf.endswith(b"\n"):
75-
chunk = s.recv(4096)
76-
if not chunk:
77-
break
78-
buf += chunk
79-
s.close()
80-
data = json.loads(buf.decode())
81-
except (OSError, ValueError) as exc:
82-
raise RuntimeError(
83-
f"security-proxy agent not reachable at {_AGENT_SOCK} ({exc}); "
84-
"is the proxy running? (see ~/src/ali-bot/security-proxy)"
85-
) from exc
86-
if "error" in data:
87-
raise RuntimeError(
88-
f"security-proxy: {data['error']}; known services: {data.get('services', [])}"
89-
)
90-
port, token = int(data["port"]), data.get("token", "")
91-
_creds_cache[svc] = (port, token, now)
92-
return port, token
65+
return _spc.proxy_creds(_PROXY_SERVICE)
9366

9467

9568
def _alimon() -> str:
9669
"""Base URL of the /alimonitor/ proxy route (= alimonitor.cern.ch root)."""
97-
port, _ = _proxy_creds()
98-
return f"http://127.0.0.1:{port}/{_PROXY_SERVICE}"
70+
return _spc.proxy_base_url(_PROXY_SERVICE)
9971

10072

10173
def _api() -> str:
@@ -114,11 +86,7 @@ def _api() -> str:
11486

11587

11688
def _headers() -> dict[str, str]:
117-
_, tok = _proxy_creds()
118-
h = {"Accept-Encoding": "identity"}
119-
if tok:
120-
h["Authorization"] = f"Bearer {tok}"
121-
return h
89+
return _spc.bearer_headers(_PROXY_SERVICE)
12290

12391

12492
async def _get(path: str, params: dict | None = None) -> any:

0 commit comments

Comments
 (0)