API ВЕГА
English

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 задаётся оператором при создании токена, а не подтверждается сервером. Если передать токен с меткой human LLM-агенту, это обойдёт защиту.

Реализованные возможности

  • Инвентаризация нескольких кластеров: один сервер обслуживает множество кластеров. Для каждого кластера задаются собственный 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.Auditget_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.Auditget_storage, list_backups, list_isos, list_templates
VM.PowerMgmtstart_vm, stop_vm, shutdown_vm, reset_vm, start_container, stop_container, restart_container
VM.Snapshotcreate_snapshot, delete_snapshot (операция plan_proxmox_destroy при применении)
VM.Snapshot.Rollbackrollback_snapshot (операция при применении)
VM.Backupcreate_backup, restore_backup (операция при применении)
VM.Cloneclone_vm
VM.Config.Diskresize_disk
VM.Config.CPU, VM.Config.Memoryupdate_container_resources (для ядер CPU и памяти/swap соответственно)
VM.Config.NetworkПрименение правил межсетевого экрана гостевой системы (apply_firewall_change для одной гостевой системы). Proxmox проверяет эту привилегию для /vms/{vmid}
VM.Allocatecreate_vm, create_container, delete_vm/delete_container (операция destroy_guest при применении), а также restore_backup, если при восстановлении перезаписывается существующий VMID
Datastore.AllocateSpacecreate_vm/create_container (выделение дискового пространства), create_backup, delete_backup
Datastore.AllocateTemplatedownload_iso (целевое хранилище)
Datastore.Allocatedelete_iso. Дополнительная привилегия, отсутствующая в базовой роли; см. ниже
Sys.AccessNetworkdownload_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-серверов. Этот механизм не включается в релизный бинарный файл.

Соседние серверы

rustjunosmcprustpanosmcprustmistmcprustunifimcprustsdcmcprustproxmoxmcp
ПроизводительJuniper Junos / SRXPalo Alto PAN-OSJuniper MistUbiquiti UniFi NetworkHPE Juniper Security Director CloudProxmox VE
ТранспортNETCONF через SSHHTTPS XML-APIHTTPS RESTHTTPS RESTHTTPS RESTHTTPS 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 / KVMnwfilter на интерфейсе гостевой системы
Kubernetesисходящий трафик, контролируемый NetworkPolicy на CNI, который поддерживает эту функцию
Облачный экземплярфильтр пакетов внутри гостевой системы для обоих адресов метаданных и группы безопасности для всего остального
Физический сервер, VM с работающим systemdдирективы unit; этот раздел не применим

Какой бы вариант вы ни выбрали, стоит проверить два свойства: оба часто встречаются и оба приводят к тому, что контроль выглядит настроенным, хотя фактически не работает.

  • Некоторые уровни принимают правила для исходящего трафика, но не применяют их. Обычно это происходит при подключении к сети контейнера и в некоторых реализациях CNI.

  • Трафик к облачным метаданным часто обходит облачный межсетевой экран. В EC2 трафик к IMDS обрабатывается на уровне ниже групп безопасности и слоя NACL, поэтому правило для исходящего трафика там его не блокирует. Это относится и к конечной точке IPv6: fd00:ec2::254 — ULA-адрес, а не link-local-адрес. Поэтому легко принять его за обычный маршрутизируемый трафик, который контролирует межсетевой экран, хотя это не так. Контроль должен применяться внутри гостевой системы либо IMDS нужно полностью отключить. Сверьтесь с актуальными рекомендациями поставщика по защите метаданных: они меняются, а ошибка остаётся незаметной.

Какой бы вариант вы ни выбрали, непроверенное изнутри рабочей нагрузки правило остаётся лишь предположением. Проверьте его, а затем повторите проверку после перезагрузки: правила межсетевого экрана в ядре не сохраняются, если вы не настроили их сохранение.

Лицензия: MIT.