rust-junosmcp
Единый MCP-сервер для устройств Juniper Junos и SRX на Rust
проект mechub — суверенная автоматизация сетевой безопасности
Неофициальный проект сообщества. Этот репозиторий — независимый проект, развиваемый сообществом. Он не связан с Hewlett Packard Enterprise или Juniper Networks и не одобрен, не спонсируется и не поддерживается этими компаниями. «HPE», «Juniper», «SRX», «JUNOS», «Security Director» и «Juniper Mist» — товарные знаки соответствующих владельцев. Здесь они используются только для описания совместимости программы. По вопросам поддержки и лицензирования этих продуктов обращайтесь к соответствующим поставщикам.
Сервер Model Context Protocol для устройств Juniper Junos
и SRX, написанный на Rust. Один процесс rust-junosmcp предоставляет
основные инструменты Junos и, по умолчанию, сценарии работы с безопасностью SRX
через единый реестр инструментов и одну конечную точку. Сервер совместим с
Juniper/junos-mcp-server по формату
инвентаризации и основному набору инструментов, но построен на асинхронном Rust
(rustEZ +
rustnetconf), а не на PyEZ.
Чем rust-junosmcp отличается от Juniper/junos-mcp-server
Полная совместимость с devices.json и основными инструментами — а также множество возможностей, которых нет в сервере на Python/PyEZ:
-
Безопаснее работа с конфигурацией —
commit_check_configпроверяет конфигурацию, но не применяет её; поддерживаются подтверждаемые изменения с автоматическим откатом иdiscard_candidateдля очистки изменённой кандидатной конфигурации. -
Управление жизненным циклом устройств — поэтапное обновление Junos с помощью
upgrade_junos(образ → установка → перезагрузка → проверка), передача и загрузка файлов через SCP с помощьюtransfer_file/fetch_file, команды PFE. -
Масштабирование и удобство работы — пакетное выполнение с параллельным пулом сессий (примерно в 1,7 раза быстрее), фильтры
| last N/| count, ограничения выводаmax_lines/max_bytes, псевдонимыrouter/router_name, шаблоны Jinja2. -
Транспорт и аутентификация — streamable-HTTP с отдельными для каждого токена областями доступа к маршрутизаторам и инструментам, TLS и списком разрешённых значений
Host; исходный сервер поддерживает только stdio. -
Инструменты SRX (включены по умолчанию функцией
srx) — обновление пакетов сигнатур IDP и Application-ID (проверка, загрузка, установка, откат), проверка состояния кластера шасси, лицензий и служб безопасности, сбор диагностического пакета JTAC с удалением секретов, а также просмотр политик безопасности, адресных книг, приложений и правил NAT только для чтения.
Производительность
Тестирование проведено 2026-10-05 в сравнении с Juniper/junos-mcp-server
1.1.1 и shigechika/junos-mcp 0.18.0 / 0.22.0
при одинаковой нагрузке только на чтение на тестовом устройстве vSRX (Junos 26.2R1.7): 3 запуска × 30 вызовов для каждой операции, ошибок вызовов нет.
Медианные значения по результатам запусков:
| rust-junosmcp 0.27.2 | Juniper 1.1.1 | shigechika 0.22.0 | |
|---|---|---|---|
| Холодный запуск | 6 мс | 264 мс | 392 мс |
| Пиковое потребление памяти (RSS) | 20 MiB | 129 MiB | 98 MiB |
| SSH-подключений за запуск | 1 | 121 | 1 |
show version, p50 | 216 мс | 878 мс | 228 мс |
| RPC конфигурации (ответ с отказом в доступе¹), p50 | 11 мс | 776 мс | 21 мс |
¹ У учётной записи для тестирования в режиме только чтения нет доступа к представлению конфигурации, поэтому измеряется время обмена с ответом размером около 400 байт, а не получение полной конфигурации. Подробнее см. в разделе
Полные результаты, данные о накладных расходах при работе с имитируемой целью, описание методики и инструкции по воспроизведению тестов:
Выпуск v0.10.0 — прочитайте перед обновлением
Внесены два несовместимых изменения в авторизацию, требующих действий со стороны оператора: область доступа к инструментам с подстановочным знаком (
"tools": ["*"]) больше не предоставляет доступ к 15 инструментам, способным изменять данные, а файлtokens.jsonдолжен иметь права0600, иначе сервер откажется запускаться. Новая командаtoken set-scopeпозволяет изменить области доступа токена без повторной выдачи секрета. Поэтому перед заменой бинарного файла можно ограничить области доступа на работающем сервере версии 0.9.x. Порядок действий см. в разделе «Обновление до v0.10». Безопасность: стек авторизации теперь полностью обходится безunsafe—rust-junosmcp-authиспользует общий crate mecmcp-auth, который заменяет написанное вручную обнуление секретов наzeroize, аlibc::getuid— наrustix. Кроме того, ошибка в одной некорректной записи токена больше не приводит к недоступности всего хранилища при загрузке. Текущий набор инструментов: по умолчанию — 44 инструмента, с параметром--no-default-features— только 28 инструментов Junos. Подробнее об изменениях авторизации, описанных выше, см. в примечаниях к выпуску v0.10.0.
Состав функций
v0.1 (выпущена)
- 6 инструментов:
get_router_list,gather_device_facts,execute_junos_command,
get_junos_config, junos_config_diff, load_and_commit_config.
-
Только транспорт stdio.
-
Совместимость с
devices.jsonбез дополнительных изменений (auth.type∈ {password,ssh_key}). -
Образ Docker (distroless) и архив релиза для LXC с модулем systemd.
v0.2 (выпущена)
-
Транспорт streamable-http (с опциональной поддержкой TLS через rustls).
-
Аутентификация по bearer-токену с отдельными областями доступа к маршрутизаторам и инструментам для каждого токена.
-
Горячая перезагрузка хранилища токенов по сигналу SIGHUP.
Дополнение к v0.2: PFE и пакетное выполнение (выпущено)
-
execute_junos_pfe_command— одиночный вызов оболочки PFE для явно указанной цели FPC. -
execute_junos_command_batch— выполнение N команд операционной CLI на M маршрутизаторах с параллельной обработкой маршрутизаторов, тайм-аутами для отдельных команд и, при необходимости, всего пакета. Перед выполнением проверяются блок-лист и наличие маршрутизаторов; после этих проверок выполнение продолжается даже при ошибках отдельных команд. -
Новые правила
pfe_commandsв_blocklist_defaultsи блок-листе каждого устройства. Они обрабатываются независимо отcommands.
Дополнение к v0.2: шаблоны (выпущено)
-
render_and_apply_j2_template— формирует конфигурацию из шаблона Jinja2 (встроенное содержимоеtemplate_content) и объекта JSONvars_content. Поддерживаются один маршрутизатор (router_name) или несколько (router_names), пробный запуск и полное применение конфигурации. Используются те же блок-лист и проверка формата, что и вload_and_commit_config. -
Переменные должны быть представлены объектом JSON верхнего уровня. Начиная с v0.5.2 YAML больше не поддерживается (RJMCP-SEC-002): цепочка предупреждений для
serde_yml/libyml(RUSTSEC-2025-0067/-0068) могла быть доступна через входные данные MCP, поэтому поддержку YAML удалили. -
Ограничения размера:
template_contentиvars_contentне могут превышать 64 KiB каждый. -
Строгая обработка неопределённых переменных: если переменная отсутствует, выполнение завершается ошибкой с указанием её имени вместо подстановки пустого значения.
-
Автоматическое определение формата: начальный `` на устройстве Junos. Передача файла начинается только после проверки свободного места в
/var(запасlocal_size + 32 MiB); затем проверяется SHA-256, а после передачи контрольная сумма проверяется повторно. При несовпадении файл удаляется. Поддерживается только аутентификация по SSH-ключу; для устройств с аутентификацией по паролю возвращается[code=unsupported_auth]. -
list_staged_files— всегда выводит список файлов в промежуточном каталоге хоста, а при указанииrouter_nameтакже показывает содержимое/var/tmp/на устройстве. -
Стабильные коды ошибок — при каждой ошибке передачи в сообщении
Displayуказывается понятный LLM код вида[code=...](bad_source_path,insufficient_disk,unsupported_auth,dest_exists_differs,scp_failed,connect_timeout,host_key_mismatch,host_key_revoked,verify_mismatch,outer_timeout,device_probe_failed). -
Новые параметры CLI —
--staging-dir(по умолчанию/var/lib/jmcp/staging) и--known-hosts-file(по умолчанию/etc/jmcp/known_hosts). -
Установка —
install.shсоздаёт новые файлы и каталоги на диске, принадлежащие пользователю и группеjmcp:jmcp. Подробнее см. раздел «Передача файлов» ниже. -
Число инструментов: с 11 до 13.
v0.5 (выпущена)
-
upgrade_junos— обновление ПО Junos в два вызова: подготовка, затем подтверждение. Пакет передаётся по той же схеме, что и вtransfer_file; затем выполняютсяrequest system software addи перезагрузка. Поддерживается только при автономной работе; вызов отклоняется, если для целевого маршрутизатора есть запись в пуле сессий. -
Число инструментов: с 13 до 14.
v0.6 (выпущена)
-
fetch_file— загружает файл из:/var/tmp/в промежуточный каталог хоста. Контрольная сумма SHA-256 проверяется; если локальная копия уже совпадает с исходной, загрузка пропускается. Операции для одного маршрутизатора выполняются последовательно. Зеркальная функция дляtransfer_file. -
Число инструментов: с 14 до 15.
v0.7 (выпущена)
-
commit_check_config— проверяет кандидатную конфигурацию (commit check), не применяя её: загружает конфигурацию, показывает различия, выполняет проверку, а затем отбрасывает изменения. Рабочая конфигурация не меняется. Для инструмента предусмотрена отдельная область доступа к токену в соответствии с принципом минимальных привилегий. -
discard_candidate— отбрасывает неприменённые изменения кандидатной конфигурации (rollback 0), чтобы восстановить состояние после того, как кандидатная конфигурация осталась изменённой («configuration database modified»). Рабочая конфигурация не меняется. Для инструмента предусмотрена отдельная область доступа к токену в соответствии с принципом минимальных привилегий. -
Число инструментов: с 15 до 17.
v0.8 (выпущен)
-
Объединённый сервер Junos/SRX — один бинарный файл, endpoint, процесс, инвентаризация, менеджер устройств, менеджер аренды, поверхность аутентификации и механизм перезагрузки по SIGHUP. Сборки по умолчанию предоставляют 26 инструментов; параметр
--no-default-featuresсохраняет набор из 17 инструментов только для Junos. -
Средства управления эксплуатацией — ограничение параллельных операций для каждого маршрутизатора, лимиты сессий и RPS для каждого токена, строгий глобальный контроль допуска сессий, ограниченные метрики Prometheus и дополнительная передача аудита в нативный journald.
-
Безопасность и упаковка — результаты списка маршрутизаторов с учётом области доступа, межпроцессные аренды для деструктивных операций, поддержка SCP в контейнерах, запуск образа без прав root, проверки состояния процесса и проверка обновления пакетов.
-
Миграция с разделённого сервиса — при обновлении удаляются
rust-srxmcp, его модуль systemd и порт 30032, а пакеты поддержки сохраняются. Для v0.8.0 принимаются устаревшие псевдонимыJMCP_SRX_*.
v0.9 (выпущен)
-
Инструмент
rollback_config— загружает архив отката Junos (rollback N, от 0 до 49) в конфигурацию-кандидат и позволяет предварительно просмотреть его (по умолчанию) или применить, в том числе с подтверждённой фиксацией. Количество инструментов: 26 → 27 (17 → 18 только для Junos). -
Корректная фильтрация через конвейер — фильтры
| match/| exceptприменяются на стороне сервера; RPC `` незаметно отбрасывал их, поэтому запросы конфигурации с фильтрами возвращали полную конфигурацию (незаметный ложноотрицательный результат аудита). -
Более безопасные команды для работы с конфигурацией —
junos_config_diffподдерживаетrollback 0(сравнение конфигурации-кандидата с активной);commit_check_configвозвращает один из трёх вариантовoutcome(valid/invalid/check_failed), чтобы результат неопределённой проверки кластера с несколькими RE нельзя было принять за некорректную конфигурацию;discard_candidateснимает блокировку с изменённой конфигурации-кандидата без блокировки. -
Более понятная диагностика — если RPC-проверка состояния служб SRX завершается ошибкой, результатом будет
error, а неnot_configured; в журнале указывается, почему не удалось определить маршрутизатор: имя неизвестно или не входит в область доступа (ответ клиенту не меняется). -
Безопасность — SSH-транспорт переведён с prerelease-версии RustCrypto на russh 0.62; количество криптографических пакетов с суффиксом
-rcсокращено с 13 до 3.
v0.10 (выпущен)
-
Шаблонная область доступа к инструментам больше не включает инструменты записи (несовместимое изменение) —
"tools": ["*"]охватывает все инструменты только для чтения, но ни один из 15 инструментов, способных изменять данные. Предоставление прав на запись теперь всегда требует явного решения с указанием конкретных инструментов. См. раздел «Области доступа к инструментам и инструменты записи». -
Файл
tokens.jsonдолжен иметь режим0600(несовместимое изменение) — сервер отказывается запускаться, если файл токенов доступен группе или всем пользователям. В сообщении указываются uid владельца, режим доступа, uid вызывающего пользователя и командаchmod, которую нужно выполнить. -
token set-scope— позволяет изменить область доступа токена без повторной выдачи его секрета. Благодаря этому область можно сузить перед обновлением бинарного файла без перерыва в работе клиента. -
Устойчивое чтение токенов — имена областей доступа проверяются при записи, а не при чтении. Поэтому одна устаревшая запись больше не приводит к отключению всего хранилища.
-
Безопасность —
rust-junosmcp-authтеперь представляет собой тонкую обёртку для повторного экспорта общего crate mecmcp-auth и не содержитunsafe; вместо самописного обнуления секретов используетсяzeroize, а вместоlibc::getuid—rustix. Количество инструментов не изменилось (27 / 18).
v0.11 (выпущен)
- Четыре новых инструмента SRX только для чтения —
srx_list_policies(политики безопасности с учётом контекста from-zone/to-zone, включая глобальные политики и, при необходимости, счётчики срабатываний для каждой политики),srx_resolve_addressиsrx_resolve_application(разрешение адресов из address-book и приложений/application-set, включая предопределённые значенияjunos-*, с рекурсивным разрешением вложенных наборов и явным отклонением циклических ссылок — без зацикливания и незаметно неполных ответов), а такжеsrx_list_nat_rules(правила source NAT, destination NAT и static NAT, каждое — независимо). Данные address-book и приложений извлекаются из конфигурации с помощью вручную сформированного RPCget-configurationс фильтрацией поддерева, посколькуcall()вrustezподдерживает только плоские аргументы «ключ/значение». Имена в политиках и правилах NAT (адреса, приложения) намеренно возвращаются без разрешения —srx_resolve_address/srx_resolve_applicationвыполняются отдельно, по явному запросу. Результаты всех четырёх инструментов разбиваются на страницы и ограничиваются по объёму; при этом возвращается явный флагtruncated, а не выполняется незаметное усечение. Количество инструментов: 37 → 41 (с 9 до 13 инструментов SRX; сборка только для Junos не меняется и по-прежнему содержит 27 / 18 инструментов, поскольку эти инструменты включены через функциюsrx, активную по умолчанию).
v0.12 (ещё не выпущен)
-
Подтверждённая фиксация включена по умолчанию (несовместимое изменение) —
load_and_commit_config,rollback_config(commit=true),render_and_apply_j2_templateиapply_junos_change_setтеперь выполняютcommit confirmed, если вызывающий явно не отключит его, указавconfirm_timeout_mins: 0. Ранее по умолчанию выполнялась обычная безусловная фиксация: если подготовленное моделью изменение обрывало доступ к управлению, ничто не возвращало устройство к прежнему состоянию. См. раздел «Подтверждённая фиксация». -
--commit-confirm-default-mins(по умолчанию10) задаёт общесерверный период подтверждения, если при вызове не указанconfirm_timeout_mins; значение, заданное для конкретного вызова, имеет приоритет. -
confirm_commit— новый инструмент записи, который отправляет подтверждающую фиксацию для любого из четырёх перечисленных выше сценариев и отменяет ожидающий автоматический откат. -
Отказ от подтверждённой фиксации фиксируется в событии аудита как
commit_confirmed=false. -
Количество инструментов: 43 → 44 (27 → 28 только для Junos).
Защитные правила списка блокировки (v0.2)
В devices.json можно добавить необязательный блок _blocklist_defaults, а также необязательное поле blocklist в запись каждого устройства. Правила используют простые шаблоны с подстановочными знаками (*, ?) и действие "deny" или "allow". Приоритет имеет наиболее конкретное совпадение; если совпадают правила устройства и значения по умолчанию верхнего уровня, преимущество отдаётся правилам устройства. Пример см. в devices-template.json, а полное описание — в docs/superpowers/specs/2026-05-04-blocklist-guardrails-design.md.
Список правил pfe_commands действует независимо: запрет для commands не ограничивает execute_junos_pfe_command, и наоборот. Используйте его, чтобы ограничить ввод для PFE (например, set *), не затрагивая операционный CLI.
Список блокировки применяется к execute_junos_command и load_and_commit_config.
Если для устройства действуют какие-либо правила конфигурации, для load_and_commit_config необходимо задать config_format равным set; в этом случае запросы с данными форматов text и xml отклоняются на предварительной проверке.
Примечание о совместимости: файлы с
_blocklist_defaultsилиblocklistдля отдельных устройств несовместимы с форматом инвентаризации Juniper/junos-mcp-server. Файлы без этих полей сохраняют полную совместимость.
Режим авторизации execute_junos_command: allowlist (по умолчанию) или blocklist
_blocklist_defaults (и в дальнейшем только этот ключ — см. ограничение для отдельных устройств ниже) может содержать параметр mode со значением "allowlist" или "blocklist". Он определяет правила для execute_junos_command, execute_junos_command_batch, execute_junos_pfe_command, а также get_junos_config (эта команда выполняет show configuration [path] [| display ] и использует тот же список разрешённых или запрещённых команд — см. раздел «Формат вывода конфигурации и режим загрузки» ниже). Домен config, используемый в load_and_commit_config, остаётся списком запрещённых команд с разрешением по умолчанию независимо от значения mode.
-
allowlist(запрет по умолчанию; используется для новых конфигураций) — команда запрещается, если она не соответствует буквальному префиксу, разбитому на токены по пробелам, из спискаallow(шаблоны glob отклоняются при загрузке, а не просто игнорируются). Каждый этап команды после|должен отдельно соответствовать префиксу изallowed_pipes; в противном случае команда отклоняется целиком. Запрещённые метасимволы оболочки (;, перенаправления, обратные кавычки, символы новой строки) блокируются сразу, ещё до проверки префикса. Спискиallowиallowed_pipesобъединяются так же, как сейчас объединяются правила запретаcommands:_blocklist_defaults∪ собственный список устройства. Дополнениеallowдля отдельного устройства не распространяется на остальные устройства: для каждого устройства компилируется собственная политика разрешений.Для
execute_junos_pfe_commandпредусмотрена отдельная пара ключей:pfe_allow/pfe_allowed_pipes. Они объединяются по тому же принципу, что иallow/allowed_pipes(значения по умолчанию ∪ значения устройства). Запись вallowне разрешает команды PFE, как и запись вpfe_allowне разрешает обычные команды: эти домены никак не связаны. Если в конфигурации заданallow, но не заданpfe_allow, в режиме allowlist все команды PFE будут отклоняться. Это ожидаемое поведение с запретом по умолчанию для ненастроенного домена, а не ошибка. Чтобы использоватьexecute_junos_pfe_command, добавьте списокpfe_allow. -
blocklist(разрешение по умолчанию; устаревший режим) — поведение до MEC-93: команда запрещается, только если соответствует шаблону glob из списка запрещённых командcommands(илиpfe_commands); все остальные команды разрешены.
Допустимые символы (MEC-1337). В режиме allowlist команда может содержать только печатные символы ASCII. Разделителем токенов может быть только обычный пробел ASCII (U+0020). Любой другой символ сразу приводит к отказу (forbidden_metachar), так же как при существующей проверке на ;, перенаправления, обратные кавычки и символы новой строки.
Переход на новый режим: файл devices.json, содержащий правила запрета commands/pfe_commands, но не содержащий ключ mode, загружается в режиме blocklist. При запуске записывается одно предупреждение WARN о том, что режим blocklist разрешает команды по умолчанию, со ссылкой на этот раздел. Если в файле нет _blocklist_defaults или это только что созданный пример конфигурации, он загружается в режиме allowlist. Параметр mode допустим только в _blocklist_defaults. Если задать его в blocklist отдельного устройства, при загрузке возникнет ошибка: базовый движок политик выбирает режим команд для всего файла, а переопределение для одного устройства не будет действовать и может остаться незамеченным.
При каждом отказе — в режиме allowlist или blocklist — через существующий механизм аудита записывается событие с постоянным кодом причины: not_allowlisted, pipe_not_allowlisted, forbidden_metachar или устаревший blocked.
Минимальный начальный список разрешённых команд только для чтения:
"_blocklist_defaults": {
"mode": "allowlist",
"allow": [
"show version",
"show interfaces",
"show route",
"show security policies",
"show chassis"
],
"pfe_allow": [
"show cos"
]
}
Формат вывода конфигурации и режим загрузки
Для get_junos_config можно указать необязательный параметр format: text (по умолчанию, без изменений), set, xml или json. Каждый формат формируется на устройстве с помощью соответствующего модификатора CLI Junos | display . Поэтому вывод проходит через ту же проверку политик, список запрещённых команд и ограничение размера вывода, что и формат text. Для новых форматов отдельный путь обработки не предусмотрен.
{ "router_name": "core-1", "config_path": "system services", "format": "set" }
В режиме mode: allowlist для get_junos_config действует список разрешённых команд commands; отдельного правила для этой команды нет. Сформированная команда выглядит так: show configuration [config_path] [| display set|xml|json]. Она проходит ту же проверку, что и любой другой запрос execute_junos_command: префикс show configuration должен присутствовать в allow, а при выборе формата, отличного от text по умолчанию, префикс display set, display xml или display json должен присутствовать в allowed_pipes. В начальный список разрешённых команд из devices-template.json не входит show configuration. Поэтому при копировании шаблона без изменений вызовы get_junos_config будут полностью отклоняться. Это безопасно благодаря запрету по умолчанию, но об этом легко забыть. Чтобы разрешить вызовы, добавьте "show configuration" в allow, а нужные варианты display — в allowed_pipes. Учтите, что это открывает модели доступ ко всей текущей конфигурации, включая хешированные секреты.
Для load_and_commit_config, render_and_apply_j2_template и параметра payload каждого действия в create_junos_change_set предусмотрен необязательный mode: merge (по умолчанию, без изменений), replace или override.
Режим override заменяет всю конфигурацию-кандидат и представляет собой операцию с наибольшим потенциальным воздействием из доступных на этом сервере. load_and_commit_config и render_and_apply_j2_template выполняют commit непосредственно в рамках того же вызова, без проверки со стороны второго субъекта, поэтому режим override для них запрещён. Он доступен только через процесс работы с набором изменений (create_junos_change_set → approve_junos_change_set → apply_junos_change_set), в котором второй субъект должен одобрить план до выполнения commit.
Для config_format=set (конфигурация в формате configuration-set, то есть список команд set/delete) в Junos не предусмотрено действие override на уровне протокола. Такое сочетание отклоняется до отправки любого RPC-запроса — на всех путях, включая работу с наборами изменений.
Подтверждённый commit (v0.3; включён по умолчанию начиная с v0.12)
load_and_commit_config, rollback_config (с параметром commit=true), render_and_apply_j2_template и apply_junos_change_set по умолчанию выполняют commit через Junos commit confirmed. Если изменение не подтвердить в течение заданного времени, маршрутизатор автоматически откатит его. Поэтому при неудачном изменении, из-за которого пропадает доступ к управлению, конфигурация восстановится автоматически, и выезд специалиста не потребуется.
По умолчанию используется интервал, заданный серверным флагом --commit-confirm-default-mins (по умолчанию 10 минут; проверка значения такая же, как для параметра вызова). Параметр confirm_timeout_mins, заданный для отдельного вызова, переопределяет это значение только для соответствующего commit:
{
"router_name": "core-1",
"config_text": "set interfaces ge-0/0/0 description test",
"confirm_timeout_mins": 10,
"commit_comment": "safe change with rollback window"
}
Ответ:
{
"success": true,
"diff": "[edit interfaces ge-0/0/0]\n+ description test;",
"confirmed": true,
"rollback_in_minutes": 10,
"rollback_deadline_unix": 1780000600,
"message": "Commit confirmed: auto-rollback in 10 minutes unless confirmed. Send another commit to confirm."
}
В ответе со статусом от apply_junos_change_set также указывается rollback_deadline_unix.
Отключить commit-confirmed для отдельного вызова можно, передав confirm_timeout_mins: 0. В этом случае выполняется обычный безусловный commit, а в событии аудита фиксируется commit_confirmed=false, чтобы это исключение можно было отследить позднее.
Чтобы подтвердить commit и отменить запланированный автоматический откат, вызовите инструмент confirm_commit, указав имя устройства. Он выполняет подтверждающий commit так же, как confirm_junos_change_set в процессе работы с набором изменений. Отправка другого вызова load_and_commit_config (или rollback_config / render_and_apply_j2_template) также подтверждает изменение: Junos считает любой commit для конфигурации-кандидата подтверждением, независимо от того, какой инструмент его выполнил. Пока действует интервал commit-confirmed, upgrade_junos откажется продолжать операцию (commit_confirmed_active), чтобы не перезагружать устройство, конфигурация которого ещё может быть автоматически отменена.
Передача файлов (transfer_file / fetch_file / list_staged_files)
transfer_file отправляет файл из подготовленного на хосте каталога в /var/tmp/ на
устройстве Junos, используя встроенный клиент SCP1 из mecmcp-scp через SSH exec channel —
тот же сетевой протокол, который принудительно использует OpenSSH при вызове scp -O,
поскольку Junos отключает SFTP через SSH.
Внешний процесс scp не запускается (#212). Операция идемпотентна по SHA-256:
если удалённый файл уже существует и его контрольная сумма совпадает, вызов возвращает
status: "skipped". Чтобы перезаписать файл, если контрольные суммы различаются,
передайте force: true.
fetch_file выполняет обратную операцию: загружает файл из /var/tmp/ на
устройстве Junos в каталог подготовки на хосте, используя тот же встроенный механизм SCP1.
Операция идемпотентна по SHA-256: если локальный файл уже существует и его контрольная
сумма совпадает, вызов возвращает status: "skipped". Для fetch_file действуют те же
правила последовательного выполнения операций для каждого маршрутизатора и повторной
проверки SHA-256 после передачи, что и для transfer_file.
Аутентификация: только с помощью SSH-ключа. Устройства с auth.type = "password" отклоняются
с ошибкой [code=unsupported_auth]. Добавьте SSH-ключ на устройство и укажите путь к нему
в auth.private_key_path в devices.json.
Файлы и каталоги на диске:
| Путь | Назначение | Режим по умолчанию | Владелец |
|---|---|---|---|
/var/lib/jmcp/staging/ | Каталог на хосте для файлов, ожидающих передачи | 0750 | jmcp:jmcp |
/etc/jmcp/known_hosts | Файл SSH known_hosts, используемый при каждой отправке | 0644 | jmcp:jmcp |
/var/lib/jmcp/device-leases | Общие блокировки деструктивных операций Junos/SRX | 0700 | jmcp:jmcp |
Задайте другие пути при запуске с помощью параметров --staging-dir , --known-hosts-file
и --device-lease-dir . Сервисы Junos и SRX должны использовать один и тот же
каталог блокировок устройств.
Политика проверки ключа хоста (v0.5.2+): по умолчанию проверка ключа хоста выполняется строго —
ключи неизвестных устройств отклоняются. Файл known_hosts должен
существовать до первого вызова transfer_file или upgrade_junos, иначе
инструмент вернёт ошибку [code=known_hosts_missing]. Заполните его заранее с помощью
входящего в комплект вспомогательного скрипта:
scripts/scan-known-hosts.sh --inventory /etc/jmcp/devices.json \
--known-hosts /etc/jmcp/known_hosts
Для лабораторного использования или первого подключения передайте параметр
--ssh-accept-new-host-keys, чтобы включить режим OpenSSH accept-new (TOFU):
при первом подключении ключи неизвестных хостов сохраняются в known_hosts, но если
впоследствии хост предъявит другой ключ, подключение всё равно будет отклонено.
Это правило одинаково применяется к transfer_file / upgrade_junos (scp) и NETCONF SSH.
--ssh-insecure-accept-any-host-key (только для лабораторного использования): полностью отключает
проверку ключа хоста для NETCONF SSH и scp
(transfer_file / upgrade_junos) — ключи не сохраняются в known_hosts,
несовпадения не обнаруживаются, защита от атак типа «человек посередине» отсутствует. Параметр
нельзя использовать одновременно с --ssh-accept-new-host-keys. При запуске сервера
выводится заметное предупреждение, а событие записывается в журнал аудита. Никогда не используйте
этот параметр для производственных устройств; вместо него используйте
--ssh-accept-new-host-keys, который безопасно обеспечивает поведение TOFU.
list_staged_files возвращает содержимое каталога подготовки на хосте. Если указан
router_name, инструмент также выполняет на устройстве команду file list /var/tmp/ detail
и включает полученные записи в device_files.
Безопасность пути к исходному файлу: source_path должен содержать только имя файла (без /, без \,
без .., не должен начинаться с точки и должен быть длиной не более 255 байт). Путь разрешается
относительно --staging-dir и не может вести за его пределы.
Предварительные проверки: перед передачей transfer_file выполняет команду
show system storage no-forwarding и отказывается отправлять файл, если свободное место в /var
меньше размера локального файла плюс 32 MiB.
Проверка после передачи: если не передан параметр verify: false, контрольная сумма файла
на устройстве вычисляется повторно с помощью команды file checksum sha-256 /var/tmp/.
При несовпадении файл удаляется.
Длительные операционные команды
Каждый инструмент MCP предоставляет параметр timeout для отдельного вызова; значение
по умолчанию — 360 с. Это единственное ограничение длительности операции, видимое пользователю:
внутреннему объекту rustez::Device при подключении задаётся тайм-аут RPC в 1 час, поэтому
команды, выполнение которых может занимать много минут (request system software add,
request support information, request system snapshot и т. д.), не будут незаметно прерваны.
Если требуется выполнить операцию длительностью более 1 часа, разделите её на
этапы либо запустите её на устройстве в фоновом режиме и отдельно проверяйте
завершение.
Примечание: если после длительного RPC устройство перезагружается,
сеанс NETCONF, разумеется, прерывается. При следующем вызове пул сеансов
успешно устанавливает соединение заново.
Предупреждение о безопасности
Этот сервер позволяет LLM выполнять команды и отправлять изменения конфигурации на ваши
устройства Junos. Перед развёртыванием прочитайте уведомление о безопасности Juniper/junos-mcp-server.
Те же предупреждения относятся и к этому серверу.
-
Предпочитайте аутентификацию с помощью SSH-ключей, а не паролей.
-
Проверяйте конфигурации, прежде чем разрешать выполнение инструментов фиксации изменений.
-
Ограничьте сетевой доступ к серверу MCP.
-
Не развёртывайте сервер в недоверенных сетях.
-
Установите для
devices.jsonправа доступа0600— в этом файле содержатся учётные данные SSH. -
Перед отправкой вызывающему компоненту результаты
get_junos_config,junos_config_diffи других инструментов, возвращающих конфигурацию устройства или вывод команд, очищаются от секретов: значения, соответствующие известным шаблонам секретных данных, включая значения Junos## SECRET-DATA(строки вида$9$..., которые обратимо зашифрованы с помощью симметричного шифра Juniper, а не хешированы — любой, у кого есть главный ключ или парольная фраза устройства, может восстановить исходный текст), предварительные ключи IKE, секреты RADIUS/TACACS и сообщества SNMP заменяются маркером. Структура, имена хостов и несекретные значения сохраняются. Такая очистка — лишь наилучшая попытка (список запрещённых значений и дополнительная проверка формата значений), а не гарантия; поэтому разрешайте доступ к этому инструменту только доверенным токенам. -
Для
reload_devicesпараметрfile_nameдолжен содержать относительный путь, который разрешается внутри исходного каталога--device-mapping(начиная с v0.5.2). Абсолютные пути, обход каталогов с помощью..и символические ссылки, ведущие за пределы каталога инвентаризации, отклоняются. -
Текстовые поля ввода (
command,config_text,template_content,pfe_command) ограничены размером 1 MB. В пакетных списках допускается не более 100 маршрутизаторов и 50 команд.
--lab-mode
--lab-mode отменяет второе обязательное требование к наборам изменений. Режим предназначен для лабораторных сред с одним оператором, где контроль двумя людьми сложно реализовать. По умолчанию он выключен. Его нельзя включать на серверах, управляющих устройствами в production-среде.
Что меняется и что остаётся без изменений:
-
Наборы изменений автоматически получают одобрение при создании. Отдельного этапа одобрения нет, а процесс по-прежнему состоит из планирования и применения — как в production-среде.
-
Планирование, вычисление дайджеста плана, обнаружение дрейфа конфигурации и повторная проверка перед применением по-прежнему выполняются. Lab mode отменяет участие второго проверяющего, но не саму запись об изменении.
-
Одобрение не приписывается фиктивному согласующему. Для набора изменений, созданного с отменой требования, записывается
approver: nullиapproval_waiver: "lab-mode". Такая запись криптографически отличается от подлинного одобрения двумя людьми, и впоследствии её нельзя переименовать. Это важно, если потребуется доказать, какие изменения действительно прошли проверку с разделением обязанностей. -
При каждом запуске сервера он выводит заметное предупреждение, если режим включён.
Для одиночного тестирования операций записи без отмены контроля можно создать два токена с разными именами: один использовать для планирования, а другой — для одобрения. Однако такой подход слабее, чем кажется: оба токена принадлежат одному человеку, поэтому «проверка двумя людьми» на деле сводится к тому, что оператор одобряет собственный план под другим именем. Субъект определяется именем токена, и самоодобрение запрещено, поэтому такой способ честно проверяет последовательность вызовов API планирование → одобрение → применение, но не обеспечивает реальной проверки вторым человеком.
В mec mcp 0.27.0 добавлен процесс проверки одобрения человеком (MEC-994/MEC-995), который устраняет этот недостаток: личность согласующего связывается с новой аутентификацией через IdP, а не с именем токена.
- При создании привяжите токен владельца к личности в IdP:
token add --oidc-issuer https://idp.example.com --oidc-subject alice@example.com ....
-
Запустите сервер с параметрами
--oidc-issuer,--oidc-audienceи--require-verified-approver. Также укажите--approval-digest-key-file: в строгом режиме этот параметр обязателен, чтобы поля проверенного согласующего нельзя было незаметно подменить. -
Для одобрения используйте mecmcp-approve. Эта утилита выполняет полноценную аутентификацию через OIDC (по умолчанию с PKCE) и передаёт полученное утверждение в заголовке
Mecmcp-Approver-Assertion:
mecmcp-approve --server-url https://junos01.example:8443/mcp --approve-tool approve_junos_change_set --oidc-issuer https://idp.example.com --oidc-client-id mecmcp-approve --arg change_set_id=... --arg device=... --arg expected_digest=....
- В строгом режиме координатор отклоняет одобрение, если проверенный субъект совпадает с субъектом, к которому привязан владелец. Один и тот же человек не может выступать обеими сторонами проверки, даже если у него несколько токенов с разными именами.
Описанный выше обходной вариант с двумя токенами по-прежнему подходит для функционального тестирования последовательности планирование → одобрение → применение, если IdP недоступен. Но важно понимать его назначение: он позволяет одному оператору проверить работу API, а не получить одобрение.
Включение режима
Добавьте --lab-mode в файл service unit. При установке пакета используйте drop-in для systemd, а не редактируйте установленный файл: иначе обновление может незаметно удалить внесённые изменения.
sudo systemctl edit rust-junosmcp
При замене ExecStart необходимо указать его целиком. Скопируйте команду из установленного файла и добавьте флаг, не сокращая её. Если опустить другие параметры, вместе с включением lab mode можно случайно отключить структурированный аудит или редактирование HMAC:
[Service]
# Перед заменой очистите установленный ExecStart: иначе systemd добавит новую команду к существующей.
ExecStart=
ExecStart=/usr/local/bin/rust-junosmcp \
--device-mapping /etc/jmcp/devices.json \
--transport streamable-http \
--host 127.0.0.1 \
--port 30030 \
--tokens-file /etc/jmcp/tokens.json \
--audit-format json \
--audit-journald \
--audit-redact devices=hmac \
--audit-hmac-key-file /etc/jmcp/audit-hmac.key \
--lab-mode
Перед применением сверьте команду с packaging/systemd/rust-junosmcp.service: именно установленный файл является источником актуальных параметров, а этот пример со временем может устареть.
sudo systemctl daemon-reload && sudo systemctl restart rust-junosmcp
Убедитесь, что режим включился. Предупреждение при запуске помечено target: "audit". Чтобы увидеть записи журнала systemd-службы, выполните поиск текста о lab mode с достаточными правами:
sudo journalctl -u rust-junosmcp -b | grep -i "lab mode"
Ожидаемый вывод:
lab mode enabled: change sets are approved on creation with no second principal. Records carry approval_waiver=lab-mode. Do not run this against production devices.
Если вывода нет, режим выключен. Однако команда journalctl, запущенная без достаточных прав, тоже может ничего не вывести — не из-за выключенного флага, а из-за отсутствия доступа. Поэтому в примере используется sudo.
--allow-direct-commit
Четыре инструмента не создают набор изменений вообще: load_and_commit_config — всегда; render_and_apply_j2_template — если он действительно применяет изменения; rollback_config — при commit=true; upgrade_junos — при confirm=true после завершения этапа предварительного просмотра и проверки. Каждый из них за один вызов выполняет подготовку, проверку и фиксацию изменений на устройстве. Поэтому проверка вторым субъектом по своей природе невозможна: нет набора изменений, который второй субъект мог бы одобрить. По умолчанию эти инструменты выключены. Без этого флага все четыре инструмента отклоняются до обращения к устройству. Это правило действует для stdio так же, как и для HTTP: stdio не передаёт контекст вызывающего пользователя, поэтому к нему нельзя относиться менее строго, чем к аутентифицированному сеансу.
Остаточный риск. --allow-direct-commit — это обходной путь, а не способ устранить риск. Оператор, включающий этот параметр, принимает решение, что для конкретных инструментов в данной среде допустимо работать без независимой проверки. Это решение:
-
Заметно отражается в журнале при каждом запуске. Если флаг включён, при запуске процесса выводится предупреждение
SECURITY:, в котором назван связанный с этим риск. -
Фиксируется при каждом вызове. В запись аудита каждого вызова прямой фиксации добавляется
direct_commit_allowed=true. Отказ также попадает в аудит с параметрамиauthorization=denied reason=direct_commit_disabled.
Этот флаг не добавляет проверку вторым субъектом, а лишь делает её отсутствие заметным. По возможности используйте процесс с набором изменений (create_junos_change_set → approve_junos_change_set → apply_junos_change_set), а данный флаг оставьте для инструментов, которым такой процесс недоступен.
Включение параметра
Действуйте так же, как для --lab-mode: добавьте --allow-direct-commit в drop-in для systemd, полностью скопировав установленную команду ExecStart, а не указывая её в сокращённом виде.
Убедитесь, что параметр включился. В отличие от сообщения о --lab-mode, это предупреждение намеренно не помечено как target: "audit": у этого потока фиксированная схема для вызовов, а в сообщении при запуске нет предусмотренных ею полей. Поэтому ищите обычный текст сообщения:
sudo journalctl -u rust-junosmcp -b | grep -i "allow-direct-commit is enabled"
Если вывода нет, параметр выключен.
Журналирование аудита
rust-junosmcp формирует структурированные события аудита для каждого вызова инструментов Junos и SRX. В каждом событии записываются вызывающая сторона, инструмент, целевые маршрутизаторы, решение об авторизации, результат и длительность операции. Полная схема и рекомендации по пересылке событий приведены в docs/AUDIT.md.
| Параметр | Переменная среды | По умолчанию | Описание |
|---|---|---|---|
--audit-format | JMCP_AUDIT_FORMAT | text | Формат вывода: text или json. |
--audit-log-file | JMCP_AUDIT_LOG_FILE | (нет) | Необязательный путь к файлу, в который добавляются события в формате JSON (в дополнение к stderr). |
--audit-journald | JMCP_AUDIT_JOURNALD | false | Необязательная передача структурированных полей аудита в journald. Если параметр включён явно, но journald недоступен, запуск завершается ошибкой. |
Быстрый запуск (локально)
git clone https://github.com/mechubsec/rustjunosmcp.git
cd RustJunosMCP
# Build the default 44-tool Junos/SRX server with TLS.
cargo build --release
# Optional: build the 28-tool Junos-only server without TLS.
cargo build --release --no-default-features
# Optional: build Junos-only with TLS.
cargo build --release --no-default-features --features tls
# Configure devices.
cp devices-template.json devices.json
$EDITOR devices.json # set ip / username / auth
# Run as MCP stdio server.
./target/release/rust-junosmcp -f devices.json
Настройка Claude Desktop
Одна регистрация предоставляет доступ ко всем инструментам, включённым в собранный бинарный файл (44 инструмента с функцией srx по умолчанию или 28 инструментов в сборке только для Junos):
{
"mcpServers": {
"junos": {
"command": "/path/to/rust-junosmcp",
"args": ["-f", "/path/to/devices.json"]
}
}
}
Docker
Запуск с Docker
Вызов stdio, удобный для каталога, заменяет HTTP-команду CMD образа на
--transport stdio. ENTRYPOINT уже задаёт пути к файлам инвентаризации,
состояния, известных хостов, блокировок и токенов, поэтому не указывайте эти параметры повторно.
Каталог состояния подключается как bind mount, поэтому изначально принадлежит
пользователю хоста. Перед первым запуском он и файл tokens.json внутри него
должны принадлежать UID/GID 65532:65532; для tokens.json также необходимо
установить режим 0600:
jmcp-state/tokens.json
mkdir -p jmcp-state
cat > jmcp-state/tokens.json <<'EOF'
{
"version": 1,
"tokens": [
{
"name": "catalog",
"digest": "sha256:REPLACE_WITH_TOKEN_ADD_OUTPUT",
"devices": ["r1"],
"tools": ["gather_device_facts"],
"created_at": "2026-01-01T00:00:00Z"
}
]
}
EOF
sudo chown -R 65532:65532 jmcp-state
sudo chmod 0700 jmcp-state
sudo chmod 0600 jmcp-state/tokens.json
sudo chown 65532:65532 devices.json
sudo chmod 0600 devices.json
docker run --rm -i \
-v "$PWD/devices.json:/etc/jmcp/devices.json:ro" \
-v "$PWD/keys:/etc/jmcp/keys:ro" \
-v "$PWD/jmcp-state:/var/lib/jmcp" \
ghcr.io/mechubsec/rustjunosmcp:latest \
--transport stdio
При использовании rootless Docker или хоста с переназначением user namespace
UID 65532 внутри контейнера соответствует другому UID на хосте. В этом случае
назначьте точкам монтирования соответствующий отображённый UID, а не напрямую
65532.
Например, devices.json использует формат инвентаризации сервера. Перед
использованием замените секрет-заполнитель:
{
"r1": {
"ip": "192.0.2.10",
"port": 22,
"username": "netconf-user",
"auth": {
"type": "password",
"password": "replace-with-device-password"
}
}
}
При таком запуске HTTP и TLS отключены, поскольку замена CMD образа также
удаляет параметры привязки HTTP и настройки TLS.
По умолчанию сервер использует порт 22, через который NETCONF работает поверх
обычной службы SSH устройства (set system services ssh). Если вы включили
отдельный прослушиватель NETCONF с помощью set system services netconf ssh
(по умолчанию порт 830) и хотите использовать его, явно задайте port со
значением 830.
Инструкции по запуску пары «два пользователя» и «лабораторный режим» в виде контейнеров, включая ловушку с опубликованным и внутренним портами, из-за которой списки разрешений отклоняют все запросы с кодом 421, приведены в docs/HOW-TO-SETUP-DOCKER.md.
Готовые образы публикуются в GHCR при выпуске каждой версии. Пакет общедоступен,
выполнять docker login не нужно. Среда выполнения — distroless: в ней нет
оболочки, apt и клиента OpenSSH (#201). Контейнер запускается с числовым
UID/GID 65532:65532.
transfer_file/fetch_file больше не запускают scp: они нативно реализуют
протокол передачи SCP1 через mecmcp-scp по каналу выполнения SSH (#212).
Подготовьте подключаемые только для чтения файлы конфигурации и ключей, а также
один постоянный каталог состояния с правом записи. Пути к закрытым ключам в
devices.json должны указывать на их расположение внутри контейнера в
/etc/jmcp/keys.
# Pull the prebuilt image (tags: latest, 0.25, 0.26, 0.27).
docker pull ghcr.io/mechubsec/rustjunosmcp:latest
# Prepare host paths. Review scanned host-key fingerprints against a trusted
# source before starting the server in strict mode.
mkdir -p keys jmcp-state/staging jmcp-state/device-leases
touch jmcp-state/known_hosts
./scripts/scan-known-hosts.sh \
--inventory "$PWD/devices.json" \
--known-hosts "$PWD/jmcp-state/known_hosts" \
--replace
# The image's non-root process must own its writable state and be able to read
# the inventory and private keys. Keep all three private from other host users.
sudo chown -R 65532:65532 devices.json keys jmcp-state
sudo chmod 0600 devices.json keys/* jmcp-state/known_hosts
sudo chmod 0700 keys jmcp-state jmcp-state/device-leases
sudo chmod 0750 jmcp-state/staging
# Run with configuration/keys read-only and state persistent + writable.
docker run --rm -i \
-v "$PWD/devices.json:/etc/jmcp/devices.json:ro" \
-v "$PWD/keys:/etc/jmcp/keys:ro" \
-v "$PWD/jmcp-state:/var/lib/jmcp" \
ghcr.io/mechubsec/rustjunosmcp:latest
Проверка подписи образа: каждый образ, опубликованный рабочим процессом
Release image, подписывается без ключей с помощью cosign
через OIDC GitHub Actions — пары ключей не существует. При проверке личность
подписанта привязывается к конкретному рабочему процессу, поэтому подпись,
полученная где-либо ещё (например, в форке, другом репозитории или локальной
сборке), проверку не пройдёт:
cosign verify \
--certificate-identity-regexp '^https://github\.com/mechubsec/rustjunosmcp/\.github/workflows/release-image\.yml@refs/(tags/v[0-9]+\.[0-9]+\.[0-9]+|heads/main)$' \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/mechubsec/rustjunosmcp:latest
Здесь используется регулярное выражение, а не точное значение
--certificate-identity: GitHub помещает в сертификат ссылку, вызвавшую запуск,
а у этого рабочего процесса есть два допустимых триггера с разными ссылками.
При обычной отправке тега указывается @refs/tags/vX.Y.Z — тег конкретного
выпуска, который меняется для каждой версии. Это относится и к тегу :latest,
поскольку при каждом выпуске он публикуется и подписывается заново. При
запуске workflow_dispatch для обратного заполнения (см. параметр ref выше)
обычно указывается @refs/heads/main — ветка, из которой был запущен процесс.
Этот рабочий процесс не может запускаться ни при создании pull request, ни при отправке изменений в любую другую ветку, поэтому других вариантов личности подписанта быть не может. Если вы проверяете конкретный выпуск, а не просто «какую-либо сборку выпуска этого рабочего процесса», укажите точный тег вместо диапазона версий. Каждая подпись также создаёт общедоступную запись в журнале прозрачности Rekor —
это ожидаемое поведение, и такая запись не раскрывает ничего сверх того, что уже стало общедоступным при публикации образа.
Образы, опубликованные до 2026-09-29, подписывались рабочим процессом из
github.com/fastrevmd-lab/RustJunosMCP, поэтому для проверки старого тега
необходимо использовать именно эту личность.
Проверка аттестации SBOM: при выпуске CycloneDX SBOM графа зависимостей Rust
(не базового образа distroless) прикрепляется к выпуску GitHub и публикуется
как in-toto-аттестация образа, подписанная без ключей тем же способом, что и
выше. Эту аттестацию подписывает задача sbom: в файле release-sbom.yml
этого репозитория. Начиная с первого выпуска после v0.27.5, она передаёт
фактическую аттестацию повторно используемому рабочему процессу mecmcp
reusable-attest-release-sbom.yml. Поэтому субъект сертификата OIDC указывает
на путь именно к этому рабочему процессу,
зафиксированный на конкретном SHA коммита, который в данный момент указан в
строке uses: файла release-sbom.yml, — а не на собственный файл рабочего
процесса этого репозитория и не на ссылку ветки. Этот SHA меняется, когда
release-sbom.yml перенастраивают на более новый SHA или тег mecmcp, поэтому
не считайте SHA из этого README актуальным навсегда: проверьте текущую
фиксацию в строке uses: файла .github/workflows/release-sbom.yml.
Поскольку этот повторно используемый рабочий процесс находится в публичном репозитории, любой репозиторий GitHub может его вызвать и получить сертификат с той же личностью. Поэтому одной только личности недостаточно, чтобы доказать, что аттестация получена из выпуска именно этого репозитория.
Параметры --certificate-github-workflow-repository и
--certificate-github-workflow-trigger устраняют эту неоднозначность: они
проверяют поля сертификата с указанием вызывающего репозитория и события,
которое запустило процесс. Значения этих полей должны быть
mechubsec/rustjunosmcp и release. Не удаляйте эти параметры. Используется
та же личность, что и для подписи архива выпуска ниже, но она отличается
от личности release-image.yml, которой подписан образ выше: это отдельная
задача, выполняемая после публикации образа:
">
cosign verify-attestation --type cyclonedx \
--certificate-identity-regexp '^https://github\.com/mechubsec/mecmcp/\.github/workflows/reusable-attest-release-sbom\.yml@[0-9a-f]{40}$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository mechubsec/rustjunosmcp \
--certificate-github-workflow-trigger release \
ghcr.io/mechubsec/rustjunosmcp:<version>
В регулярном выражении используется SHA, а не точное значение
--certificate-identity, поскольку dependabot или сопровождающий проекта может
перенастроить release-sbom.yml на более новый коммит mecmcp. Так проверяется
только путь, а не конкретный коммит. Для выпусков вплоть до v0.27.5 включительно
аттестацию вместо этого подписывала непосредственно личность рабочего процесса
release-sbom.yml этого репозитория:
">
cosign verify-attestation --type cyclonedx \
--certificate-identity-regexp '^https://github\.com/mechubsec/rustjunosmcp/\.github/workflows/release-sbom\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/mechubsec/rustjunosmcp:<version>
В подключённом каталоге состояния хранятся подготовленные файлы для загрузки и
скачивания, общие блокировки операций с разрушающими последствиями и файл
known_hosts. Не удаляйте файлы блокировок, пока сервер работает. По умолчанию
проверка ключей хостов выполняется в строгом режиме. Только для изолированной
лабораторной среды добавьте к команде docker run параметр
--ssh-accept-new-host-keys; это позволит
сервер добавляет впервые обнаруженные ключи в файл состояния, доступный для записи, но не выполняет внеполосную аутентификацию при первом подключении.
Apple Silicon (M-series): образы собираются только для
linux/amd64, поэтому на Apple Silicon они работают в режиме эмуляции. Это работает, но при появлении предупреждения о несовпадении платформ добавьте--platform linux/amd64в командыpullиrun.
Вместо этого рекомендуется собрать образ локально:
docker build -t rust-junosmcp:local .
docker run --rm -i \
-v "$PWD/devices.json:/etc/jmcp/devices.json:ro" \
-v "$PWD/keys:/etc/jmcp/keys:ro" \
-v "$PWD/jmcp-state:/var/lib/jmcp" \
rust-junosmcp:local
LXC (Proxmox)
Инструкции по сборке контейнера с нуля — в том числе по настройке пары для двух пользователей и режима лаборатории, выбору правильных режимов учётных данных до первого запуска и проверке того, что параметры seccomp взяты из поставляемого systemd unit, — приведены в документации по настройке LXC.
# Build the tarball and checksum.
./scripts/package-lxc.sh
# Verify the checksum.
sha256sum -c dist/rust-junosmcp_0.27.2_amd64.tar.gz.sha256
# Push and install on VM 115. The installer copies the unified binary and unit
# from its extracted package root.
#
# The container MUST be Debian 13 (trixie). This is not a style preference:
# `package-lxc.sh` builds against the glibc of whatever host runs it, and the
# current published binary requires GLIBC_2.39. Debian 12 ships 2.36, so the
# service dies at start with a "GLIBC_2.39 not found" symbol error — after a
# clean build and a clean install, which is the worst place to discover it.
# Debian 13 ships 2.41. Check your own tarball with:
# objdump -T dist/rust-junosmcp_*/usr/local/bin/rust-junosmcp \
# | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -1
#
# Debian 13 also matches docs/PACKAGING.md §2, the container runtime base, and
# rustpanosmcp — one distro generation to track CVEs against, not three.
pct push 115 dist/rust-junosmcp_0.27.2_amd64.tar.gz /tmp/jmcp.tar.gz
pct exec 115 -- bash -c "tar xzf /tmp/jmcp.tar.gz -C /tmp && /tmp/rust-junosmcp_0.27.2_amd64/install.sh"
Загрузка предварительно собранного архива релиза: в каждом релизе GitHub также публикуются архив, его контрольная сумма .sha256 и пакет подписи cosign без ключа (.cosign.bundle). Подпись создаёт workflow Sign release tarball — так же, как описано выше для образа контейнера: без пары ключей, только с использованием GitHub Actions OIDC. Проверяйте и контрольную сумму, и подпись: одна лишь контрольная сумма подтверждает, что файл не был повреждён при передаче, но не доказывает, что он получен из workflow релиза этого репозитория:
version=0.27.2
base="https://github.com/mechubsec/rustjunosmcp/releases/download/v${version}"
curl -LO "${base}/rust-junosmcp_${version}_amd64.tar.gz"
curl -LO "${base}/rust-junosmcp_${version}_amd64.tar.gz.sha256"
curl -LO "${base}/rust-junosmcp_${version}_amd64.tar.gz.cosign.bundle"
sha256sum -c "rust-junosmcp_${version}_amd64.tar.gz.sha256"
cosign verify-blob \
--certificate-identity "https://github.com/mechubsec/mecmcp/.github/workflows/reusable-sign-release-tarball.yml@f927c820f39369b2601e11e31334cc5b504b1fd1" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-github-workflow-repository "mechubsec/rustjunosmcp" \
--certificate-github-workflow-trigger "release" \
--bundle "rust-junosmcp_${version}_amd64.tar.gz.cosign.bundle" \
"rust-junosmcp_${version}_amd64.tar.gz"
# Backfilled assets (workflow_dispatch of an already-published tag) use
# --certificate-github-workflow-trigger "workflow_dispatch" instead.
В отличие от описанного выше workflow для образа, задача sign в release-sign-tarball.yml этого репозитория делегирует собственно подписание повторно используемому workflow из mecmcp. Поэтому в субъекте сертификата OIDC указан путь к тому workflow, зафиксированный на конкретной ревизии SHA release-sign-tarball.yml, на которую сейчас ссылается строка uses: в задаче sign:. Это не файл workflow самого репозитория и не ссылка на ветку. Эта привязка меняется, когда задачу sign: перенастраивают на более новую ревизию или тег mecmcp. Поэтому не считайте SHA в этом README постоянным: проверьте текущую привязку в строке uses: файла .github/workflows/release-sign-tarball.yml.
Поскольку этот повторно используемый workflow находится в публичном репозитории, вызвать его может любой репозиторий GitHub и получить сертификат с тем же значением идентификатора. Поэтому одного идентификатора недостаточно, чтобы подтвердить, что архив создан для релиза именно этого репозитория.
Параметры --certificate-github-workflow-repository и --certificate-github-workflow-trigger устраняют эту неоднозначность: они проверяют поля сертификата с вызывающим репозиторием и событием-триггером. Их значения должны быть mechubsec/rustjunosmcp и одним из двух допустимых триггеров этого workflow.
Обычный выпуск релиза использует release; для добавления недостающих файлов к уже опубликованному подписанному тегу через workflow_dispatch используется workflow_dispatch. Запрос на добавление отклоняется, если у релиза уже есть пакет подписи архива. Если архив опубликован, но пакет подписи отсутствует, его всё ещё можно добавить. Значение параметра проверяется на точное совпадение, поэтому при проверке такого архива замените его на соответствующее. Не удаляйте ни один из этих параметров.
При любом несовпадении cosign verify-blob завершится с ненулевым кодом: если не совпадает идентификатор, издатель, вызывающий репозиторий или триггер либо архив не соответствует пакету подписи. Ошибка здесь означает, что устанавливать архив нельзя, а не то, что «скорее всего, всё в порядке».
Отредактируйте список устройств:
# Copy the example and configure your devices.
pct exec 115 -- cp /etc/jmcp/devices.json.example /etc/jmcp/devices.json
pct exec 115 -- $EDITOR /etc/jmcp/devices.json # or edit via another method
pct exec 115 -- chown jmcp:jmcp /etc/jmcp/devices.json
pct exec 115 -- chmod 0600 /etc/jmcp/devices.json
Создайте токен доступа:
# A wildcard tool scope grants read-only tools only; write tools must be named
# explicitly. See "Upgrading to v0.10" and "Tool scopes and write tools" below.
pct exec 115 -- runuser -u jmcp -- /usr/local/bin/rust-junosmcp token add \
--tokens-file /etc/jmcp/tokens.json \
--name ops \
--routers '*' \
--tools get_router_list,gather_device_facts,execute_junos_command,get_junos_config,commit_check_config,load_and_commit_config
Запустите службу:
pct exec 115 -- systemctl enable --now rust-junosmcp
Установщик идемпотентен: при повторном запуске он обновляет бинарные файлы и systemd unit, не перезаписывая devices.json, tokens.json и known_hosts. Перед изменением состояния системы он проверяет архив целиком. Установленный сервер предоставляет все активированные инструменты Junos и SRX по адресу 127.0.0.1:30030/mcp и требует аутентификацию с помощью токена доступа. Для удалённых клиентов используйте SSH-туннель или обратный прокси с TLS.
Сброс зависшей операции:
state resolveзавершает операцию, которая после неудачного применения осталась в промежуточном состоянии. Такая запись блокирует все последующие изменения на устройстве, а ни один инструмент не может её очистить:cancel_junos_ change_setотказывается отменять набор изменений, уже перешедший в конечное состояние. Сначала остановите службу, иначе работающий сервер перезапишет файл данными из памяти:
runuser -u jmcp -- /usr/local/bin/rust-junosmcp state resolve \
--state-file /var/lib/jmcp/changeset-state.json
--operation-id
--disposition discarded
--confirmation "RESOLVED AS DISCARDED" systemctl start rust-junosmcp">
systemctl stop rust-junosmcp
runuser -u jmcp -- /usr/local/bin/rust-junosmcp state resolve \
--state-file /var/lib/jmcp/changeset-state.json \
--operation-id <64-hex-operation-id> \
--disposition discarded \
--confirmation "RESOLVED <64-hex-operation-id> AS DISCARDED"
systemctl start rust-junosmcp
Строка подтверждения должна в точности совпадать с указанной: оператор тем самым подтверждает, что проверил устройство и описал его фактическое состояние. Используйте
committedтолько в том случае, если журнал коммитов самого устройства подтверждает, что изменение применено.
Сужение области действия существующего токена:
token set-scopeпозволяет изменить области действия токена для маршрутизаторов или инструментов без выпуска нового секрета. Поэтому клиенты продолжат работать с тем же токеном доступа. Это удобно, если нужно отозвать часть доступа, не перенастраивая каждый клиент. Примеры приведены в разделе управления токенами.
При обновлении с версии с разделёнными серверами удаляются устаревшие исполняемый файл rust-srxmcp, systemd unit и ссылка на включённую службу. Существующие пакеты поддержки в /var/lib/jmcp/srx-staging/bundles при этом сохраняются.
Удалённый транспорт и аутентификация
Выпуск токена
cargo run -- token add \
--tokens-file tokens.json \
--name ops \
--routers '*' \
--tools execute_junos_command,gather_device_facts
get_router_list применяет к списку маршрутизаторов те же ограничения, что и инструменты для работы с устройствами. Токены с авторизацией и списком разрешённых маршрутизаторов получают только текущие имена устройств из своей области действия; устаревшие записи и исключённые маршрутизаторы пропускаются без подсчёта и ошибок.
Если список разрешённых маршрутизаторов пуст или пересечение с инвентарём пусто, возвращается []. Токены с подстановочным знаком, локальный stdio и явно включённый режим без аутентификации для loopback сохраняют доступ ко всему инвентарю.
Примечание: формат файла показан в tokens-template.json. Используйте
token add, а не редактируйте файл вручную: полеdigestдолжно содержать версионированный SHA-256 секрета, а не сам секрет. Канонические имена полей —digestиdevices. Старые вариантыhashиroutersпо-прежнему принимаются как псевдонимы, поэтому существующие файлы загружаются без изменений. Флаг CLI по-прежнему называется--routers.
Файл
tokens.jsonдолжен иметь режим0600(начиная с v0.10.0). Сервер не запускается, если файл токенов доступен группе или всем пользователям. В сообщении об ошибке указываются uid владельца файла, его режим, uid вызывающего процесса и командаchmod, которую нужно выполнить. Все подкомандыtokenзаписывают файл с режимом0600, а установщик LXC задаёт этот режим автоматически. Поэтому проблема возникает только с файлами, созданными или скопированными вручную.
Области действия инструментов и инструменты записи
Область действия инструмента задаётся либо символом *, либо явным списком имён инструментов. Смешивать эти варианты нельзя: команда --tools '*',transfer_file будет отклонена.
Область действия с подстановочным знаком не даёт доступа к инструментам записи (начиная с v0.10.0). Значение "tools": ["*"] открывает доступ ко всем инструментам только для чтения, но не к перечисленным ниже 15 инструментам, способным изменять данные:
| Инструмент записи | |
|---|---|
add_device | apply_junos_change_set |
approve_junos_change_set | confirm_junos_change_set |
create_junos_change_set | discard_candidate |
execute | load_and_commit_config |
manage_appid_signature_package | manage_idp_security_package |
reload_devices | render_and_apply_j2_template |
rollback_config | transfer_file |
upgrade_junos |
Доступ на запись всегда предоставляется явным указанием конкретных инструментов. Например, если токену нужен load_and_commit_config, этот инструмент необходимо перечислить вместе со всеми остальными используемыми инструментами. Явный список разрешённых инструментов работает как и прежде.
execute — это строгая оболочка для 40 конкретных операций. Токен, использующий её, должен явно включать и execute, и выбранную конкретную операцию. Область действия с подстановочным знаком не предоставляет доступ к execute. Оболочка передаёт аргументы выбранной операции без изменений, поэтому указывайте точные имена аргументов этой операции:
{"operation":"gather_device_facts","arguments":{"device":"vsrx-ci"}}
Прямые вызовы всех 40 конкретных инструментов по-прежнему поддерживаются.
В ответе tools/list указываются только инструменты, доступные вызывающему токену. Поэтому список, который видит агент, соответствует инструментам, которые он действительно может вызвать. Для токена с подстановочным знаком отображаются инструменты только для чтения, но не 15 инструментов записи. Если токену не назначена ни одна область действия, возвращается пустой список.
Кэшированные списки устаревают. Если клиент получил
tools/listдо изменения области действия токена с помощьюtoken set-scope, он продолжит видеть старый список, пока не переподключится. При перезагрузке после SIGHUP сервер пока не отправляетnotifications/tools/list_changed. При этом авторизация не меняется: вызов инструмента, недоступного токену, будет отклонён независимо от того, что клиент считает доступным.
Проверка областей действия применяется только к аутентифицированным HTTP-клиентам. Локальный stdio и обходной режим --allow-no-auth для loopback не передают сведения о вызывающей стороне и не ограничиваются областями действия.
Запускайте подкоманды для управления токенами от имени пользователя службы. Если systemd запускает сервер от отдельного пользователя (например,
User=jmcpв поставляемом unit-файле), файл, записанный командамиtoken add/revoke/rotate/set-scope, будет принадлежать пользователю, который вызвал команду. Если выполнить команду от имениroot, файлtokens.jsonполучит владельцаroot:rootи режим0600, и пользователь службы не сможет его прочитать. При запуске сервер будет постоянно аварийно завершаться с ошибкойPermission denied. Возможны два варианта:
sudo -u jmcp rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ...
Or fix ownership after running as root.
rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ... chown jmcp:jmcp /etc/jmcp/tokens.json">
# Preferred: run subcommands as the service user.
sudo -u jmcp rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ...
# Or fix ownership after running as root.
rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ...
chown jmcp:jmcp /etc/jmcp/tokens.json
Если при запуске сервер столкнётся с этой проблемой, сообщение об ошибке укажет uid и режим файла, а также uid вызывающего пользователя. Это позволит быстро определить решение, не изучая журналы journald.
Запуск с авторизацией (streamable-http)
cargo run -- \
--device-mapping devices.json \
--transport streamable-http \
-H 127.0.0.1 \
-p 8765 \
--tokens-file tokens.json
Обходной режим без авторизации (только для локального подключения)
cargo run -- --device-mapping devices.json --transport streamable-http \
-H 127.0.0.1 -p 8765 --allow-no-auth
Флаг --allow-no-auth не допускается, если адрес привязки не относится к loopback.
Для привязки к нелокальному адресу требуется TLS
cargo run -- \
--device-mapping devices.json \
--transport streamable-http \
-H 0.0.0.0 \
-p 8765 \
--tokens-file tokens.json \
--tls-cert cert.pem \
--tls-key key.pem
Чтобы привязать сервер к адресу вне loopback и использовать обычный HTTP (например, за прокси с завершением TLS на том же узле), добавьте --allow-insecure-bind. Этот флаг отменяет требование TLS, поэтому используйте его с осторожностью — только если безопасность транспортного соединения гарантируется другим способом.
Список разрешённых хостов (защита от DNS rebinding)
Транспорт streamable-http проверяет входящий заголовок Host по списку разрешённых хостов (по умолчанию разрешены только loopback-адреса: localhost, 127.0.0.1, ::1). Это устраняет уязвимость RUSTSEC-2026-0189, связанную с DNS rebinding. Клиенты, подключающиеся не через loopback, нужно добавить в список с помощью повторяемого флага --allowed-host. В противном случае запросы будут отклонены с HTTP 403 независимо от состояния авторизации:
cargo run -- \
--device-mapping devices.json \
--transport streamable-http \
-H 0.0.0.0 \
-p 8765 \
--tokens-file tokens.json \
--tls-cert cert.pem \
--tls-key key.pem \
--allowed-host jmcp.example.net
Отключить проверку списка разрешённых хостов нельзя. Флаг --disable-host-check удалён в версии 0.15.3, и теперь сервер отклоняет его при запуске. Этот список служит защитой от DNS rebinding (RUSTSEC-2026-0189). Такая атака направлена именно на службы, привязанные к loopback: браузер разрешает контролируемое злоумышленником имя в 127.0.0.1 и обращается к серверу с чужим значением Host. Поэтому отключение проверки опаснее всего именно в ситуации, которая кажется наиболее безопасной. Если клиент отправляет серверу неизвестное имя узла, добавьте его с помощью --allowed-host. Этот флаг можно указывать несколько раз.
Горячая перезагрузка
После отзыва или замены токена сервер перечитывает хранилище токенов без перезапуска. Добавьте --server-pid к любой подкоманде записи, и после записи файла сигнал SIGHUP будет отправлен автоматически:
Add a new token and signal in one step.
cargo run -- token add
--tokens-file tokens.json
--name ops2
--routers '*'
--tools execute_junos_command,gather_device_facts
--server-pid ">
# Revoke — writes file, then signals the server.
cargo run -- token revoke --tokens-file tokens.json --name ops --server-pid <pid>
# Rotate (mints a new secret, preserves scopes) — same pattern.
cargo run -- token rotate --tokens-file tokens.json --name ops --server-pid <pid>
# Add a new token and signal in one step.
cargo run -- token add \
--tokens-file tokens.json \
--name ops2 \
--routers '*' \
--tools execute_junos_command,gather_device_facts \
--server-pid <pid>
Изменение области действия токена без замены секрета
Команда token set-scope изменяет области действия существующего токена. Значения digest, created_at и версия оболочки сохраняются, поэтому клиенты продолжают работать с имеющимся секретом. В этом состоит отличие от rotate, которая создаёт новый секрет и требует одновременно обновить его у всех потребителей.
">
# Narrow tools; leave the router scope alone.
cargo run -- token set-scope \
--tokens-file tokens.json \
--name ops \
--tools gather_device_facts,get_junos_config,load_and_commit_config \
--server-pid <pid>
# Narrow routers; leave the tool scope alone.
cargo run -- token set-scope \
--tokens-file tokens.json \
--name ops \
--routers edge-1,edge-2 \
--server-pid <pid>
Флаги --routers и --tools необязательны и независимы друг от друга: если не указать один из них, соответствующая область действия останется без изменений. Если не указать ни один, команда завершится с ошибкой. Результирующие области действия выводятся в stderr, а флаг --server-pid отправляет обычный сигнал SIGHUP после записи файла. Неизвестные имена инструментов отклоняются. Неизвестные имена устройств принимаются с предупреждением, поскольку команды управления токенами выполняются до загрузки инвентаря.
Чтобы инициировать перезагрузку без изменения токена (например, после ручного редактирования файла), отправьте сигнал SIGHUP напрямую:
kill -HUP <pid>
Матрица отказов
| Флаги | Адрес привязки | Результат |
|---|---|---|
| (нет) | любой | Отказ — для streamable-http требуется --tokens-file или --allow-no-auth |
Только --allow-no-auth | не loopback | Отказ — --allow-no-auth разрешён только для loopback |
Только --allow-no-auth | loopback | Разрешено — обратите внимание: даже если вы также укажете --tls-cert/--tls-key, аутентификация останется отключённой. TLS обеспечивает конфиденциальность, но любой клиент, который может подключиться к порту, получит полный доступ к инструментам (опасная ловушка) |
Только --tokens-file | не loopback, без TLS | Отказ — добавьте --tls-cert/--tls-key или --allow-insecure-bind |
--tokens-file --allow-insecure-bind | не loopback, без TLS | Разрешено — токены проверяются; вы подтверждаете, что транспорт защищён внешними средствами |
--tokens-file --tls-cert cert.pem --tls-key key.pem | любой | Разрешено |
Канонические переменные окружения
В унифицированном сервере используется единое пространство имён JMCP_*. Значения, заданные в командной строке, имеют приоритет над значениями из переменных окружения.
| Назначение | Каноническая переменная окружения |
|---|---|
| Прослушивание подключений | JMCP_HTTP_HOST, JMCP_HTTP_PORT |
| Список устройств и токены | JMCP_DEVICES_PATH, JMCP_TOKENS_PATH |
| TLS | JMCP_TLS_CERT, JMCP_TLS_KEY |
| Аренда для разрушительных операций | JMCP_DEVICE_LEASE_DIR |
| Подготовка пакета поддержки | JMCP_SUPPORT_BUNDLE_STAGING_DIR, JMCP_SUPPORT_BUNDLE_STAGING_MAX_BYTES |
| Аудит | JMCP_AUDIT_FORMAT, JMCP_AUDIT_LOG_FILE, JMCP_AUDIT_JOURNALD, JMCP_AUDIT_REDACT, JMCP_AUDIT_HMAC_KEY_FILE |
| Метрики | JMCP_ENABLE_METRICS |
Для миграции псевдонимы старого формата JMCP_SRX_* принимаются в версии 0.8.0 с предупреждением, но только если не заданы соответствующий параметр командной строки или каноническая переменная.
JMCP_SRX_HTTP_PORT всегда игнорируется: второго, выведенного из эксплуатации слушателя больше нет. Переведите развёртывания на канонические имена уже сейчас.
Обновление до v0.10
В версии v0.10.0 вводятся два несовместимых изменения в авторизации. Новый бинарный файл проверяет оба условия во время работы, поэтому подготовьтесь, пока ещё работает версия 0.9.x — иначе после обновления сервер либо не запустится, либо клиенты, которым нужна запись, перестанут работать без явного предупреждения.
1. Исправьте права доступа к файлу токенов
Новый бинарный файл отказывается запускаться, если к tokens.json открыт доступ для группы или всех пользователей. Перед заменой бинарного файла проверьте и исправьте права:
# On the server host, as the service user.
stat -c '%a %U:%G %n' /etc/jmcp/tokens.json
chmod 0600 /etc/jmcp/tokens.json
Установщик LXC уже задаёт права 0600, поэтому в пакетных установках проблем почти наверняка не будет. Проверить стоит файлы, созданные вручную, скопированные с других хостов, восстановленные из резервной копии или записанные средствами управления конфигурацией. Если сервер обнаружит такую проблему, в сообщении об ошибке при запуске будут указаны uid владельца файла, режим доступа, uid вызывающего процесса и точная команда chmod. Ошибка не останется незамеченной.
2. Уточните области действия токенов с шаблоном, которым нужны инструменты записи
Область действия с шаблоном * больше не предоставляет доступ к 15 инструментам, поддерживающим запись. После обновления вызовы таких инструментов токенами с "tools": ["*"] начнут отклоняться с ошибкой ToolNotInScope.
Найдите затронутые токены:
sudo -u jmcp rust-junosmcp token list --tokens-file /etc/jmcp/tokens.json
Каждая строка, в которой в столбце TOOLS указано *, — потенциально затронутый токен. Для каждого такого токена определите, действительно ли ему нужен доступ на запись:
-
Фактически используется только для чтения — оставьте токен без изменений. Область действия с шаблоном по-прежнему охватывает все инструменты только для чтения и теперь безопаснее, чем раньше.
-
Нужны инструменты записи — замените шаблон явным списком всех используемых инструментов, включая инструменты только для чтения. Нельзя указывать
*вместе с именами инструментов.
Команда token set-scope позволяет изменить область действия, не выпуская новый секрет, поэтому клиенты продолжат работать:
sudo -u jmcp rust-junosmcp token set-scope \
--tokens-file /etc/jmcp/tokens.json \
--name ops \
--tools get_router_list,gather_device_facts,get_junos_config,junos_config_diff,commit_check_config,load_and_commit_config \
--server-pid "$(systemctl show -p MainPID --value rust-junosmcp)"
Команда set-scope доступна в версии 0.10.0 и новее. В версии 0.9.x аналогичное изменение нужно внести вручную в tokens.json: измените только массив tools, оставив hash/digest и created_at без изменений, а затем выполните systemctl kill -s HUP rust-junosmcp. Не используйте для этого команду rotate: она создаёт новый секрет и сразу лишает доступа всех потребителей этого токена.
3. Выполните обновление и проверьте результат
После установки новой версии убедитесь, что хранилище загружено и области действия токенов настроены ожидаемым образом:
systemctl status rust-junosmcp
sudo -u jmcp rust-junosmcp token list --tokens-file /etc/jmcp/tokens.json
Затем проверьте работу одного инструмента записи через затронутый клиент. Если на этом этапе получен отказ, значит, в списке разрешённых инструментов соответствующего токена отсутствует имя этого инструмента.
Перед обновлением создайте резервную копию
tokens.json— откат не является симметричным. v0.10 читает старые варианты названийhash/routers, но любая запись токена в v0.10 (add,rotate,revoke,set-scope) перезаписывает весь файл, используя канонические названияdigest/devices. Версия 0.9.x не может разобрать такой файл: она требует полеhashи не загружает хранилище, поэтому сервер после отката не запустится. Сохраните копию:
sudo -u jmcp cp -a /etc/jmcp/tokens.json /etc/jmcp/tokens.json.0.9-backup
Если вы не выполняли запись токена в v0.10, файл остаётся побайтно идентичным, и откат пройдёт без проблем. В противном случае восстановите резервную копию — сами секреты и области действия не меняются при обновлении — или вручную переименуйте два поля обратно. В Proxmox снимок LXC-контейнера, сделанный перед установкой, позволит сохранить это состояние вместе со всем остальным.
Ограничения ресурсов (streamable-HTTP)
Единая конечная точка применяет настраиваемые ограничения для защиты от DoS. Большинство ограничений включено по умолчанию и имеет достаточно высокие значения. Необязательное ограничение частоты запросов для каждого токена отключено, пока оба соответствующих параметра не получат положительные значения. Значение 0 отключает отдельное ограничение, кроме случая с парой параметров частоты и всплеска, описанного ниже.
| Параметр | Переменная окружения | По умолчанию | Действие |
|---|---|---|---|
--max-request-body-bytes | JMCP_MAX_REQUEST_BODY_BYTES | 10 MiB | Отклоняет запросы с телом больше указанного размера кодом 413 до буферизации |
--max-inflight-requests | JMCP_MAX_INFLIGHT_REQUESTS | 64 | Ограничивает общее число параллельных запросов; при превышении возвращается 503 |
--max-inflight-requests-per-token | JMCP_MAX_INFLIGHT_REQUESTS_PER_TOKEN | 16 | Ограничивает число параллельных запросов для одного токена; при превышении возвращается 503 |
--max-requests-per-second-per-token | JMCP_MAX_REQUESTS_PER_SECOND_PER_TOKEN | 0 | Задаёт скорость пополнения для каждого токена; используется вместе с ограничением всплеска (0/0 отключает оба ограничения) |
--max-request-burst-per-token | JMCP_MAX_REQUEST_BURST_PER_TOKEN | 0 | Задаёт максимальный мгновенный всплеск запросов для каждого токена; используется вместе с ограничением частоты (0/0 отключает оба ограничения) |
--max-inflight-requests-per-router | JMCP_MAX_INFLIGHT_REQUESTS_PER_ROUTER | 4 | Ограничивает число параллельных запросов для каждого маршрутизатора; при превышении возвращается 503 |
--max-sessions | JMCP_MAX_SESSIONS | 128 | Ограничивает общее число сессий; при превышении возвращается 503 |
--max-sessions-per-token | JMCP_MAX_SESSIONS_PER_TOKEN | 16 | Ограничивает число сессий для каждого bearer-токена; при превышении возвращается 503 |
--session-idle-timeout-secs | JMCP_SESSION_IDLE_TIMEOUT_SECS | 300 | Завершает бездействующие сессии |
--session-max-lifetime-secs | JMCP_SESSION_MAX_LIFETIME_SECS | 3600 | Завершает сессии, достигшие максимального срока жизни |
Общий лимит сессий применяется атомарно при их создании. Middleware заранее отклоняет запросы, если система явно перегружена. Общий менеджер сессий также закрывает сессию, созданную параллельно, если она не смогла занять последнее свободное место, и возвращает тот же ответ 503 с кодом session_cap. При отклонении инициализации заголовок Mcp-Session-Id не возвращается. Закрытие или удаление сессии освобождает место.
Учёт сессий для каждого токена ведётся по точному имени аутентифицированного токена. При успешной инициализации возвращённый Mcp-Session-Id привязывается к токену. Явное закрытие сессии, а также её удаление по тайм-ауту бездействия или по истечении срока жизни освобождают место. При достижении лимита возвращается {"error":"overloaded","limit":"token_session_cap"}. В явном режиме без аутентификации этот лимит не применяется, поскольку идентификатор токена отсутствует.
В ответах о превышении лимита передаётся заголовок Retry-After: 1. Разрешения на параллельные запросы освобождаются после завершения потока ответа. Вызов с несколькими маршрутизаторами занимает по одному слоту для каждого уникального целевого значения верхнего уровня в router, router_name, routers или router_names.
О превышении лимита для маршрутизатора сообщает тело ответа {"error":"overloaded","limit":"router_concurrency"}.
HTTP-разрешение для маршрутизатора запрашивается до того, как деструктивный рабочий процесс начинает ожидать блокировку устройства, используемую между процессами. Деструктивный вызов учитывается один раз — как во время ожидания этой блокировки, так и при её удержании. Лимит HTTP-запросов ограничивает как запросы на чтение, так и деструктивные вызовы, ожидающие блокировку. При этом именно блокировка обеспечивает последовательное выполнение деструктивных операций между процессами.
Ограничение частоты запросов для каждого токена — это необязательный токен-бакет, ключом которого служит точное имя аутентифицированного токена. Чтобы включить его, задайте положительные значения для частоты запросов в секунду и размера всплеска. Чтобы отключить ограничение, оставьте оба значения равными 0. Если положительным задано только одно из значений, запуск завершится ошибкой. Каждый аутентифицированный HTTP-запрос к /mcp расходует один токен. Если токенов недостаточно, до применения ограничений на параллельные запросы или сессии возвращается 429, заголовок Retry-After: 1 и ответ {"error":"rate_limited","limit":"token_rate"}. В явном режиме без аутентификации это ограничение не применяется.
Используйте ограничение RPS, чтобы сглаживать всплески большого числа быстрых и недорогих вызовов. Ограничения параллельности помогают контролировать число одновременных ресурсоёмких операций NETCONF/SSH и медленных потоков ответов. Эти механизмы независимы и могут работать одновременно: сначала проверяется частота запросов, а при превышении лимита параллельности или ёмкости сессий сохраняется существующий контракт с ответом 503.
Экспорт Prometheus включается параметром --enable-metrics (JMCP_ENABLE_METRICS). Он добавляет не требующую аутентификации конечную точку GET /metrics рядом с /mcp; ограничьте к ней доступ сетевыми средствами.
См. метрики Prometheus: там приведены настройки сбора метрик, их имена и метки, а также примеры PromQL.
CLI
JSON-файл с сопоставлением устройств (совместим с Juniper junos-mcp-server) [по умолчанию: devices.json]
-t, --transport Транспорт [по умолчанию: stdio] [допустимые значения: stdio, streamable-http] -H, --host Хост для привязки (только для streamable-http) [по умолчанию: 127.0.0.1] -p, --port Порт для привязки (только для streamable-http) [по умолчанию: 30030] --tokens-file Файл bearer-токенов. Обязателен для streamable-http, если не указан --allow-no-auth --tls-cert Сертификат TLS в формате PEM (только для streamable-http). Используется вместе с --tls-key --tls-key Ключ TLS в формате PEM (только для streamable-http). Используется вместе с --tls-cert --allow-no-auth Отключить аутентификацию по bearer-токену. Привязка к адресу вне loopback запрещена --allow-insecure-bind Разрешить привязку к адресу вне loopback через HTTP без шифрования. Обязателен для хостов, отличных от 127.0.0.1, если TLS не настроен --inventory-readonly Всегда отклонять add_device и reload_devices --allow-password-auth-add Разрешить add_device принимать auth.type=password (несовместим с --inventory-readonly) --device-lease-dir Общий каталог для блокировок деструктивных операций между процессами [по умолчанию: /var/lib/jmcp/device-leases] --allowed-host Дополнительные значения Host, принимаемые конечной точкой streamable-http помимо стандартных адресов loopback (localhost, 127.0.0.1, ::1). Параметр можно указывать несколько раз -h, --help Показать справку -V, --version Показать версию">
Junos MCP server (Rust)
Usage: rust-junosmcp [OPTIONS] [COMMAND]
Commands:
token Manage the bearer-token store
help Print this message or the help of the given subcommand(s)
Options:
-f, --device-mapping <DEVICE_MAPPING>
JSON file with device mapping (Juniper junos-mcp-server compatible) [default: devices.json]
-t, --transport <TRANSPORT>
Transport [default: stdio] [possible values: stdio, streamable-http]
-H, --host <HOST>
Bind host (streamable-http only) [default: 127.0.0.1]
-p, --port <PORT>
Bind port (streamable-http only) [default: 30030]
--tokens-file <TOKENS_FILE>
Bearer-token file. Required for streamable-http unless --allow-no-auth
--tls-cert <TLS_CERT>
PEM-encoded TLS cert (streamable-http only). Pair with --tls-key
--tls-key <TLS_KEY>
PEM-encoded TLS key (streamable-http only). Pair with --tls-cert
--allow-no-auth
Disable bearer-token auth. Refuses to bind off-loopback
--allow-insecure-bind
Bind off-loopback over plain HTTP. Required for non-127.0.0.1 hosts when TLS is not configured
--inventory-readonly
Reject add_device and reload_devices unconditionally
--allow-password-auth-add
Permit add_device to accept auth.type=password (mutually exclusive
with --inventory-readonly)
--device-lease-dir <DEVICE_LEASE_DIR>
Shared directory for cross-process destructive-operation leases
[default: /var/lib/jmcp/device-leases]
--allowed-host <HOST>
Additional Host authorities to accept on the streamable-http
endpoint, beyond the loopback defaults (localhost, 127.0.0.1, ::1).
Repeatable
-h, --help
Print help
-V, --version
Print version
Тестирование на реальном устройстве
JMCP_TEST_HOST=10.0.0.1 \
JMCP_TEST_USER=admin \
JMCP_TEST_PASS=secret \
cargo test -p rust-junosmcp-core --test integration_real_device -- --ignored --nocapture
Пересылка аудита в хранилище событий
Журнал аудита не хранится только на этом хосте. Сервер следует общему для семейства стандарту — стандарту пересылки аудита.
Запись аудита, существующая только на машине, где она была создана, — это не журнал аудита, а файл журнала на компьютере, оператор которого является объектом этой записи.
Формат записи (уже действует)
--audit-format json \
--audit-log-file /var/lib/jmcp/audit.jsonl
Формат JSON обязателен. Формат text предназначен для чтения в терминале и не подходит для обработки. Файл предназначен для оператора и должен ротироваться — сервер никогда не очищает его содержимое.
Транспорт (описан, но пока не реализован)
Записи напрямую сохраняются в ssdf.audit в виде строк, связанных хеш-цепочкой. Согласно объединённому контракту SSDF для доказательств, благодаря этому можно обнаружить удаление или изменение строки. Работа отслеживается в mecmcp#292.
Более простой путь через syslog был разработан, но от него отказались: он работает, однако записи не связаны цепочкой. Все остальные связи в этой схеме по своей конструкции защищены от незаметного изменения: дайджесты плана привязывают согласования, в согласованиях указан отдельный субъект, а token_verified_fields отделяет подтверждённое происхождение данных от заявленного. Последний, не связанный цепочкой этап лишил бы схему этой гарантии именно там, где она нужна аудитору. Обоснование зафиксировано в стандарте.
Как читать результат
В token_verified_fields перечислены поля происхождения данных, достоверность которых подтвердил токен.
Остальные поля этой группы — client_name, model_id, session_id — заявлены клиентом и ничем не подтверждены. Не считайте их равнозначными.
request_id связывает событие транспорта, событие обработчика и, в случае Junos, комментарий к фиксации изменений на устройстве.
Лицензия
Лицензия MIT.
проект mechub · детерминированная логика принимает решения · модель объясняет · человек утверждает