Files
forge-tools-proxmox/README.md
T

12 KiB
Raw Blame History

forge-tools-proxmox

MCP-сервер для гипервизора Proxmox VE (MCP по stdio) — управление кластером/нодами, QEMU-ВМ, LXC-контейнерами, снапшотами, бэкапами, задачами, хранилищами и сетью. Зеркало архитектуры forge-tools/ssh + postgres: stdio-only, одиночный потокобезопасный Manager, ручные JSON-Schema, --health, graceful shutdown, isolation=pooled (per-agent конфиг приходит на каждый вызов через _tenant_config).

Границы (принцип «дополнять, а не дублировать»). Домен — только гипервизор. Запуск кода внутри гостя — не здесь: это ssh__run (remote exec); локальные файлы — filesystem; БД — postgres. Управление Ceph/ZFS/SDN/firewall/HA/ACL и восстановление бэкапов — кандидаты в отдельные узкие forge-tools (см. «Roadmap»).

Инструменты (54)

Домен Инструменты
cluster (4) clusters_list, cluster_status, nodes_list, node_status (read)
node (1) node_network (read)
VM (13) vms_list, vm_describe, vm_config, vm_next_id (read) · vm_start, vm_stop, vm_reboot, vm_shutdown, vm_suspend, vm_resume, vm_clone, vm_delete, vm_convert_template (мутации)
VM config/disk/net (10) vm_config_update, vm_resize_disk, vm_add_disk, vm_remove_disk, vm_move_disk, vm_add_network, vm_update_network, vm_remove_network, vm_set_cloudinit (мутации)
LXC (12) containers_list, container_describe, container_config (read) · container_start, container_stop, container_reboot, container_shutdown, container_clone, container_delete, container_config_update, container_resize (мутации)
snapshot (4) snapshot_list (read) · snapshot_create, snapshot_delete, snapshot_rollback (мутации)
backup (2) backup_list (read) · backup_create (async UPID)
task (4) tasks_list, task_status (+wait), task_log (read) · task_stop (мутация)
guest (1) guest_ips (read, QEMU guest-agent)
storage (4) storage_list, storage_status, templates_list, isos_list (read)

Конфигурация

Путь per-agent pve.json (pooled → на каждый вызов ядро инжектит _tenant_config); либо статический -config для одиночного режима.

{
  "hosts": [
    { "alias": "pve1",
      "url": "https://10.0.0.10:8006/api2/json",
      "token_id": "root@pam!forge-dev",
      "token_secret": "${PROXMOX_TOKEN_SECRET}",   // из env / gitignored *.local.json
      "ca_file": "${PROXMOX_CA_FILE}",             // предпочтительно; insecure=true только for dev-lab
      "insecure": false,
      "allow_nodes": ["pve1"],
      "allow_vmids": [500] }
  ],
  "default": "pve1",
  "read_only": true,               // false включает мутации (в доп. к confirm+approval)
  "timeout_sec": 15,
  "task_poll_max_sec": 600,
  "max_output_bytes": 4194304
}
  • Секреты никогда в git: token_secret/пароль — только ${VAR} или gitignored *.local.json. В репозитории хранится лишь шаблон pve.json.example; рабочие pve.json/config.json и *.local.json закрыты локальным .gitignore.
  • read_only (дефолт true) и allow_nodes/allow_vmids (per-host) — fail-closed: без явных allow_vmids мутации недостижимы. Это второй слой поверх least-privilege токена.
  • ca_file — кастомный CA для self-signed кластера; insecure — только для локального dev-lab.
  • Live-reload через configreload: правки pve.json применяются без рестарта (по контент-хэшу).

Переменные окружения

Переменная Назначение
FORGE_TENANT_CONFIG Каталог с per-agent pve.json (режим isolation=pooled).
PROXMOX_HOST Адрес гипервизора (подставляется в url).
PROXMOX_TOKEN_ID ID API-токена PVE, напр. root@pam!forge-dev.
PROXMOX_TOKEN_SECRET Секрет API-токена.
PROXMOX_CA_FILE Путь к кастомному CA self-signed кластера.

Значения в конфиге поддерживают подстановку ${VAR} из окружения — реальные адреса и токены в репозиторий не попадают.

Несколько гипервизоров и коллизии VMID

Каждый hosts[] — отдельный кластер со своими кредами/TLS и своими правами. VMID/ноды на разных гипервизорах могут пересекаться, поэтому идентичность ресурса — всегда (host, node, vmid), а не «голый vmid».

  • host выбирает целевой гипервизор (aliасс, дефолт = default).
  • allow_vmids/allow_nodes — per-host: права изолированы между кластерами. Одни и те же vmid=100 на pve1 и pve2 дают разный вердикт авторизации (никакого протекания прав).
  • При len(hosts) > 1 host обязателен для всех инструментов, кроме clusters_list (флот-обзор). Не указан → отказ (fail-closed на неоднозначность), чтобы случайно не задеть чужой кластер.
  • Глобальный allowlist при мульти-гипервизоре — запрещён (ошибка конфигурации): права обязаны быть per-host. Для одиночного гипервизора глобальный allowlist остаётся допустимым фолбэком.
  • Probe-паттерны несут host=<alias> первым — permission-правила могут различать кластеры:
    patterns:
      - match: "proxmox__vm_delete"
        pattern: "host=pve1 node=pve vmid=500"
        then: ask
    
  • Нет флот-агрегатов по VMID (clusters_list даёт карту алиасов, дальше — вызов с host), чтобы не терять принадлежность хоста.

