fix(mcp): harden ssh provider connection params
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 59s
CD Pipeline / build-and-deploy (push) Successful in 3m20s
CD Pipeline / post-deploy-checks (push) Successful in 1m17s

This commit is contained in:
Your Name
2026-05-06 21:51:38 +08:00
parent 150f17b219
commit 8396d37275
3 changed files with 84 additions and 5 deletions

View File

@@ -58,6 +58,7 @@ logger = structlog.get_logger(__name__)
SSH_KEY_PATH = "/run/secrets/ssh_mcp_key"
SSH_USER = "wooo"
SSH_PORT = 22
DEFAULT_HOST_USERS = {
# AI/Web host is operated by the ollama account in the current topology.
"192.168.0.188": "ollama",
@@ -104,6 +105,29 @@ def _validate_param(key: str, value: str) -> str:
# tail / port / lines 由呼叫方 int() 轉換,不需字串白名單
return value
def _normalize_ssh_host(value: str) -> str:
"""
Normalize host labels before they enter asyncssh.
Prometheus labels often arrive as ``192.168.0.110:9100``. That port is the
exporter port, not SSH. The SSH provider must connect to the host on the
platform SSH port, otherwise asyncssh can receive a stringly port from
config/labels and fail with ``%d format`` before the tool even runs.
"""
host = (value or "").strip()
if host.startswith("ssh://"):
host = host.removeprefix("ssh://")
if "@" in host:
host = host.rsplit("@", 1)[1]
if host.startswith("[") and "]" in host:
return host[1:host.index("]")]
if host.count(":") == 1:
maybe_host, maybe_port = host.rsplit(":", 1)
if maybe_port.isdigit():
return maybe_host
return host
# 群組 A只讀
GROUP_A_TOOLS = {
"ssh_diagnose",
@@ -375,7 +399,7 @@ class SSHProvider(MCPToolProvider):
error=f"Unknown tool: {tool_name}",
)
host = parameters.get("host", "")
host = _normalize_ssh_host(str(parameters.get("host", "")))
# 守衛 2: 允許的 host
if host not in self._allowed_hosts():
@@ -604,7 +628,7 @@ class SSHProvider(MCPToolProvider):
raise RuntimeError(
"asyncssh is not installed. "
"Add 'asyncssh' to pyproject.toml dependencies."
)
) from None
import os
if not os.path.exists(SSH_KEY_PATH):
@@ -625,11 +649,13 @@ class SSHProvider(MCPToolProvider):
async with asyncssh.connect(
host,
port=SSH_PORT,
username=username or SSH_USER,
client_keys=[SSH_KEY_PATH],
known_hosts=known_hosts_path, # None = 跳過驗證(內網),或指定文件路徑
connect_timeout=timeout,
config=None, # 禁止讀取使用者 ssh config避免 Port 字串污染 asyncssh
connect_timeout=float(timeout),
) as conn:
# Bug 根因asyncssh 模組沒有頂層 run();應呼叫 conn.run()2026-04-24 Claude Sonnet 4.6
result = await conn.run(cmd, timeout=timeout, check=False)
result = await conn.run(cmd, timeout=float(timeout), check=False)
return (result.stdout or ""), (result.stderr or "")