fix(ssh-auto): 主機告警 SSH 自動診斷授權(HostHighCpuLoad 不再卡人工審核)
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 9m7s

根因:SSH_MCP_ALLOWED_HOSTS 未設定 → _ssh_execute() 全部攔截
      + auto_approve 只認 kubectl 不認 ssh → 主機告警永遠降級人工

修復:
- ConfigMap: 補 SSH_MCP_ALLOWED_HOSTS 四主機白名單
- alert_rules: HostHighCpuLoad 等從 NO_ACTION 改為 ssh_diagnose 指令
- auto_approve: _has_executable 加入 ssh 開頭識別
- decision_manager: _ssh_execute() 加入 ssh_diagnose 路由
- ssh_provider: 新增 ssh_diagnose tool(ps aux + free -h + df -h,只讀)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-27 20:13:07 +08:00
parent 277808758d
commit c1a1be61bd
5 changed files with 41 additions and 9 deletions

View File

@@ -301,11 +301,19 @@ class AutoApprovePolicy:
# P1-2 改用 NO_EXECUTABLE_ACTION避免污染 KM 飛輪學習資料)
_raw_action = proposal_data.get("action", "") or ""
_kubectl_cmd = proposal_data.get("kubectl_command", "") or ""
_has_kubectl = "kubectl" in _raw_action.lower() or "kubectl" in _kubectl_cmd.lower()
if not _has_kubectl:
# 2026-04-27 Claude Sonnet 4.6: 擴充可執行指令識別,加入 SSH 診斷路徑
# 根因_has_kubectl 只認 kubectlSSH 診斷指令(主機告警)被全部攔截 → 飛輪停轉
# 修復ssh {host} '...' 格式也是可執行指令,允許走 _ssh_execute() 路徑
_has_executable = (
"kubectl" in _raw_action.lower()
or "kubectl" in _kubectl_cmd.lower()
or _raw_action.lower().strip().startswith("ssh ")
or _kubectl_cmd.lower().strip().startswith("ssh ")
)
if not _has_executable:
return self._reject(
reason=AutoApproveReason.NO_EXECUTABLE_ACTION,
detail=f"Action '{_raw_action[:60] or _kubectl_cmd[:60]}' is natural language — no kubectl command, requires human review",
detail=f"Action '{_raw_action[:60] or _kubectl_cmd[:60]}' is natural language — no kubectl/ssh command, requires human review",
risk_level=risk_level,
trust_score=trust_score,
confidence=confidence,

View File

@@ -3101,6 +3101,9 @@ class DecisionManager:
return
# 解析 SSH tool + params
# 2026-04-27 Claude Sonnet 4.6: 加入主機診斷路徑
# 根因:只支援 docker/systemctl restart主機告警 ssh {host} '...' 格式全降級人工
# 修復:識別 ssh_diagnose 模式,路由到 ssh_get_top_processes / ssh_get_disk_usage
_action_lower = action.lower().strip()
if _action_lower.startswith("docker restart"):
_tool = "docker_restart"
@@ -3108,6 +3111,9 @@ class DecisionManager:
elif _action_lower.startswith("systemctl restart"):
_tool = "service_restart"
_service = target
elif _action_lower.startswith("ssh ") and ("ps aux" in _action_lower or "top" in _action_lower or "free" in _action_lower or "df -h" in _action_lower or "uptime" in _action_lower):
# 主機診斷指令:自動收集 CPU/記憶體/磁碟,不修改任何狀態
_tool = "ssh_diagnose"
else:
logger.info(
"ssh_execute_unknown_action",
@@ -3125,8 +3131,9 @@ class DecisionManager:
params: dict = {"host": _host}
if _tool == "docker_restart":
params["container"] = _container
else:
elif _tool == "service_restart":
params["service"] = _service
# ssh_diagnose: 只需 host無額外 params
try:
result = await self._ssh.execute(tool_name=_tool, parameters=params)