Files

295 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-ассистент<br/>(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-портов |
---
## Конфигурация
Доступы задаются декларативно в `<FORGE_TENANT_CONFIG>/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).