feat(sre): enforce typed host ansible handoffs
This commit is contained in:
@@ -19,10 +19,10 @@ SSH MCP Tool Provider — MCP Phase 2a
|
||||
群組 B (6 個安全操作工具,需 trust_score >= 0.8)
|
||||
ssh_docker_restart — DockerContainerExited / HarborDown
|
||||
ssh_docker_compose_restart — SentryDown / SignOzDown / GiteaDown
|
||||
ssh_systemctl_restart — OllamaDown / KaliScannerDown
|
||||
ssh_systemctl_restart — compatibility name; direct execution disabled
|
||||
ssh_clear_docker_logs — HostOutOfDiskSpace (log 佔用)
|
||||
ssh_renew_ssl — TLSCertExpiringIn7Days
|
||||
ssh_reload_nginx — TLSProbeFailure / conf 更新後
|
||||
ssh_reload_nginx — compatibility name; direct execution disabled
|
||||
|
||||
四層安全守衛 (缺一不可):
|
||||
1. tool_name 必須在白名單
|
||||
@@ -358,7 +358,10 @@ class SSHProvider(MCPToolProvider):
|
||||
),
|
||||
MCPTool(
|
||||
name="ssh_systemctl_restart",
|
||||
description="Restart a systemd service (systemctl restart <service>). Requires trust_score >= 0.8.",
|
||||
description=(
|
||||
"Compatibility-only name. Direct SSH execution is disabled; "
|
||||
"use the incident-bound Host Ansible controlled executor."
|
||||
),
|
||||
input_schema={"type": "object", "properties": {
|
||||
"host": {"type": "string"},
|
||||
"service": {"type": "string"},
|
||||
@@ -388,7 +391,10 @@ class SSHProvider(MCPToolProvider):
|
||||
),
|
||||
MCPTool(
|
||||
name="ssh_reload_nginx",
|
||||
description="Test and reload nginx config (nginx -t && systemctl reload nginx). Requires trust_score >= 0.8.",
|
||||
description=(
|
||||
"Compatibility-only name. Direct reload is disabled; use "
|
||||
"the incident-bound Host Ansible controlled executor."
|
||||
),
|
||||
input_schema={"type": "object", "properties": {
|
||||
"host": {"type": "string"},
|
||||
"trust_score": {"type": "number"},
|
||||
@@ -430,6 +436,19 @@ class SSHProvider(MCPToolProvider):
|
||||
error=f"Unknown tool: {tool_name}",
|
||||
)
|
||||
|
||||
# Host/systemd mutation has one writer: the typed Ansible broker. Keep
|
||||
# the legacy tool name discoverable for compatibility, but never turn
|
||||
# it into a raw SSH command.
|
||||
if tool_name in {"ssh_systemctl_restart", "ssh_reload_nginx"}:
|
||||
return MCPToolResult(
|
||||
success=False,
|
||||
execution_id=execution_id,
|
||||
error=(
|
||||
f"{tool_name}_disabled_use_"
|
||||
"host_ansible_controlled_executor"
|
||||
),
|
||||
)
|
||||
|
||||
host = _normalize_ssh_host(str(parameters.get("host", "")))
|
||||
|
||||
# 守衛 2: 允許的 host
|
||||
@@ -553,6 +572,11 @@ class SSHProvider(MCPToolProvider):
|
||||
|
||||
def _build_command(self, tool_name: str, params: dict) -> str:
|
||||
# 所有接受用戶字串的工具,必須先通過 _validate_param() 白名單驗證
|
||||
if tool_name in {"ssh_systemctl_restart", "ssh_reload_nginx"}:
|
||||
raise ValueError(
|
||||
f"{tool_name}_disabled_use_host_ansible_controlled_executor"
|
||||
)
|
||||
|
||||
if tool_name == "ssh_diagnose":
|
||||
# 2026-04-27 Claude Sonnet 4.6: 主機告警自動診斷 — 只讀,不修改任何狀態
|
||||
command = (
|
||||
@@ -618,10 +642,6 @@ class SSHProvider(MCPToolProvider):
|
||||
service = _validate_param("service", params["service"])
|
||||
return f"cd {compose_dir} && docker compose restart {service}"
|
||||
|
||||
if tool_name == "ssh_systemctl_restart":
|
||||
svc = _validate_param("service", params["service"])
|
||||
return f"systemctl restart {svc}"
|
||||
|
||||
if tool_name == "ssh_clear_docker_logs":
|
||||
name = _validate_param("container_name", params["container_name"])
|
||||
# 透過 docker inspect 取得 log 路徑,再截斷
|
||||
@@ -635,9 +655,6 @@ class SSHProvider(MCPToolProvider):
|
||||
domain = _validate_param("domain", params["domain"])
|
||||
return f"/snap/bin/certbot renew --cert-name {domain} --non-interactive 2>&1"
|
||||
|
||||
if tool_name == "ssh_reload_nginx":
|
||||
return "nginx -t 2>&1 && systemctl reload nginx && echo 'Nginx reloaded'"
|
||||
|
||||
if tool_name == "ssh_docker_prune":
|
||||
# Disk-gated docker prune: only acts when the alerting condition still holds.
|
||||
# 2026-05-02 ogt + Claude Sonnet 4.6: ADR-068 飛輪 — disk full SOP
|
||||
|
||||
Reference in New Issue
Block a user