# forge-tools-ssh > MCP-сервер, который даёт AI-ассистенту аккуратный доступ к серверам по SSH. ![Go](https://img.shields.io/badge/Go-1.27-00ADD8?logo=go&logoColor=white) ![MCP](https://img.shields.io/badge/MCP-stdio-6E56CF) ![Tools](https://img.shields.io/badge/tools-45-success) ![License](https://img.shields.io/badge/license-Apache--2.0-blue) **forge-tools-ssh** — это небольшая отдельная программа, которая превращает обычный SSH в набор инструментов, понятных AI-ассистенту. Ассистент вызывает инструмент по имени — `run`, `disk_usage`, `docker_ps`, `journal_read` — и получает готовый результат, не выдумывая каждый раз синтаксис `ssh`, `grep`, `docker` и `df`. Типичные задачи: посмотреть состояние сервера, найти причину падения сервиса, прочитать лог, проверить занятое место, разобраться с контейнерами или БД, а при явном разрешении — отредактировать конфиг и перезапустить службу. > ### Коротко про MCP > [MCP](https://modelcontextprotocol.io) — стандарт подключения AI-ассистентов > к инструментам. Сервер — это программа, которая читает запросы ассистента > через стандартные ввод/вывод и возвращает результат. Один и тот же сервер > работает с любым MCP-клиентом. Модуль намеренно **не содержит адресов, логинов и ключей**: всё, к чему можно подключиться, объявляет оператор. Ассистент лишь выбирает из разрешённого. --- ## Возможности - **45 инструментов** — от запуска команды до разбора SIP-трафика. - **Подключение через джамп-хост** — доступ к закрытому контуру через bastion. - **PAM-шлюзы** — двухстадийная аутентификация через корпоративный шлюз. - **Проверка синтаксиса** JSON/YAML/TOML/XML/INI/ENV/Dockerfile **до** записи файла — на самом сервере MCP, без зависимостей на удалённой машине. - **Политика подключений** (`ssh.json`): именованные профили и allowlist хостов, которые оператор задаёт декларативно. Ошибка конфига — отказ, а не тихий обход. - **Live-reload**: правки `ssh.json` подхватываются без перезапуска. - **Устойчивость**: перезапуск связи при обрыве, таймауты, лимиты на размер и длину вывода, корректная отмена задач. --- ## Архитектура ```mermaid flowchart LR A["AI-ассистент
(MCP-клиент)"] -- "stdio · JSON-RPC" --> B["forge-tools-ssh"] B -- "SSH / SFTP" --> C["Сервер"] B -- "SSH → bastion" --> D["Сервер в закрытом контуре"] B -- "SSH → PAM-шлюз" --> E["Целевой сервер"] style B fill:#6E56CF,color:#fff ``` Сервер — stdio-процесс: он не открывает портов и не слушает сеть. Общение с AI-клиентом идёт через потоки ввода/вывода, а наружу он ходит только по SSH. --- ## Быстрый старт ### Сборка ```bash export GOPRIVATE=git.totmin.ru go build -trimpath -o forge-tools-ssh . ``` Проверить, что бинарник живой: ```bash ./forge-tools-ssh --health # → ok ``` ### Подключение к AI-клиенту Сервер запускается клиентом как дочерний процесс. Пример конфигурации: ```json { "mcp": { "servers": { "ssh": { "command": "/opt/forge-tools/ssh/forge-tools-ssh", "env": { "FORGE_TENANT_CONFIG": "/etc/forge/agents/my-agent", "SSH_MCP_KEY_PATH": "/etc/forge/agents/my-agent/ssh/id_ed25519" } } } } } ``` Дальше достаточно попросить ассистента, например: «посмотри, почему упал nginx на `prod-web`, и покажи последние 100 строк журнала». --- ## Инструменты (45) ### Ядро — 5 | Инструмент | Назначение | |---|---| | `connect` | Установить SSH-соединение (по профилю или ad-hoc в рамках allowlist) | | `disconnect` | Закрыть соединение (или все сразу) | | `run` | Выполнить команду на удалённом хосте | | `identity` | Показать публичный ключ сервера для `authorized_keys` | | `info` | ОС, ядро, hostname, архитектура | ### Файлы — 6 | Инструмент | Назначение | |---|---| | `read` | Прочитать файл (лимит 10 МБ) | | `write` | Записать файл с валидацией синтаксиса перед записью | | `edit` | Правка файла: replace / regex / insert / append / prepend / delete / replace_line | | `validate` | Проверить синтаксис файла на стороне MCP-сервера | | `list_dir` | Список каталога | | `sync` | Потоковая передача файла между двумя хостами | ### Мониторинг — 7 | Инструмент | Назначение | |---|---| | `usage` | Загрузка, память, диски | | `ps` | Топ процессов по CPU или памяти | | `logs` | Хвост лог-файла (опционально с фильтром) | | `journal_read` | `journalctl` / syslog | | `dmesg_read` | Кольцевой буфер ядра | | `diagnose_system` | Экспресс-диагностика: load, OOM, диски, упавшие службы | | `list_services` | Список служб (systemd / OpenRC и др.) | ### Диск — 2 | Инструмент | Назначение | |---|---| | `disk_usage` | Место на томе, содержащем указанный путь | | `disk_usage_all` | Все точки монтирования, разделы ≥ 80% помечены | ### Сеть и поиск — 4 | Инструмент | Назначение | |---|---| | `net_stat` | Слушающие порты (`ss` / `netstat`) | | `search_files` | Поиск файлов (`find`) | | `search_text` | Поиск текста (`grep`) | | `package_manage` | Пакеты: apt / apk / dnf / yum | ### Docker — 8 | Инструмент | Назначение | |---|---| | `docker_ps` | Список контейнеров | | `docker_logs` | Логи контейнера | | `docker_op` | start / stop / restart | | `docker_ip` | IP-адреса контейнера | | `docker_find_by_ip` | Найти контейнер по IP | | `docker_networks` | Сети Docker | | `docker_cp_from` | Копировать файл из контейнера на хост | | `docker_cp_to` | Копировать файл с хоста в контейнер | ### Базы данных — 3 | Инструмент | Назначение | |---|---| | `db_query` | SQL/CQL/Mongo-запрос внутри контейнера (postgres, mysql, scylladb, cassandra, mongodb) | | `db_schema` | Схема: таблицы / коллекции | | `list_db_containers` | Найти контейнеры, похожие на БД | ### VoIP — 10 | Инструмент | Назначение | |---|---| | `voip_discover_containers` | Найти VoIP-контейнеры по имени/образу | | `voip_sip_capture` | Захват SIP-сигналинга в PCAP (`sngrep`) | | `voip_call_flow` | Разбор SIP call flow из PCAP | | `voip_registrations` | REGISTER-диалоги и их исход | | `voip_call_stats` | Агрегированная статистика вызовов | | `voip_extract_sdp` | Кодеки и RTP-порты из SDP | | `voip_packet_check` | Быстрая проверка наличия SIP-пакетов | | `voip_network_capture` | Захват SIP через `tcpdump` | | `voip_rtp_capture` | Захват RTP для проверки медиа-потока | | `voip_network_diagnostics` | ping / traceroute / проверка TCP-портов | --- ## Конфигурация Доступы задаются декларативно в `/ssh.json`. Оператор описывает профили (что и как подключать) и allowlist хостов; ассистент работает только с этими именами. Файл перечитывается на лету при изменении. ```jsonc { // Ключ по умолчанию, если профиль или вызов не задали свой. "default_key_path": "${SSH_KEY_PATH}", // Glob-паттерны разрешённых хостов. Наличие списка включает проверку, // пустой список — ad-hoc без ограничений. Держите список непустым. "allowed_hosts": ["10.0.*", "*.internal.example.com"], "profiles": [ { "alias": "prod-web", "host": "10.0.1.10", "username": "deploy", "port": 22, "key": "${PROD_SSH_KEY}" }, { "alias": "bastion", "host": "bastion.example.com", "username": "deploy" }, { "alias": "db-via-pam", "host": "pam-gateway.example.com", "username": "operator", "target": "10.0.2.20", "target_password": "${DB_TARGET_PASSWORD}" } ] } ``` Готовый шаблон — [`ssh.json.example`](ssh.json.example). | Поле профиля | Смысл | |---|---| | `alias` | Имя профиля, на которое ссылается ассистент | | `host` | Хост или IP | | `username` | Пользователь SSH | | `port` | Порт (по умолчанию `22`) | | `key` | Приватный ключ (иначе `default_key_path`, иначе системный ключ) | | `via` | Алиас jump-хоста, через который подключаться | | `target` | Хост за PAM-шлюзом (SafeInspect): шлюзу отправляется `username@target` | | `target_password` | Пароль целевого аккаунта для второй стадии аутентификации PAM | ### Переменные окружения | Переменная | Назначение | |---|---| | `FORGE_TENANT_CONFIG` | Каталог с `ssh.json`. Если не задан — режим без политики. | | `SSH_MCP_KEY_PATH` | Путь к системному приватному ключу Ed25519. | Значения в конфиге поддерживают подстановку `${VAR}` из окружения — реальные адреса и ключи в репозиторий не попадают. --- ## Безопасность - **Проваливается закрытым (fail-closed).** Если задан `allowed_hosts`, любой хост вне списка получает отказ. Битый `ssh.json` — тоже отказ. - **Креды задаёт оператор, а не модель.** Ассистент выбирает профиль по имени и не может придумать произвольный хост, ключ или адрес. - **Валидация вместо порчи.** `write` проверяет синтаксис перед записью, `edit` — после правки, и предупреждает, если файл сломан. - **Экранирование.** Все пользовательские строки, попадающие в shell-команды, проходят кавычивание и санитизацию. - **Лимиты.** Таймауты команд, ограничение размера файла и длины вывода, отдельный лимит на чтение. - **Ключи.** Каталог ключей создаётся с правами `0700`, приватный ключ — `0600`. - **Осторожно:** host key удалённого сервера не проверяется (`InsecureIgnoreHostKey`). Это осознанный компромисс для работы с динамической инфраструктурой — за целостность канала отвечает сеть. Перед публикацией убедитесь, что реальные ключи и конфиги с секретами не попали в репозиторий (см. [`.gitignore`](.gitignore)). --- ## Разработка ```bash go build ./... && go vet ./... go test -race ./... gofmt -l . # должно быть пусто ``` Требования: Go 1.27+ и доступ к приватному Go-модулю [`forge-toolkit`](https://git.totmin.ru/en2zmax/forge-toolkit) (`export GOPRIVATE=git.totmin.ru`). --- ## Лицензия Apache-2.0 — см. [`LICENSE`](LICENSE).