# SSH MCP Provider 設定 Runbook > 建立日期: 2026-04-11 (Claude Sonnet 4.6) > 關聯: ADR-070 MCP Phase 2a --- ## 架構說明 SSH MCP Provider (`ssh_provider.py`) 讓 API Pod 可透過 SSH 對宿主機執行診斷和修復指令。 ``` K8s Pod (awoooi-api) ↓ asyncssh 188 (ollama@192.168.0.188) — Docker/Nginx/PM2 操作 110 (wooo@192.168.0.110) — Harbor/Gitea/Sentry 操作 111 (ollama@192.168.0.111) — Ollama GPU 操作 ``` --- ## 安全設計 ### 四層守衛 1. **SSH Key 認證**:ed25519 私鑰掛載至 `/run/secrets/ssh_mcp_key`(0400) 2. **known_hosts 驗證**:`/etc/ssh-mcp/known_hosts` — 僅信任已知主機指紋 3. **參數白名單**:所有工具參數都通過 `_validate_param()` 驗證 4. **指令白名單**:只允許預定義工具,不允許任意指令執行 ### 參數驗證規則 | 參數類型 | 規則 | |---------|------| | container_name | `^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$` | | service | 同 container_name | | compose_dir | 必須以 `/opt/` 或 `/srv/` 開頭,禁止 `..` | | domain | FQDN 格式 | | tail/lines | int,1-5000 | | port | int,1-65535 | --- ## 建立步驟 ### 1. 生成 SSH Key Pair ```bash ssh-keygen -t ed25519 -f /tmp/ssh-mcp-key -N "" -C "awoooi-mcp@k3s" ``` ### 2. 將公鑰加入目標主機 ```bash ssh-copy-id -i /tmp/ssh-mcp-key.pub ollama@192.168.0.188 ssh-copy-id -i /tmp/ssh-mcp-key.pub wooo@192.168.0.110 ssh-copy-id -i /tmp/ssh-mcp-key.pub wooo@192.168.0.120 ssh-copy-id -i /tmp/ssh-mcp-key.pub wooo@192.168.0.121 ``` ### 3. 生成 known_hosts ```bash ssh-keyscan 192.168.0.110 192.168.0.120 192.168.0.121 192.168.0.188 > /tmp/ssh-mcp-known_hosts ``` ### 4. 建立 K8s Secret ```bash kubectl create secret generic ssh-mcp-key \ --from-file=ssh_mcp_key=/tmp/ssh-mcp-key \ --from-literal=known_hosts="$(cat /tmp/ssh-mcp-known_hosts)" \ -n awoooi-prod # 更新既有 Secret 時,用 merge patch,避免 json add 在 key 狀態漂移時失敗 kubectl patch secret ssh-mcp-key -n awoooi-prod --type=merge \ -p "{\"data\":{\"known_hosts\":\"$(base64 -w 0 /tmp/ssh-mcp-known_hosts)\"}}" # 清除暫存 rm /tmp/ssh-mcp-key /tmp/ssh-mcp-key.pub /tmp/ssh-mcp-known_hosts ``` ### 5. ConfigMap 設定(已設定) ```yaml SSH_MCP_ENABLED: "true" SSH_MCP_KNOWN_HOSTS_FILE: "/etc/ssh-mcp/known_hosts" SSH_MCP_HOST_USERS: "192.168.0.188=ollama" ``` ### 6. Deployment Volume Mount(已設定) ```yaml volumeMounts: - name: ssh-mcp-key mountPath: /run/secrets/ssh_mcp_key subPath: ssh_mcp_key readOnly: true - name: ssh-mcp-key mountPath: /etc/ssh-mcp/known_hosts subPath: known_hosts readOnly: true volumes: - name: ssh-mcp-key secret: secretName: ssh-mcp-key defaultMode: 0400 optional: true ``` --- ## 驗證 ```bash # 確認私鑰掛載 kubectl exec -n awoooi-prod deploy/awoooi-api -- ls -la /run/secrets/ssh_mcp_key # 期望: -r--r----- ... 411 ... # 確認 known_hosts 掛載 kubectl exec -n awoooi-prod deploy/awoooi-api -- ls -la /etc/ssh-mcp/known_hosts kubectl exec -n awoooi-prod deploy/awoooi-api -- wc -c /etc/ssh-mcp/known_hosts # 確認 provider 已啟用 kubectl logs -n awoooi-prod deploy/awoooi-api | grep '"name": "ssh_host"' # 期望: "enabled": true # 測試 SSH 連線(從本機) ssh -i /tmp/ssh-mcp-key ollama@192.168.0.188 "echo OK" ``` --- ## 輪換 Key ```bash # 1. 生成新 key ssh-keygen -t ed25519 -f /tmp/ssh-mcp-key-new -N "" -C "awoooi-mcp@k3s-$(date +%Y%m%d)" # 2. 雙寫(新舊並存,避免服務中斷) ssh-copy-id -i /tmp/ssh-mcp-key-new.pub ollama@192.168.0.188 ssh-copy-id -i /tmp/ssh-mcp-key-new.pub wooo@192.168.0.110 # 3. 更新 Secret kubectl create secret generic ssh-mcp-key \ --from-file=ssh_mcp_key=/tmp/ssh-mcp-key-new \ --dry-run=client -o yaml | kubectl apply -f - # 4. Rollout restart kubectl rollout restart deploy/awoooi-api -n awoooi-prod # 5. 確認後移除舊 key(從各主機 authorized_keys 刪除舊 key) ``` --- ## 故障排查 | 症狀 | 原因 | 解決 | |------|------|------| | `ssh_host` provider enabled=false | SSH_MCP_ENABLED 未設定 | 確認 ConfigMap | | known_hosts WARNING | SSH_MCP_KNOWN_HOSTS_FILE 指向空檔 | 確認 Secret 有 known_hosts key;若用 subPath 掛載,patch 後需 rollout restart API/worker | | Connection refused | authorized_keys 未加入公鑰 | 重做步驟 2 | | Host key verification failed | known_hosts 過期 | 重做步驟 3+4 |