mirror of
https://github.com/Av0idD4rk/packmate.git
synced 2026-10-23 14:31:01 +00:00
256 lines
20 KiB
Markdown
256 lines
20 KiB
Markdown
# MCP для Packmate
|
||
|
||
MCP работает отдельным контейнером и предоставляет **22 инструмента с единым полным доступом**.
|
||
Любой действительный токен может использовать каждый из них, включая создание и удаление сервисов,
|
||
паттерны IGNORE, избранное и Lookback. Ролей analyst/operator/admin нет.
|
||
|
||
## Запуск
|
||
|
||
В `.env` включите MCP и задайте случайный токен длиной не менее 32 ASCII-символов без пробелов:
|
||
|
||
```dotenv
|
||
PACKMATE_MCP_ENABLED=true
|
||
PACKMATE_MCP_TOKEN=<случайный-токен>
|
||
PACKMATE_MCP_BIND=127.0.0.1
|
||
PACKMATE_MCP_PORT=65002
|
||
PACKMATE_MCP_ALLOWED_HOSTS=127.0.0.1:*,localhost:*,[::1]:*
|
||
PACKMATE_MCP_JOB_TIMEOUT_SECONDS=30
|
||
PACKMATE_MCP_JOB_MAX_MIB=32
|
||
PACKMATE_PUBLIC_URL=http://your-packmate-host:65000
|
||
```
|
||
|
||
Сгенерировать токен: `python3 -c 'import secrets; print(secrets.token_hex(32))'`.
|
||
Затем выполните обычную команду:
|
||
|
||
```bash
|
||
docker compose up --build -d
|
||
```
|
||
|
||
Endpoint: `http://127.0.0.1:65002/mcp`, транспорт **Streamable HTTP**.
|
||
Клиент должен передавать `Authorization: Bearer <токен>` в каждом запросе.
|
||
Токен задаётся в настройках MCP-клиента, а не в сообщениях агенту. Поле headers/переменная окружения
|
||
для него зависит от конкретного клиента. Клиент без возможности передавать Bearer header не поддерживается.
|
||
Встроенного OAuth-сервера нет.
|
||
|
||
Для подключения с другой машины можно использовать SSH-туннель:
|
||
|
||
```bash
|
||
ssh -N -L 65002:127.0.0.1:65002 user@your-packmate-host
|
||
```
|
||
|
||
Для прямого подключения через HTTPS reverse proxy настройте bind/allowed hosts под его адрес.
|
||
Не ставьте произвольный `*` в allowed hosts. Browser Origin-запросы к MCP не разрешены;
|
||
обычный серверный MCP-клиент работает без Origin. LIVE-захват рассчитан на Linux Docker Engine
|
||
с host network; Docker Desktop видит сеть своей VM, а не все интерфейсы Windows.
|
||
|
||
По умолчанию MCP отключён: `/healthz` отвечает состоянием, остальные запросы получают 503.
|
||
Отключение MCP не отключает Packmate. Активный MCP без корректного токена отказывается запускаться.
|
||
|
||
## Сервисы и паттерны видны людям
|
||
|
||
Создавайте сервисы до начала анализа трафика: Packmate сохраняет стримы только известных сервисов.
|
||
Все изменения MCP проходят через обычные backend-сервисы с их WebSocket-уведомлениями.
|
||
Созданный агентом сервис появляется в навбаре, паттерн — в меню Patterns.
|
||
FIND подсвечивает совпадения в новых пакетах; для истории используйте `start_lookback`.
|
||
IGNORE означает удаление/несохранение будущих совпавших стримов, а не только скрытие в UI.
|
||
|
||
Пример аргументов `create_service`:
|
||
|
||
```json
|
||
{
|
||
"port": 8080,
|
||
"settings": {
|
||
"name": "Shop",
|
||
"http": true,
|
||
"urldecode_http_requests": true,
|
||
"merge_adjacent_packets": true,
|
||
"parse_web_sockets": false,
|
||
"decrypt_tls": false
|
||
}
|
||
}
|
||
```
|
||
|
||
Пример аргументов `create_pattern`:
|
||
|
||
```json
|
||
{
|
||
"settings": {
|
||
"name": "Outgoing flags",
|
||
"match": {"target": "content", "type": "regex", "value": "FLAG\\{[A-Za-z0-9_]+\\}"},
|
||
"action": "FIND",
|
||
"direction": "OUTPUT",
|
||
"service_port": 8080,
|
||
"color": "#ff9800"
|
||
}
|
||
}
|
||
```
|
||
|
||
Для IP: `target` равен `source_ip`, `destination_ip` или `either_ip`, `type` — `regex`.
|
||
Паттерны создаются включёнными. При повторе идентичного запроса возвращается существующий
|
||
неудалённый объект; выключенный существующий паттерн автоматически не включается.
|
||
Это проверка текущих данных перед созданием, не гарантия exactly-once при одновременной записи
|
||
из нескольких независимых приложений. После ошибки сети проверьте список объектов.
|
||
|
||
`update_pattern` меняет имя и цвет. Для изменения выражения/направления/действия создайте новый
|
||
паттерн и отключите старый: так исторические совпадения не получают новое значение задним числом.
|
||
`update_service` принимает полную конфигурацию декодирования, а не частичный patch.
|
||
|
||
## Инструменты
|
||
|
||
| Инструменты | Назначение |
|
||
| --- | --- |
|
||
| `get_overview` | Захват, потери, очередь, настройки retention, сервисы, лимиты анализа |
|
||
| `search_traffic` | Самостоятельный поиск по сохранённым сообщениям без создания паттерна |
|
||
| `aggregate_traffic` | Число совпавших сообщений по сервисам, адресам или минутам |
|
||
| `get_stream`, `read_packet` | Метаданные стрима, страницы сообщений и фрагменты payload |
|
||
| `set_favorites` | Защита находок от retention, с подтверждением доступных ID |
|
||
| `list_services`, `create_service`, `update_service`, `delete_service` | Общие с UI сервисы |
|
||
| `list_patterns`, `validate_pattern`, `create_pattern`, `update_pattern`, `set_pattern_enabled`, `delete_pattern` | Общие с UI паттерны |
|
||
| `start_lookback` | Сохранение исторических совпадений и обновление UI |
|
||
| `get_job`, `get_job_results`, `cancel_job`, `delete_job` | Прогресс, результаты и управление задачами |
|
||
| `start_pcap_import` | Запуск уже настроенного файла в режиме FILE |
|
||
|
||
В этой версии нет изменения retention на лету, остановки/перезапуска LIVE capture, загрузки
|
||
новых PCAP/TLS-ключей, экспорта исходного PCAP и журнала событий с долговечным курсором.
|
||
Это следующие этапы API; ограничений по ролям на существующие инструменты нет.
|
||
|
||
## Поиск и задачи
|
||
|
||
```json
|
||
{
|
||
"query": {
|
||
"last_minutes": 5,
|
||
"service_ports": [8080],
|
||
"direction": "outgoing",
|
||
"client_ip": {"type": "cidr", "value": "10.60.0.0/16"},
|
||
"match": {"target": "content", "type": "regex", "value": "FLAG\\{[^}]+\\}"}
|
||
},
|
||
"limit": 100,
|
||
"context_bytes": 160
|
||
}
|
||
```
|
||
|
||
`search_traffic`, `aggregate_traffic` и `start_lookback` сразу возвращают `job_id`.
|
||
Вызывайте `get_job` примерно раз в секунду, затем `get_job_results` с `offset` и `limit`.
|
||
Результаты содержат ссылки на нужный стрим в браузере. Одна выдача — максимум 50 результатов.
|
||
|
||
Время фильтрует **начало сохранённого стрима**. `last_minutes` фиксируется при создании задачи;
|
||
эффективные `from_ms`, `to_ms`, `snapshot_ms` возвращаются в `query`.
|
||
Все абсолютные времена — Unix milliseconds UTC. Окно — максимум 24 часа.
|
||
`source_ip`/`destination_ip` относятся к сообщению, `client_ip` вычисляется с учётом incoming.
|
||
Для адресов доступны exact/CIDR/regex. `pattern_id` отбирает стримы с уже сохранённым паттерном.
|
||
|
||
Поиск выполняется по сохранённому преобразованному содержимому. Оно может быть распаковано,
|
||
декодировано или склеено настройками сервиса. Между отдельными сохранёнными сообщениями regex
|
||
не переносится. Текст — UTF-8 с заменой некорректных последовательностей, точные байты — hex/base64.
|
||
Конец диапазона в MCP — exclusive; текстовые смещения измеряются Unicode codepoints,
|
||
бинарные — байтами. В UI остаются его прежние позиции совпадений.
|
||
|
||
Проверяйте `coverage_complete`, `scan_exhausted`, `reason`, `skipped_oversize_packets` и `unavailable_packets`.
|
||
Завершившаяся задача с `result_limit` или `byte_budget` проверила только часть диапазона.
|
||
Контент сообщений больше 64 KiB не обрезается для поиска, а пропускается с отдельным счётчиком:
|
||
иначе regex с `$` мог бы дать ложное совпадение на конце обрезанного фрагмента.
|
||
Большой payload можно читать последовательно через `read_packet`.
|
||
|
||
Для продолжения частичного поиска передайте эффективный `query` предыдущей задачи и замените
|
||
его `cursor` на `next_scan_cursor`. Порядок: `(stream_timestamp, stream_id, packet_id)`.
|
||
Это read-committed чтение живой БД, а не замороженный snapshot. Cleaner может удалять стримы
|
||
между страницами; незакоммиченные или ещё собираемые соединения не входят в результаты.
|
||
`coverage_complete` означает отсутствие известных пропусков в просмотренной доступной выборке,
|
||
а не доказательство наличия всей истории сетевого трафика.
|
||
|
||
Lookback работает только для FIND; применяет сервис и направление самого паттерна.
|
||
Совпадения записываются порциями до 16 сообщений. Повторное применение не дублирует найденные
|
||
позиции. MCP Lookback сохраняет до 256 совпадений на сообщение, в том числе для старых literal-паттернов.
|
||
Уже начатая очистка, занятая блокировка, слишком большой payload или более 2048 старых
|
||
совпадений на сообщении дают `unavailable_packets`. Отмена останавливает дальнейшую работу;
|
||
выполненные записи сохраняются, а текущая транзакция могла успеть закоммититься.
|
||
Если ответ на запись Lookback не получен из-за отмены, таймаута или ошибки, курсор остаётся перед
|
||
неподтверждённой страницей, а сообщения учитываются в `unavailable_packets`. Продолжение с этим
|
||
курсором повторит страницу без дубликатов, вместо пропуска потенциально незаписанных подсветок.
|
||
|
||
Одновременно доступны две задачи, хранится максимум 32 задачи, TTL результатов — 15 минут
|
||
после завершения. Для освобождения места используйте `delete_job`. Задачи и результаты хранятся
|
||
в памяти MCP и теряются при его перезапуске; созданные сервисы/паттерны и Lookback-совпадения остаются в БД.
|
||
|
||
## Ограничение нагрузки
|
||
|
||
MCP имеет отдельный контейнер (1 CPU, 512 MiB), ограниченные кеши regex и две задачи анализа.
|
||
Backend читает через отдельный пул из двух read-only соединений с SQL timeout 2 секунды.
|
||
Страницы содержат максимум 32 сообщения и 64 KiB на сообщение. В MCP используется 16 сообщений
|
||
на запрос, между страницами есть пауза. При занятых слотах возвращается BUSY.
|
||
Бюджет задачи по умолчанию 30 секунд / 32 MiB прочитанного payload с оценкой метаданных.
|
||
Диск и PostgreSQL общие с захватом, поэтому физическая изоляция нагрузки не абсолютная.
|
||
|
||
Новые MCP regex-паттерны используют RE2J при обработке трафика и RE2 при разовом поиске.
|
||
Backreferences/lookaround не поддерживаются, неподдерживаемый синтаксис отклоняется.
|
||
`validate_pattern` проверяет regex в обоих движках, включая ограничения RE2J, до сохранения.
|
||
Для новых паттернов сохраняется до 256 совпадений на сообщение. Старые Java regex сохраняют
|
||
прежний движок; MCP Lookback для них требует создания нового RE2J-паттерна.
|
||
|
||
## Проверка
|
||
|
||
Unit checks:
|
||
|
||
```bash
|
||
docker build -t packmate-mcp:dev mcp
|
||
docker run --rm -v "$PWD/mcp:/app:ro" packmate-mcp:dev python -m unittest discover -s tests -p 'test_*.py' -v
|
||
./gradlew test --tests '*SafePatternMatcherTest' --tests '*PatternMatcherConcurrencyTest'
|
||
```
|
||
|
||
Интеграционный тест создаёт отдельную сеть и отдельную БД. Команды из корня репозитория:
|
||
|
||
```bash
|
||
./gradlew bootJar
|
||
docker build -t registry.gitlab.com/packmate/packmate:latest -f docker/Dockerfile_app .
|
||
docker build -t packmate-mcp:dev mcp
|
||
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml build target
|
||
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml up --no-build -d db target packmate mcp
|
||
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml run --rm --no-deps tester
|
||
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml down -v
|
||
```
|
||
|
||
Тест вызывает настоящий MCP Streamable HTTP, проверяет уведомления браузера, живые совпадения,
|
||
IP regex, Lookback и повторное применение. Затем выполняет 3000 TCP-соединений одновременно
|
||
с regex-задачами и удалением старой выборки из 200 стримов. Проверяет сохранность новых стримов,
|
||
избранного, отсутствие drops и orphan packets, REST p99 < 500 мс.
|
||
Отчёт: `build/reports/mcp/integration.json`. Это целевой тест MCP; большой профиль на 250000
|
||
соединений из `docs/CLEANUP.md` он не заменяет.
|
||
|
||
Увеличенный профиль (`PowerShell`, из корня, после сборки образов и `bootJar` выше):
|
||
|
||
```powershell
|
||
docker compose -p packmate-mcp-capacity -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/capacity.yml build target
|
||
./mcp/tests/run-capacity.ps1
|
||
```
|
||
|
||
Профиль проверяет 2000 исходных и 50000 новых TCP-соединений с минимальной длительностью передачи 120 секунд, по 8 сообщений
|
||
по 1024 байта в каждом направлении. Сверяются хеши полного содержимого **каждого** соединения,
|
||
IP, метаданные, live-паттерны и избранное. Одновременно работают cleaner и MCP, а MCP-контейнер
|
||
перезапускается во время нагрузки. Дополнительно проверяются BUSY, отмена, бюджеты, курсор,
|
||
96 KiB binary payload и повторный Lookback старого literal-паттерна с лимитом подсветок.
|
||
Тест намеренно использует бюджеты MCP 10 секунд / 2 MiB, чтобы проверить оба ограничения.
|
||
Захват использует 8 workers; это параметр теста, а не обещание такой скорости с любыми настройками.
|
||
`MCP_STRESS_STREAMS` и `MCP_STRESS_SECONDS` меняют нагрузку. Отчёт: `build/reports/mcp/capacity.json`.
|
||
Скрипт удаляет только свой disposable Compose-проект; `-KeepContainers` оставляет его для диагностики.
|
||
|
||
Длительный прогон на 35 минут, с обычным retention 15 минут и запуском cleaner раз в 10 секунд:
|
||
|
||
```bash
|
||
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml build target
|
||
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml up --no-build -d db target packmate mcp
|
||
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml run --rm --no-deps tester
|
||
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml down -v
|
||
```
|
||
|
||
Не запускайте эти сетевые профили одновременно: они используют один тестовый subnet.
|
||
Soak создаёт пачки по 500 соединений за 10 секунд, сверяет все payload до истечения retention,
|
||
сохраняет один стрим каждой пачки в избранное и непрерывно выполняет MCP regex-поиск.
|
||
Время записей в БД не подменяется: проверяется естественное удаление старого трафика.
|
||
Отчёты: `soak.json`, `soak-samples.json`, `soak-loads.json`; текущий прогресс — `soak-progress.json`
|
||
в `build/reports/mcp`. `MCP_SOAK_SECONDS` позволяет увеличить длительность.
|
||
|
||
Фактические параметры и результаты прогона: [MCP_TEST_RESULTS.md](MCP_TEST_RESULTS.md).
|
||
`mcp/tests/deployment.py` дополнительно проверяет настоящий SDK, валидацию обоих regex-движков
|
||
и изменения общих сервисов/паттернов внутри отдельного production Compose в режиме VIEW.
|