Безопасность (§9.5)

Против чего Защита в модуле Внешний барьер
Случайное удаление/rollback/migrate confirm="true", read_only, per-host allow_vmids/allow_nodes, path-валидация require_approval в agent.yaml
Коллизия VMID между гипервизорами host обязателен при мульти; авторизация per-host; глобальный allowlist запрещён —
Инъекция в URL (node/vmid/snapname/storage/upid) guard-валидация (ValidateIdentifier/ValidateUPID) + url.PathEscape least-privilege токен в PVE
Изменение конфига вслепую DenyConfigKey (delete/revert/hotplug/spice/...) для update-инструментов —
Внешний доступ URL объявляет оператор (модель не задаёт хост), кастомный CA, таймаут —
Долгие/необратимые операции async UPID + task_status(wait) (bounded task_poll_max_sec) —
Запуск кода в госте не реализуется (зона ssh__run) —

Главный барьер — на стороне PVE: токен с минимальными правами (для чтения достаточно Sys.Audit, VM.Audit, Datastore.Audit; для мутаций — только нужное). Модуль держит guard как второй слой (стандарт двухслойности в §9.5).

Probe/approval: мутации регистрируются через registerPatternTool (эмитятся host=<alias> node=<node> vmid=<vmid>), оператор может задать patterns-правила по ресурсу; дефолт permission — ask, и всё деструктивное ещё и в require_approval. always не эмитим — каждый опасный вызов отдельный ask.

Подключение к агенту

// config.json ядра
"mcp": { "servers": {
  "proxmox": { "command": "./forge-tools/proxmox/forge-tools-proxmox", "args": [], "isolation": "pooled" }
}}
# agent.yaml
mcp_servers:
  - proxmox
tools:
  require_approval:
    - "proxmox__vm_start"          # и прочие lifecycle
    - "proxmox__vm_delete"
    - "proxmox__vm_config_update"
    - "proxmox__vm_add_disk"  ... "proxmox__vm_move_disk"
    - "proxmox__vm_add_network"  ... "proxmox__vm_remove_network"
    - "proxmox__container_*"
    - "proxmox__snapshot_rollback" # + snapshot_delete
    - "proxmox__backup_create"
    - "proxmox__task_stop"
  blocked: []
# при желании тонкие правила по ресурсу:
permission:
  default: ask
  patterns:
    - match: "proxmox__vm_delete"
      pattern: "node=pve vmid=500"
      then: ask

Сборка и ручной тест (T1)

make build                          # CGO_ENABLED=0, статический бинарник
./forge-tools-proxmox --health      # ok

T1 по stdio:

printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}\n' \
  | ./forge-tools-proxmox -config /abs/path/pve.json

tools/list → 54 инструмента (27 мутаций с probe-флагом); node_status в read_only → данные; vm_delete без confirm/вне allow_vmids → понятный отказ без обращения к API.

Безопасный тест на homelab (не трогая существующие ВМ)

  • Single-node pve (10.0.0.10, PVE 8.4.21). Существующие ВМ — 100…304.
  • Тестовая ВМ 500 — клон шаблона 106 (template.example.com, server: OVMF, scsi0 16G на local-nc, net0→vmbr0, agent:1), через proxmox__vm_clone (newid=500) ИЛИ вручную.
  • Dev-конфиг: "hosts":[{"alias":"pve","allow_nodes":["pve"],"allow_vmids":[500]}] → модуль физически не может задеть 100–304.
  • guest_ips тестируется на клоне 500 (нужен guest-agent, в шаблоне включён).

Roadmap (отдельные узкие модули, не сюда)

  • proxmox-backup — restore/delete/prune vzdump (необратимые, нужна выверенная семантика), расписания, retention.
  • proxmox-storage — Ceph/ZFS-pools, ISO/template upload, disk import.
  • proxmox-net — bridges/bonds/SDN-VXLAN, HA, firewall, ACL/users.
  • Node reboot/shutdown — сознательно НЕ включено (однонодовый blast-radius).

Тесты

go test ./... -count=1   # unit: политика/guard (без сети и без PVE)

Integration-кейсы (-run Integration) — против реального PVE, только на VMID 500.


Разработка

make lint     # go vet + gofmt -l (пусто = ок)
make test     # go test -race ./...
make build

Требования: Go 1.27+ и доступ к приватному Go-модулю forge-toolkit (export GOPRIVATE=git.totmin.ru).


Лицензия

Apache-2.0 — см. LICENSE.