rustproxmoxmcp
Один Rust MCP-сервер для множества кластеров Proxmox VE
проект mechub — автоматизация безопасности суверенных сетей
Неофициальный проект сообщества. Это независимый проект сообщества, не связанный с Proxmox Server Solutions GmbH и не получивший её одобрения. Названия продуктов и товарные знаки используются исключительно для указания систем, с которыми взаимодействует программное обеспечение.
Статус: 0.11.0 — инструментарий полностью готов, за исключением одного пробела
50 вызываемых инструментов: 31 инструмент чтения, 18 инструментов уровня low и apply_proxmox_change_set как единственная точка входа уровня destructive. Ещё семь имён — delete_vm, delete_container, delete_snapshot, delete_backup, delete_iso, restore_backup, rollback_snapshot — это области авторизации, а не инструменты: токен предоставляет их по имени, а доступ к ним осуществляется через plan_proxmox_destroy. Поэтому KNOWN_TOOLS содержит 57 записей.
Что ещё не реализовано
execute_vm_command. Это сделано намеренно. Согласно проектной спецификации, инструмент можно добавить, только еслиmecmcp-policyбудет компилировать набор правил разрешения и запрета для субъекта команды. Такая возможность ещё не подключена. Произвольное выполнение команд внутри каждой гостевой системы — это удалённое выполнение кода в виде вызова инструмента; такой инструмент либо поставляется с движком политик, либо не поставляется вовсе.
Оставшийся пробел отслеживается в #57. Для всего остального, что предлагает сторонний proxmox-mcp, здесь есть эквивалент. Подробнее см. docs/MIGRATING-FROM-PROXMOX-MCP.md; в документе также перечислены аргументы, формат которых изменился.
Контроль изменений
Деструктивные операции выполняются по схеме: планирование → утверждение → применение. При планировании сервер формирует предварительный просмотр и регистрирует действие. Утверждение привязывается к дайджесту плана, рассчитанному по (owner, device, expected fingerprint, actions). При применении сервер повторно проверяет отпечаток гостевой системы и отказывает, если тот изменился.
Важно прямо обозначить два момента: в обоих случаях легко ошибочно предположить обратное.
- Утверждение привязано к предварительному просмотру. Начиная с mecmcp 0.23.0, дайджест утверждения включает хеш сохранённого предварительного просмотра. Таким образом, утверждающий подтверждает не только операцию и её параметры, но и точный текст, который ему показали. Если после утверждения изменить или удалить этот предварительный просмотр, координатор отклонит последующую запись (#56).
Это отличается от того, что было сказано в более ранних редакциях этого раздела. До версии 0.23.0 предварительный просмотр сохранялся вместе с собственным хешем, но не входил в дайджест. Поэтому утверждающий подтверждал действие, а предварительный просмотр лишь формировался на его основе.
--lab-mode— это переопределение защиты, а не безусловное снятие всех ограничений. Этот режим предоставляет переопределение, необходимое для защищённой гостевой системы. Поэтому на сервере в режиме лаборатории защищённая гостевая система при создании получает утверждение без участия второго субъекта, а для обычной гостевой системы второй субъект по-прежнему необходим; самостоятельное утверждение запрещено. Такое отличие нередко становится неожиданностью.
Файл --waivers-file (по умолчанию /etc/proxmoxmcp/waivers.json, права доступа 0600, владелец — служебная учётная запись) содержит временные исключения оператора. Оба переопределения задаются вне вызова инструмента: инструмента grant_waiver и аргумента force намеренно нет, поскольку переданное вызывающей стороной переопределение не является настоящим переопределением.
- Исключение снимает только защиту, но не требование второго утверждающего. Подходящее исключение позволяет плану для защищённой гостевой системы пройти проверку защиты. Однако созданный набор изменений по-прежнему проходит обычную последовательность планирование → утверждение → применение и требует отдельного утверждающего-человека. Единственное исключение —
--lab-mode, который также снимает требование об утверждении. Причина и номер заявки, указанные в исключении, выводятся в сохранённом предварительном просмотре, чтобы утверждающий мог увидеть, почему защита снята, и решить, утверждать ли операцию.
Схема waivers.json
{
"version": 1,
"waivers": [
{
"cluster": "pve3",
"vmid": 905,
"until": "2026-12-31T23:59:59Z",
"reason": "decommission per CHG-1234",
"ticket": "CHG-1234",
"ops": ["destroy_guest"],
"principal": "ops-token"
}
]
}
-
Поле
opsобязательно и не может быть пустым. При загрузке сервер отклоняет запись версии 1 в файле исключений, если полеopsотсутствует или содержит пустой список: сервер не запустится с исключением, которое не распространяется ни на одну операцию. Это несовместимое с прежними версиями и намеренно безопасное при отказе изменение по сравнению с файлами исключений до MEC-447, в которых поляopsне было. Перед обновлением добавьте это поле в каждую запись и укажите в нём точные операции, на которые она должна распространяться. -
Поле
principalнеобязательно. Если оно задано, исключение подходит только для токена с указанным именем; если не задано — для любого вызывающего. Здесь указывается токен, который планирует операцию, а не тот, который её утверждает или применяет:approve_proxmox_change_setиget_proxmox_change_setпроверяют исключение по данным планировщика, сохранённым в наборе изменений. Это необходимо, поскольку правило двух участников требует, чтобы утверждающий был отдельным субъектом, не совпадающим с планировщиком. Поэтому исключение, привязанное кprincipal, отклонит применение, если его выполняет не планировщик. Это ожидаемое безопасное поведение, а не ошибка. -
Строки операций в
opsзависят от пути, для которого предназначено исключение:-
Деструктивная операция, запланированная через
plan_proxmox_destroy: значение аргументаop, переданное этому вызову (destroy_guest,delete_snapshot,rollback_snapshot,delete_backup,delete_iso,restore_backup,migrate,update_vm_config). -
Восстановление с назначением нового VMID (
restore_backup_new_vmid): фиксированная строкаrestore_new_vmid. Она обозначает гостевую систему — владельца архива, которую защищает исключение, а не новый VMID. -
Инструмент уровня
low, прерывающий работу сервиса (например,stop_vm,stop_container,reboot_vm): имя самого инструмента. -
Изменение правила HA, запланированное через
plan_ha_rule_change:ha_rule_, за которым следует значениеop, переданное этому вызову (ha_rule_create,ha_rule_update,ha_rule_delete). -
Изменение межсетевого экрана, запланированное через
plan_firewall_change:firewall_, за которым следуют объект и операция (firewall_rule_create,firewall_options_update,firewall_ipset_entry_delete).
-
Если в операции допущена опечатка или она не указана в исключении, исключение не сработает — вызов будет отклонён так же, как если бы исключения не было.
- Для
approve_proxmox_change_setтребуется токен утверждающего-человека. Сервер передаёт значениеactor_typeтокена вызывающего в mecmcp, который отклоняет любое утверждение отagentили неатрибутированного вызывающего (stdio). Утверждать может только субъект сactor_type: human. Создайте токен утверждающего с помощьюrust-proxmoxmcp token add ... --actor-type human. Значениеactor_typeзадаётся оператором при создании токена, а не подтверждается сервером. Если передать токен с меткойhumanLLM-агенту, это обойдёт защиту.
Реализованные возможности
-
Инвентаризация нескольких кластеров: один сервер обслуживает множество кластеров. Для каждого кластера задаются собственный API-токен и политика защиты.
-
Двухэтапная авторизация:
-
Этап 1 (до вызова каталога): проверка Bearer-токена, областей доступа к инструментам и кластерам.
-
Этап 2 (только для инструментов, адресуемых по гостевой системе): поиск гостевой системы, проверка прав (диапазоны VMID, теги, селекторы пулов) и отказ при невозможности безопасно проверить защиту.
-
-
Объединение правил защиты: гостевая система считается защищённой, если она указана в
protected_vmidsили имеет тег изprotected_tags. Любой деструктивный инструмент или инструмент, прерывающий работу сервиса, откажет в операции над защищённой гостевой системой, если исключение или режим лаборатории не предоставляют переопределение. Инструменты чтения работают с защищёнными гостевыми системами как обычно. -
Инструмент, адресуемый по гостевой системе, никогда не принимает узел от вызывающей стороны. Узел определяется заново при каждом вызове и повторно при применении, поскольку гостевые системы могут мигрировать. Инструменты, работающие с узлами, хранилищами и задачами (
get_node_status,get_storage,list_backups,list_isos,list_templates,list_tasks,get_task_status,download_iso,get_node_firewall_rules,get_node_firewall_options), принимают параметрnode, поскольку адресуются к узлу, — гостевой системы, по которой можно было бы определить узел, здесь нет. Важное исключение —create_vm/create_container: они создают гостевую систему, которой ещё не существует, поэтому вызывающая сторона указывает узел. Это единственный случай, когда вызов, адресуемый по гостевой системе, может попасть не на тот узел.stop_taskнамеренно не включён в этот список: он получает узел из UPID. -
Диспетчеризация на основе каталога: HTTP-метод, шаблон пути, флаг запроса и фильтр типов для каждого инструмента чтения объявлены один раз в
catalog.rs. -
Восстановление незавершённых операций: если после перезапуска набор изменений остаётся в состоянии
Applyingи у него есть дескриптор задачи, при запуске сервер повторно проверяет его. Это позволяет разрешить результат применения, а не оставлять его неопределённым навсегда. -
Перезагрузка по SIGHUP: команда
systemctl reloadобновляетclusters.jsonбез разрыва выполняющихся вызовов. Если перезагрузка завершается ошибкой, сервер записывает её в журнал и сохраняет предыдущую конфигурацию. -
Закрепление CA для каждого кластера: для каждого кластера можно указать
ca_pem_path. Проверка сертификата не отключается ни на одном уровне, поэтому для кластера с частным CA необходимо установить его сертификат CA, а обращаться к кластеру нужно по имени, которое указано в сертификате. -
Журнал аудита: журналы в формате JSON с возможностью скрывать персональные данные (с помощью ключа HMAC или полного удаления). Для каждого вызова инструмента записываются кластер, гостевая система, уровень и статус защиты.
31 инструмент чтения
| Инструмент | Область действия | Описание |
|---|---|---|
get_cluster_status | кластер | Кворум и состав узлов |
get_nodes | кластер | Все узлы с их статусом и суммарными ресурсами |
get_node_status | узел | Подробный статус одного узла |
get_vms | кластер | Все гостевые системы QEMU с указанием узла, статуса и тегов (поддерживает пагинацию: offset/limit, по умолчанию 500, максимум 700) |
get_containers | кластер | Все гостевые системы LXC с указанием узла, статуса и тегов (поддерживает пагинацию: offset/limit, по умолчанию 500, максимум 700) |
get_vm_config | гостевая система (только QEMU) | Конфигурация, включая дайджест Proxmox. Содержимое description/cicustom/args редактируется с наилучшими возможными усилиями: похожие на учетные данные строки удаляются, однако хранить секреты здесь небезопасно (sshkeys и сетевые настройки сохраняются) |
get_container_config | гостевая система (только LXC) | Конфигурация, включая дайджест Proxmox. Содержимое description/cicustom/args редактируется с наилучшими возможными усилиями: похожие на учетные данные строки удаляются, однако хранить секреты здесь небезопасно (sshkeys и сетевые настройки сохраняются) |
get_container_ip | гостевая система (только LXC) | Сетевые интерфейсы и адреса |
get_guest_status | гостевая система | Текущий статус выполнения |
list_snapshots | гостевая система | Снимки одной гостевой системы |
get_storage | узел | Хранилища, доступные одному узлу |
list_backups | хранилище | Архивы резервных копий в одном хранилище (поддерживает пагинацию: offset/limit, по умолчанию 500, максимум 700) |
list_isos | хранилище | ISO-образы в одном хранилище |
list_templates | хранилище | Шаблоны контейнеров в одном хранилище |
list_tasks | узел | Недавние задачи на одном узле (без пагинации: Proxmox применяет собственное серверное ограничение, обычно возвращая 50 последних задач) |
get_task_status | задача | Статус одной задачи по UPID |
get_proxmox_change_set | набор изменений | Состояние и предварительный просмотр одного набора изменений |
get_cluster_firewall_rules | кластер | Правила межсетевого экрана на уровне всего кластера |
get_cluster_firewall_options | кластер | Параметры межсетевого экрана на уровне всего кластера (включение, политика по умолчанию) |
list_firewall_security_groups | кластер | Группы безопасности, определенные в кластере |
get_firewall_security_group_rules | кластер + группа | Правила, входящие в одну группу безопасности |
list_firewall_ipsets | кластер | IPSets на уровне всего кластера |
get_firewall_ipset_entries | кластер + ipset | Записи CIDR в одном IPSet уровня кластера |
list_firewall_aliases | кластер | Псевдонимы адресов межсетевого экрана на уровне всего кластера |
get_node_firewall_rules | узел | Правила межсетевого экрана на одном узле |
get_node_firewall_options | узел | Параметры межсетевого экрана на одном узле |
get_guest_firewall_rules | гостевая система | Правила межсетевого экрана одной гостевой системы |
get_guest_firewall_options | гостевая система | Параметры межсетевого экрана одной гостевой системы |
list_guest_firewall_aliases | гостевая система | Псевдонимы адресов межсетевого экрана одной гостевой системы |
list_guest_firewall_ipsets | гостевая система | IPSets, определенные для одной гостевой системы |
get_guest_firewall_ipset_entries | гостевая система + ipset | Записи CIDR в одном IPSet гостевой системы |
Три инструмента чтения с указанием типа гостевой системы определяют несовпадение по имени, а не обращаются к несуществующей конечной точке.
Инструменты чтения настроек межсетевого экрана повторяют структуру областей, заданную в самом Proxmox: на уровне узла доступны правила и параметры, но нет псевдонимов, IPSets и групп безопасности — они существуют только на уровне кластера и гостевой системы. Для изменения настроек межсетевого экрана используются plan_firewall_change, approve_firewall_change и apply_firewall_change.
Изменение настроек межсетевого экрана без утвержденного набора изменений запрещено. Для режима лаборатории и утверждения двумя лицами действуют те же правила, что и для остальных операций с контролируемым доступом: --lab-mode утверждает план для защищенной гостевой системы без участия второго субъекта, а для обычной гостевой системы, а также межсетевого экрана кластера или узла по-прежнему требуется второй участник.
Пагинация: get_vms, get_containers и list_backups могут возвращать неограниченное количество результатов в зависимости от размера кластера, узла или хранилища. В крупной среде объем ответа может превысить ограничение MCP в 512 KiB. Эти инструменты принимают необязательные параметры offset и limit (по умолчанию 500, максимум 700 — значение выбрано так, чтобы оставаться с запасом ниже ограничения) и возвращают {items, total, offset, limit, has_more}, а не простой массив. Так вызывающая сторона может определить, получен ли полный короткий список или требуется запросить следующую страницу. Значение limit больше 700 отклоняется, а не ограничивается автоматически. Страницы не являются снимком данных: при каждом вызове инструмент заново получает полный список из вышестоящего источника и сортирует его по vmid (гостевые системы) или volid (резервные копии) перед выборкой. Поэтому запись может переместиться на другую страницу только в случае создания или удаления записей между вызовами, но не из-за изменения порядка. Для list_tasks пагинация намеренно не реализована: конечная точка Proxmox /nodes/{node}/tasks применяет собственное серверное ограничение, при этом клиент не может узнать общее число задач. Дополнительная пагинация привела бы к тому, что усеченный список ошибочно выглядел бы полным.
18 инструментов низкого уровня
Управление жизненным циклом: start_vm, stop_vm, shutdown_vm, reset_vm*,
start_container, stop_container, restart_container.
Подготовка ресурсов: create_vm, create_container, clone_vm, download_iso,
resize_disk, create_snapshot, create_backup,
update_container_resources*.
Задачи и наборы изменений: stop_task*, plan_proxmox_destroy,
approve_proxmox_change_set.
- прерывает работу запущенной гостевой системы. Этот признак отслеживается отдельно от уровня инструмента: инструмент может относиться к уровню
lowи при этом останавливать службу, поэтому механизм защиты применяется и к таким операциям.
На что стоит обратить внимание:
-
По умолчанию
create_containerустанавливаетunprivileged=1. Если не указать это поле, Proxmox считает контейнер привилегированным, поэтому пропуск поля не должен приводить к выбору опасного варианта. -
Значения конфигурации, передаваемые гипервизору, отклоняются:
hookscript,args, точки монтирования хостаmpN, перенаправление устройствhostpciN/usbN/devN/serialN/parallelN, параметрыlxc.*и любые значения с абсолютным путем на хосте. Создание относится к операциям уровняlowи не должно приводить к выполнению кода на узле. -
При создании отклоняется VMID, который уже существует. Proxmox восстанавливает резервную копию запросом POST к той же конечной точке. Без этой проверки операция создания уровня
lowмогла бы перезаписать работающую гостевую систему. -
resize_diskподдерживает только увеличение размера. Уменьшение здесь не поддерживается и не поддерживается в Proxmox — командыqm resizeиpct resizeотклоняют уменьшение. -
Контейнеры останавливаются немедленно. Для LXC нет варианта корректного завершения работы:
shutdown_vmдоступен только для QEMU.
Изменение областей доступа токена
token set-scopes позволяет изменить области доступа токена к устройствам, инструментам, гостевым системам и действиям без повторной выдачи секрета. Поэтому перенастраивать клиент не требуется:
[--devices ] [--tools ]
[--guests ] [--actions read,low,destructive] [--yes]">
rust-proxmoxmcp token set-scopes --tokens-file <PATH> --name <NAME> \
[--devices <CSV|*>] [--tools <CSV|*>] \
[--guests <SELECTORS|*>] [--actions read,low,destructive] [--yes]
Если --devices или --tools не указаны, соответствующая область доступа остается без изменений. Параметры --guests и --actions полностью заменяют предоставленные разрешения, а не дополняют их. Для доступа к гостевым системам особенно важно, чтобы «я хотел заменить разрешение» случайно не означало «я добавил новое». Параметр --actions нельзя указывать отдельно: разрешение включает обе части, и создание недостающей части означало бы предоставление доступа без явно указанной области.
Расширение доступа повышает привилегии и требует интерактивного подтверждения, если не указан параметр --yes. Для сужения области доступа подтверждение не требуется, поскольку оно не может предоставить дополнительные права.
--tools '*' не дает доступа к инструментам, изменяющим состояние. WRITE_TOOLS намеренно исключен из списка инструментов, доступных по маске. Поэтому такие инструменты, как start_vm, необходимо указывать явно; в противном случае предварительная проверка завершится ошибкой 403 insufficient_scope.
Модель авторизации
Этап 1: Bearer-токен и область доступа
Для каждого вызова через streamable-HTTP передается Bearer-токен. Хранилище токенов (tokens.json) связывает токен со следующими параметрами:
-
Область инструментов (
tools: ["*"]илиtools: ["get_nodes", "get_vms"]) -
Область устройств (
devices: ["*"]илиdevices: ["pve3"]) -
Разрешение (см. этап 2)
На этапе 1 запрос отклоняется в следующих случаях:
-
Bearer-токен недействителен или отсутствует (если только не включен параметр
--allow-no-authдля loopback) -
Инструмент не входит в список
toolsтокена -
Кластер не входит в список
devicesтокена -
Инструмент из
WRITE_TOOLSзапрашивается через область доступа с маской.tools: ["*"]намеренно не включает этот набор, поэтому токен с маской не дает доступа ни к одному инструменту, изменяющему состояние: каждый такой инструмент нужно указывать явно.
Токены без ключа grant не допускаются к использованию с инструментами, адресованными гостевым системам. Это политика с отказом по умолчанию: если токен не задает селектор гостевых систем, он не должен автоматически получать доступ ко всем гостевым системам.
Этап 2: Определение гостевых систем и защита
Инструменты, работающие с гостевыми системами, определяют VMID и находят соответствующую запись GuestFacts (имя, узел, тип, теги, пул), а затем проверяют разрешения токена:
{
"guests": ["vmid:600-699", "tag:disposable", "pool:test-vms"],
"actions": ["read"]
}
Гостевая система входит в область действия разрешения, если совпадает хотя бы один критерий. Затем сервер проверяет объединение правил защиты:
-
Гостевая система считается защищённой, если её VMID указан в
protected_vmidsкластера или у неё есть тег изprotected_tags(по умолчанию:["protected"]). -
Ни один изменяющий данные инструмент не может обращаться к защищённой гостевой системе, даже если в разрешении указано
"guests": ["*"]. -
Инструменты чтения видят защищённые гостевые системы без ограничений.
Если гостевая система не входит в область действия разрешения или уровень действия (read/low/destructive) не указан в разрешениях токена, сервер отклоняет запрос и возвращает сообщение, не раскрывающее подробности: "authorization failed" без сведений о гостевой системе.
Конфигурация
Инвентаризация кластеров: clusters.json
{
"version": 1,
"devices": {
"pve3": {
"endpoint": "https://pve3.example.org:8006",
"token_id": "mcp-automation@pve!mcp",
"token_secret_env": "PVE_PVE3_TOKEN",
"protected_vmids": [905, 906, 907],
"protected_tags": ["protected"]
}
},
"policy": {
"resource_cache_ttl_secs": 10
}
}
Примечание: Корневой ключ — devices, а не clusters. Это канонический формат mecmcp-inventory; сервер обрабатывает каждую запись в нём как кластер.
Учётные данные не хранятся в этом файле. Для доступа к секрету API-токена каждый кластер использует один из двух способов:
-
token_secret_file(по умолчанию): указывает на отдельный файл, например/etc/proxmoxmcp/secrets/.token. Это более надёжный вариант: файл считывается тем же защищённым загрузчиком, что иclusters.jsonиtokens.json(права 0600, обычный файл, владелец — пользователь службы,O_NOFOLLOW). При этом учётные данные не попадают в окружение процесса, где могли бы оказаться в дампах аварийного завершения или/proc//environ. -
token_secret_env: задаёт имя переменной окружения. Этот способ поддерживается черезEnvironmentFile=-/etc/proxmoxmcp/secrets.envв модуле systemd. Префикс-означает, что отсутствие файла не считается критической ошибкой. Этот вариант менее надёжен, поскольку учётные данные становятся доступными из окружения процесса.
Оба способа используются через mecmcp-secret: он создаёт OutboundSecret, который обнуляется при удалении и не реализует ни Debug, ни Serialize.
Токен на стороне Proxmox: минимальные привилегии вместо root@pam
token_id указывает на API-токен Proxmox. Привилегии, назначенные этому токену на стороне Proxmox, образуют второй уровень авторизации, который сервер не контролирует.
Этапы 1 и 2 (выше) ограничивают действия вызывающей стороны MCP, но не определяют, какие операции разрешены базовым учётным данным Proxmox после того, как запрос поступит в кластер. Если передать серверу токен root@pam!..., этот второй уровень защиты исчезнет: root@pam — встроенная учётная запись суперпользователя Proxmox, полностью обходящая проверки ACL. Подробнее см. документацию Proxmox VE по управлению пользователями. Поэтому назначение роли, ограничение путём и любые настройки в clusters.json не смогут ограничить такой токен. Ошибка в сервере, кража токена или чрезмерно широкое разрешение в tokens.json приведут к полному управлению кластером вместо безопасного отказа в пределах ограниченной роли.
Вместо этого создайте отдельного пользователя без прав суперпользователя в области pve и назначьте ему пользовательскую роль, включающую только привилегии, которые действительно используют инструменты сервера:
` вместо этого и выпустите отдельный токен для каждого пула; такой вариант развёртывания не является обязательным для этого сервера. pveum acl modify / --users mcp-automation@pve --roles ProxmoxMcp
--privsep 0: токен получает ровно те же права, что и пользователь, поэтому
указанная выше роль полностью определяет набор привилегий токена и не оставляет
дополнительных прав, которые можно было бы ему назначить.
или забудьте о нём в отдельном ACL на уровне токена.
pveum user token add mcp-automation@pve mcp --privsep 0">
# A role scoped to exactly what rustproxmoxmcp's tools call, no more.
pveum role add ProxmoxMcp -privs "VM.Audit,Sys.Audit,Datastore.Audit,VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback,VM.Backup,VM.Clone,VM.Config.Disk,VM.Config.CPU,VM.Config.Memory,VM.Allocate,Datastore.AllocateSpace,Datastore.AllocateTemplate,Sys.AccessNetwork"
# A service account with no interactive password -- it is only ever reached
# through its API token.
pveum user add mcp-automation@pve --comment "rustproxmoxmcp service account"
# Grant the role cluster-wide (`/`), matching this server's own reach: guests
# migrate between nodes and clusters.json addresses a whole cluster, not one
# VM or pool. An operator who wants to scope one token to one pool of guests
# can grant ProxmoxMcp at `/pool/<name>` instead and mint a separate token per
# pool; that is a deployment choice this server does not require.
pveum acl modify / --users mcp-automation@pve --roles ProxmoxMcp
# --privsep 0: the token carries exactly the user's own permissions, so the
# role above is the token's complete privilege set with nothing left to grant
# or forget on a separate token-level ACL.
pveum user token add mcp-automation@pve mcp --privsep 0
Последняя команда выводит секрет токена только один раз. Сохраните его в файле,
на который указывает token_secret_file (или имя которого задано переменной token_secret_env), —
но не в самом clusters.json.
Каждая привилегия в ProxmoxMcp соответствует определённым инструментам, которые регистрирует этот сервер.
Ничего сверх этого не предоставляется: ни Sys.PowerMgmt (перезагрузка узла), ни VM.Console или
Sys.Console, ни VM.Migrate, ни Pool.*/Group.Allocate/Realm.Allocate/
Permissions.Modify — сервер не вызывает конечные точки Proxmox, для которых требуются эти
привилегии.
| Привилегия | Инструменты, которым она нужна |
|---|---|
VM.Audit | Все операции чтения данных гостевой системы: get_vms, get_containers, get_vm_config, get_container_config, get_container_ip, get_guest_status, list_snapshots, get_guest_firewall_rules, get_guest_firewall_options, list_guest_firewall_aliases, list_guest_firewall_ipsets, get_guest_firewall_ipset_entries, а также чтение данных для определения гостевой системы и проверки её отпечатка при каждом выполнении плана и его применении |
Sys.Audit | get_cluster_status, get_nodes, get_node_status, list_tasks, get_task_status, get_cluster_firewall_rules, get_cluster_firewall_options, list_firewall_security_groups, get_firewall_security_group_rules, list_firewall_ipsets, get_firewall_ipset_entries, list_firewall_aliases, get_node_firewall_rules, get_node_firewall_options |
Datastore.Audit | get_storage, list_backups, list_isos, list_templates |
VM.PowerMgmt | start_vm, stop_vm, shutdown_vm, reset_vm, start_container, stop_container, restart_container |
VM.Snapshot | create_snapshot, delete_snapshot (операция plan_proxmox_destroy при применении) |
VM.Snapshot.Rollback | rollback_snapshot (операция при применении) |
VM.Backup | create_backup, restore_backup (операция при применении) |
VM.Clone | clone_vm |
VM.Config.Disk | resize_disk |
VM.Config.CPU, VM.Config.Memory | update_container_resources (для ядер CPU и памяти/swap соответственно) |
VM.Config.Network | Применение правил межсетевого экрана гостевой системы (apply_firewall_change для одной гостевой системы). Proxmox проверяет эту привилегию для /vms/{vmid} |
VM.Allocate | create_vm, create_container, delete_vm/delete_container (операция destroy_guest при применении), а также restore_backup, если при восстановлении перезаписывается существующий VMID |
Datastore.AllocateSpace | create_vm/create_container (выделение дискового пространства), create_backup, delete_backup |
Datastore.AllocateTemplate | download_iso (целевое хранилище) |
Datastore.Allocate | delete_iso. Дополнительная привилегия, отсутствующая в базовой роли; см. ниже |
Sys.AccessNetwork | download_iso (download-url), привилегия назначается на /nodes/{node}. API2/Storage/Status.pm принимает либо эту привилегию, ограниченную узлом, либо связку Sys.Audit+Sys.Modify на / — вариант с ограничением по узлу не предоставляет доступ к перезагрузке узла, настройке сети или очистке дисков. Для download-url привилегия Sys.AccessNetwork требуется в PVE 8+; в более старых кластерах вместо неё используйте более широкую связку Sys.Audit+Sys.Modify на / |
Datastore.Allocate намеренно не включена в роль выше. Она нужна только инструменту delete_iso,
а обработчик удаления в API2/Storage/Content.pm
проверяет её для хранилища, а не для отдельного ISO-образа. В Proxmox нет привилегии, которая разрешала бы
«удалить этот ISO» без права «изменять или удалять определение хранилища». Если назначить эту привилегию на /,
как это сделано для базовой роли, токен получит права администратора для всех хранилищ кластера: он сможет удалять
определения хранилищ во всём кластере, перенаправлять их или добавлять новые точки монтирования NFS/CIFS/PBS,
к которым затем подключатся все узлы. Если в этой конфигурации необходим инструмент delete_iso, назначьте привилегию
не на весь кластер, а только на хранилище с ISO-образами:
--users mcp-automation@pve --roles ProxmoxMcpIsoDelete">
# Optional: only if delete_iso must work. Scoped to one storage, not `/` —
# still lets the token edit or remove *that storage's* definition, but not
# any other storage in the cluster.
pveum role add ProxmoxMcpIsoDelete -privs "Datastore.Allocate"
pveum acl modify /storage/<iso-storage> --users mcp-automation@pve --roles ProxmoxMcpIsoDelete
Sys.Modify на / намеренно не включена в роль выше. Для применения правил межсетевого экрана к кластеру и
узлу она необходима (для объектов кластера — на /, для межсетевого экрана узла —
на /nodes/{node}), как и для stop_task, когда API2/Tasks.pm останавливает задачу,
принадлежащую не вызывающему её пользователю. Задачи, созданные самим сервером, всегда принадлежат его
токену, поэтому для их остановки дополнительных прав не нужно. Планирование и подтверждение изменений
межсетевого экрана требуют только чтения (Sys.Audit или VM.Audit). Sys.Modify на / предоставляет
широкие права администратора узла — настройку сети узла, инициализацию и очистку дисков, работу с apt
и другие возможности. Добавляйте её только в том случае, если этому токену необходимо применять изменения
межсетевого экрана к кластеру или узлу либо останавливать задачи, запущенные другими субъектами:
pveum role modify ProxmoxMcp -privs "...,Sys.Modify" # append to the existing list
Не проверено; прежде чем использовать в рабочей среде, проверьте на тестовом PVE:
Для вызовов create_vm, create_container и clone_vm, которые добавляют сетевое
устройство, вероятно, также потребуется SDN.Use для моста в PVE 8+. Этот список ролей
не проверялся на мостах под управлением SDN. Если ваш кластер их использует,
сначала проверьте цикл планирования и применения на одноразовой гостевой системе.
plan_proxmox_destroy, approve_proxmox_change_set и
get_proxmox_change_set не вызывают API Proxmox, за исключением чтения данных для определения гостевой системы
(VM.Audit, уже указана выше). Подтверждение — это локальная операция в хранилище наборов изменений самого сервера.
Для плана потребуется привилегия, соответствующая его op в таблице выше. Она используется
только при фактическом выполнении операции инструментом apply_proxmox_change_set.
Хранилище токенов: tokens.json
{
"version": 1,
"tokens": {
"demo-reader": {
"hash": "$argon2id$v=19$m=...",
"grant": {
"guests": ["vmid:600-699"],
"actions": ["read"]
},
"tools": ["*"],
"devices": ["pve3"]
}
}
}
Создайте токен командой rust-proxmoxmcp token add . Открытый текст токена будет выведен только один раз; восстановить его будет невозможно. Для любого токена, который будет подтверждать наборы изменений, укажите --actor-type human — см. раздел «Контроль изменений».
ВАЖНО: запросы к инструментам, адресованным гостевым системам, отклоняются, если в токене нет ключа grant. Чтобы предоставить доступ на чтение ко всем гостевым системам:
"grant": {
"guests": ["*"],
"actions": ["read"]
}
Флаги CLI
rust-proxmoxmcp поддерживает все флаги из mecmcp_runtime::cli::Cli (транспорт, адрес привязки, TLS, разрешённые хосты и источники, аудит) и добавляет только один:
--clusters-file— файл инвентаризации кластеров (по умолчанию:/etc/proxmoxmcp/clusters.json)
Для streamable-HTTP требуется указать либо --tokens-file, либо --allow-no-auth. Второй вариант разрешает неаутентифицированные запросы на чтение только через loopback; инструменты записи при этом остаются недоступны.
Полный список можно посмотреть, выполнив rust-proxmoxmcp --help.
Установка
Пошаговые инструкции по созданию LXC с rust-proxmoxmcp с нуля см. в разделе docs/HOW-TO-SETUP-LXC.md. Инструкции по Docker приведены в разделе docs/HOW-TO-SETUP-DOCKER.md.
Запуск в Docker
Для интерактивного сервера с транспортом stdio подготовьте файл clusters.json, каталог secrets/ и доступный для записи каталог state/. В инвентаре устройств используется стандартная структура devices:
{
"version": 1,
"devices": {
"pve-demo": {
"endpoint": "https://198.51.100.10:8006",
"token_id": "mcp-automation@pve!mcp",
"token_secret_file": "/etc/proxmoxmcp/secrets/pve-demo.token",
"protected_vmids": [100, 101],
"protected_tags": ["protected"]
}
},
"policy": {
"resource_cache_ttl_secs": 10
}
}
Пока вы не замените учетные данные на созданные для вашей среды Proxmox, в файле инвентаря и файле с секретом будут указаны фиктивные значения. Это должны быть обычные файлы с правами доступа 0600, принадлежащие UID/GID 65532:65532 (пользователю, от имени которого работает образ). Подключите каталог state на хосте к /var/lib/proxmoxmcp (том образа) и предоставьте этому каталогу права на запись для того же UID. Этот путь служит корневым каталогом для сохранения состояния наборов изменений и ключа аудита. Именно этот том публикует server.json. Подключайте сам каталог, а не его подкаталог.
chmod 0600 clusters.json secrets/pve-demo.token
chown -R 65532:65532 clusters.json secrets state
docker run --rm -i \
-v "$PWD/clusters.json:/etc/proxmoxmcp/clusters.json:ro" \
-v "$PWD/secrets:/etc/proxmoxmcp/secrets:ro" \
-v "$PWD/state:/var/lib/proxmoxmcp" \
ghcr.io/mechubsec/rustproxmoxmcp:latest \
--transport stdio \
--state-file /var/lib/proxmoxmcp/changeset-state.json
Файл tokens.json с входящими bearer-токенами используется только транспортом streamable-HTTP. У stdio нет хранилища токенов, поэтому в этой конфигурации он не подключается. Параметр --state-file сохраняет состояние наборов изменений и операций при перезапуске. Без него при перезапуске контейнера будут потеряны все подтверждения, предварительные просмотры и выполняющиеся операции применения.
Команда сохраняет пути ENTRYPOINT образа, заменяя HTTP-команду CMD на запуск stdio. Поэтому входящий HTTP-трафик и TLS при таком запуске отключены. Инструкции по настройке streamable-HTTP в режиме для двух пользователей и в лабораторном режиме см. в руководстве по Docker.
Скрипт scripts/package-lxc.sh создает архив LXC для Debian 13. Скрипт packaging/lxc/install.sh — установщик POSIX для этого архива. Установщик выполняет следующие действия:
-
Создает системного пользователя
proxmoxmcp -
Устанавливает бинарный файл в
/usr/local/bin/rust-proxmoxmcp -
Устанавливает примеры конфигурационных файлов в
/etc/proxmoxmcp(права доступа0600, владелец —proxmoxmcp) только если эти файлы отсутствуют -
Устанавливает усиленную конфигурацию systemd с параметрами
ProtectSystem=strictиReadWritePaths=/var/lib/proxmoxmcp -
Выводит дальнейшие инструкции и напоминание о создании снимка контейнера перед обновлением
Перед обновлением: создайте снимок контейнера в Proxmox. Если обновление завершится с ошибкой, его можно отменить, восстановив снимок.
Примечания по разработке
Структура crate
-
rust-proxmoxmcp-core: логика предметной области — инвентарь, разрешение имен, авторизация и каталог. Не содержит сервер или транспорт. -
rust-proxmoxmcp: бинарный файл. Он собирает транспорт, загружает инвентарь и предоставляет каталог.
Функция testing
В core crate есть необязательная функция testing, отключенная по умолчанию. Она подключает rcgen, rustls, tokio-rustls и tempfile для создания тестовых HTTPS-серверов. Этот механизм не включается в релизный бинарный файл.
Соседние серверы
| rustjunosmcp | rustpanosmcp | rustmistmcp | rustunifimcp | rustsdcmcp | rustproxmoxmcp | |
|---|---|---|---|---|---|---|
| Производитель | Juniper Junos / SRX | Palo Alto PAN-OS | Juniper Mist | Ubiquiti UniFi Network | HPE Juniper Security Director Cloud | Proxmox VE |
| Транспорт | NETCONF через SSH | HTTPS XML-API | HTTPS REST | HTTPS REST | HTTPS REST | HTTPS REST |
| Статус | выпущен, v0.25.0 | выпущен, v0.14.0 | создана основа; проверка работы в режиме только для чтения на реальном клиенте пройдена | используется в production | предварительный выпуск (v0.1.0-lab) | выпущен, v0.11.0 |
Все шесть серверов используют mecmcp — семейство общих Rust-crate, лежащее в основе MCP-серверов mechub для разных производителей.
Пересылка аудита в хранилище событий
Журнал аудита не хранится только на этом хосте. Сервер следует общему стандарту семейства: AUDIT-FORWARDING-STANDARD.md.
Записи аудита, хранящиеся только на машине, где они были созданы, не образуют журнал аудита. Это всего лишь файл журнала на компьютере, оператор которого может быть стороной, к которой относится запись.
Формирование записей (уже работает)
--audit-format json \
--audit-log-file /var/lib/proxmoxmcp/audit.jsonl
Формат JSON обязателен. Формат text предназначен для чтения в терминале и не подходит для обработки. Файл — это артефакт для оператора, который необходимо ротировать. Сервер сам его не обрезает, но сохраняет файловый дескриптор, возвращенный mecmcp_audit::init_tracing, и повторно открывает файл по пути при получении SIGHUP. Поэтому при переименовании файла ротатором и отправке сигнала ротация проходит без потери данных.
Готовая к установке конфигурация входит в поставку: packaging/logrotate/rust-proxmoxmcp-audit:
/var/lib/proxmoxmcp/audit.jsonl {
daily
rotate 14
missingok
notifempty
compress
delaycompress
su proxmoxmcp proxmoxmcp
postrotate
systemctl kill -s HUP rust-proxmoxmcp.service >/dev/null 2>&1 || true
endscript
}
Переименовывайте файл и открывайте его заново; не используйте copytruncate. При получении SIGHUP сервер открывает файл аудита по пути заново, как и при горячей перезагрузке существующих файлов clusters.json и tokens.json. Поэтому в секции postrotate файл переименовывается, после чего процессу отправляется сигнал. Все последующие записи попадают в новый файл по тому же пути. Данные, записанные до переименования, не обрезаются, а последующие записи не теряются. В отличие от этого, copytruncate сначала копирует файл, а затем обрезает его на месте, из-за чего теряются данные, записанные между этими действиями.
Транспорт (описан, но пока не реализован)
Записи напрямую добавляются в ssdf.audit в виде строк с цепочкой хешей в соответствии с объединенным контрактом SSDF для доказательств. Это позволяет обнаружить удаление или изменение строки. Работа отслеживается в mecmcp#292.
Более простой вариант с syslog был разработан, но отклонен: он работает, однако записи не связаны цепочкой хешей. При этом все остальные звенья этой схемы изначально позволяют выявлять вмешательство: дайджесты планов связывают их с подтверждениями, в подтверждениях указан отдельный субъект, а token_verified_fields отделяет подтвержденные данные о происхождении от заявленных. Последнее звено без цепочки хешей лишило бы систему этой гарантии именно там, где она нужна аудитору. Обоснование приведено в стандарте.
Как интерпретировать результат
token_verified_fields содержит поля происхождения, достоверность которых подтверждена токеном.
Остальные поля этой группы — client_name, model_id, session_id — заявлены клиентом и ничем не подтверждены. Не считайте их равноценными.
request_id связывает событие транспорта, событие обработчика и, в Junos, комментарий к фиксации изменений на устройстве.
Лицензия
Эксплуатация и безопасность
Фильтрация исходящего трафика
В поставляемой конфигурации systemd указаны IPAddressDeny и IPAddressAllow для управления исходящим трафиком. Однако systemd не может применять эти директивы в непривилегированном LXC, а именно так настроены все гостевые системы в этом парке. systemd реализует их с помощью cgroup BPF и при невозможности загрузить программу пропускает трафик без фильтрации. Таким образом, в конфигурации службы может быть объявлена полная политика исходящего трафика, хотя фактически она не применяется. systemd-analyze security анализирует объявленную конфигурацию и не может выявить это различие.
Установщик проверяет фактическое применение фильтрации и выводит один из четырех результатов:
-
egress filter: ENFORCED— хост подключает программу BPF, а в установленной конфигурации службы объявлена политика -
egress filter: NOT ENFORCED— хост не может подключить программу; далее выводятся инструкции -
egress filter: NO POLICY— хост способен применить фильтрацию, но в установленной конфигурации службы не объявленIPAddressDeny(сохраненная измененная конфигурация заменяет поставляемую; чтобы восстановить ее, выполните установку повторно) -
egress filter: UNKNOWN— проверку не удалось выполнить; никаких утверждений о фильтрации не делается
Необходимо выполнение обоих условий. Проверка только возможностей хоста сообщила бы об успехе, даже если служба ничего не фильтрует.
Проверка использует учет IP-трафика, работающую через тот же механизм подключения BPF. Поэтому ненулевое значение счетчика доказывает, что фильтр подключен. Проверить его можно в любой момент:
systemctl show rust-proxmoxmcp.service -p IPEgressBytes --value
Значение [no data] означает, что директивы фильтрации исходящего трафика не работают. Установите PROXMOXMCP_REQUIRE_EGRESS_FILTER=1, чтобы установщик отказывался продолжать работу при любом результате, кроме ENFORCED. Это относится и к UNKNOWN: хост, который невозможно проверить, не дает больше гарантий, чем хост, не способный применять фильтрацию.
Применение ограничений там, где systemd не справляется
Любой результат, кроме ENFORCED, означает, что директивы unit не подтверждены, и контроль нужно перенести на внешний уровень — туда, где действительно видны пакеты этой рабочей нагрузки. Результаты NOT ENFORCED и NO POLICY показывают, что директивы заведомо ничего не делают; UNKNOWN означает, что проверка не проводилась и они, возможно, работают. Не приравнивайте последний результат к первым двум.
Политика не зависит от среды выполнения, хотя unit разрешает доступ к конечным точкам Proxmox API из диапазона RFC 1918:
-
запретите
169.254.0.0/16иfd00:ec2::254— это адреса облачных метаданных и путь от скомпрометированного HTTP-клиента к украденным учётным данным -
запретите link-local-адреса (
fe80::/10) — ни одна поддерживаемая целевая платформа их не использует -
запретите доступ к локальной подсети, кроме DNS-резолвера. Это блокирует боковое перемещение и при этом сохраняет возможность разрешения имён. Сейчас это правило не объявлено в unit сервера; при необходимости добавьте его через drop-in
Механизм зависит от платформы. Настройте его, руководствуясь документацией именно вашей платформы, а не приведённой здесь инструкцией: ниже перечислены уровни, а не способы настройки.
| Среда выполнения | Уровень, на котором видны пакеты этой рабочей нагрузки |
|---|---|
| Proxmox LXC / VM | межсетевой экран интерфейса гостевой системы |
| libvirt / KVM | nwfilter на интерфейсе гостевой системы |
| Kubernetes | исходящий трафик, контролируемый NetworkPolicy на CNI, который поддерживает эту функцию |
| Облачный экземпляр | фильтр пакетов внутри гостевой системы для обоих адресов метаданных и группы безопасности для всего остального |
| Физический сервер, VM с работающим systemd | директивы unit; этот раздел не применим |
Какой бы вариант вы ни выбрали, стоит проверить два свойства: оба часто встречаются и оба приводят к тому, что контроль выглядит настроенным, хотя фактически не работает.
-
Некоторые уровни принимают правила для исходящего трафика, но не применяют их. Обычно это происходит при подключении к сети контейнера и в некоторых реализациях CNI.
-
Трафик к облачным метаданным часто обходит облачный межсетевой экран. В EC2 трафик к IMDS обрабатывается на уровне ниже групп безопасности и слоя NACL, поэтому правило для исходящего трафика там его не блокирует. Это относится и к конечной точке IPv6:
fd00:ec2::254— ULA-адрес, а не link-local-адрес. Поэтому легко принять его за обычный маршрутизируемый трафик, который контролирует межсетевой экран, хотя это не так. Контроль должен применяться внутри гостевой системы либо IMDS нужно полностью отключить. Сверьтесь с актуальными рекомендациями поставщика по защите метаданных: они меняются, а ошибка остаётся незаметной.
Какой бы вариант вы ни выбрали, непроверенное изнутри рабочей нагрузки правило остаётся лишь предположением. Проверьте его, а затем повторите проверку после перезагрузки: правила межсетевого экрана в ядре не сохраняются, если вы не настроили их сохранение.
Лицензия: MIT.