mcpField
R
rust-proxmoxmcp

rust-proxmoxmcp

本地服务
mechub.org云与基础设施13 天前更新

关于 rust-proxmoxmcp

rust-proxmoxmcp 是用于管理多个 Proxmox VE 集群的 Rust MCP 服务,提供受权限范围约束的清单与运维工具,并通过计划、审批、应用流程管理变更,包含保护检查和审计日志。

主要功能

  1. 01多集群清单与访问范围
  2. 02查询虚拟机、节点、存储和防火墙
  3. 03计划、审批并应用变更集
  4. 04受保护来宾检查与限时豁免
  5. 05结构化审计日志及可选 PII 脱敏
  6. 06集群 CA 固定与处理中任务恢复

适用场景

  1. 01检查各集群的虚拟机和容器状态
  2. 02查看存储、备份和防火墙设置
  3. 03对 Proxmox 来宾应用已审批变更
  4. 04审计受保护基础设施上的操作

MCP 相关

MCP 服务为 Proxmox VE 集群提供受范围限制的查询和变更控制工具。破坏性变更遵循计划、审批、应用流程,审批绑定预览内容,并要求使用人类审批者令牌。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"mechubsec-rustproxmoxmcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/mechubsec/rustproxmoxmcp:0.12.0"
]
}
}
}
启动命令docker run -i --rm ghcr.io/mechubsec/rustproxmoxmcp:0.12.0

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
ocighcr.io/mechubsec/rustproxmoxmcp:0.12.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

mechub 标志

rustproxmoxmcp

一个 Rust MCP 服务,可管理多个 Proxmox VE 集群
mechub 项目 — 主权网络安全自动化

非官方 / 社区项目。 这是一个独立的社区项目,不声称与 Proxmox Server Solutions GmbH 有关联,也未获其认可。产品名称和商标仅用于标识本软件可互操作的系统。


状态:0.11.0 — 工具集已完整,但仍有一处缺口

51 个可调用工具:32 个只读工具、18 个 low 工具,以及作为唯一 destructive 入口的 apply_proxmox_change_set。另外七个名称 —— delete_vm、delete_container、delete_snapshot、delete_backup、delete_iso、restore_backup、rollback_snapshot —— 是授权范围,而非工具:令牌按名称授予这些权限,并通过 plan_proxmox_destroy 调用它们。因此,KNOWN_TOOLS 包含 58 个条目。

仍然缺少的内容
  • 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 为必填项,且不能为空。 加载时会拒绝缺少 ops 字段或列表为空的版本 1 豁免文件条目——服务器不会因不覆盖任何操作的豁免而启动。这是相对于 MEC-447 之前(完全没有 ops 字段的)豁免文件的一项不兼容变更,并且有意采用默认拒绝策略:升级前,请为每个条目重新添加它应覆盖的确切操作。

  • principal 为可选项。 设置后,豁免仅匹配该令牌名称;未设置时,则匹配任何调用方。它指定的是计划该操作的令牌,而不是批准或应用该操作的令牌——approve_proxmox_change_set 和 get_proxmox_change_set 会根据变更集上记录的规划者来评估豁免,因为双人规则要求批准者与规划者是不同的主体。因此,绑定了 principal 的豁免会拒绝由规划者以外的调用方执行应用,这是预期的默认拒绝结果,并非错误。

  • ops 中的 op 字符串必须根据豁免所覆盖的路径指定:

    • 通过 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):工具自身的名称。
    • 通过 plan_ha_rule_change 计划的 HA 规则变更: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,而 mecmcp 会拒绝来自 agent 或未归属身份的(stdio)调用方的任何批准——只有 actor_type: human 才能批准。请使用 rust-proxmoxmcp token add ... --actor-type human 创建批准者令牌。actor_type 是操作者在创建令牌时声明的属性,而不是服务器验证的内容;如果将标记为 human 的令牌交给 LLM Agent,就会绕过此关卡。

已实现的功能
  • 多集群清单: 一个服务,多个集群。每个集群都有自己的 API 令牌和保护策略。
  • 两阶段授权:
    1. 阶段 1(调用目录之前):验证 Bearer 令牌,检查工具和集群范围。
    2. 阶段 2(仅限以 Guest 为目标的工具):解析 Guest,评估授权(VMID 范围、标签、资源池选择器),并采用失败即拒绝的保护策略。
  • 保护规则取并集: 如果 Guest 出现在 protected_vmids 中,或带有 protected_tags 中的标签,就会受到保护。除非豁免或实验室模式提供覆盖,否则所有具有破坏性和会中断服务的工具都会拒绝对受保护 Guest 的操作。(读取工具可以正常查看受保护的 Guest。)
  • 以 Guest 为目标的工具绝不接受调用方提供的节点。 每次调用时都会解析节点,并在应用时再次解析,因为 Guest 可能会迁移。节点、存储和任务范围的工具(get_node_status、get_node_updates、get_storage、list_backups、list_isos、list_templates、list_tasks、get_task_status、download_iso、get_node_firewall_rules、get_node_firewall_options)确实会接受 node,因为它们操作的对象就是节点——没有 Guest 可供解析节点。需要注意的例外是 create_vm/create_container:它们以 Guest 为目标,但 Guest 尚不存在,因此调用方必须提供节点;这是以 Guest 为目标的调用中唯一可能到达错误主机的情况。stop_task 则有意不在此列:它会从 UPID 中读取节点。
  • 由目录驱动的分发: 每个读取工具的 HTTP 方法、路径模板、查询标志和类型筛选器都只在 catalog.rs 中声明一次。
  • 运行中恢复: 如果变更集处于 Applying 状态且带有任务句柄,启动时会重新探测,使被重启中断的应用操作能够得到确认,而不是永远处于未解析状态。
  • SIGHUP 重载: systemctl reload 会就地重载 clusters.json,不会丢弃正在进行的调用。重载失败时会记录日志并保留之前的快照。
  • 按集群固定 CA: 每个集群都可以指定 ca_pem_path。任何层级都不会跳过证书验证,因此使用私有 CA 的集群必须安装其 CA,并且必须通过证书涵盖的名称访问。
  • 审计日志: 采用 JSON 结构化日志,可选择对 PII 进行脱敏(使用 HMAC 密钥或直接删除)。每次工具调用都会记录集群、Guest、层级和保护状态。
32 个读取工具
工具范围描述
get_cluster_status集群仲裁和节点成员信息
get_nodes集群所有节点及其状态和资源总量
get_node_status节点单个节点的详细状态
get_node_updates节点待安装的 apt 软件包更新(需要可选权限 Sys.Modify,见下文)
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节点;可选参数 typefilter、vmid、since、until、limit(1-500)、errors、statusfilter单个节点上的近期任务(不分页——Proxmox 使用自己的服务端窗口,默认显示最近 50 条,除非设置了 limit)
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集群集群范围的 IPSet
get_firewall_ipset_entries集群 + IPSet单个集群范围 IPSet 中的 CIDR 条目
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来宾单个来宾上定义的 IPSet
get_guest_firewall_ipset_entries来宾 + IPSet单个来宾的单个 IPSet 中的 CIDR 条目

这三个特定类型的读取工具会按名称拒绝另一种来宾类型,而不是访问一个不存在的端点。

防火墙读取操作对应 Proxmox 本身提供的范围:节点级防火墙配置包含规则和选项,但不包含别名、IPSet 或安全组——这些仅存在于集群和来宾范围。防火墙写入操作使用 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},而不是纯数组,因此调用方可以判断列表较短,还是需要继续获取下一页。超过 700 的 limit 会被拒绝,不会静默截断。分页并非快照:每次调用都会重新获取完整的上游列表,并在切片前按 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 可以更改令牌的设备、工具、虚拟机和操作作用域,而无需重新签发密钥,因此无需重新配置客户端:

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 令牌(除非在 loopback 上启用了 --allow-no-auth)
  • 不在令牌 tools 列表中的工具
  • 不在令牌 devices 列表中的集群
  • 通配符范围试图访问的任何 WRITE_TOOLS 中的工具。tools: ["*"] 会刻意排除该注册表,因此通配符令牌无法访问任何更改状态的工具:每个工具都必须明确列出。

没有 grant 键的令牌无法用于面向来宾的工具。此规则默认拒绝:未声明来宾选择器的令牌不得成为通配符。

第 2 阶段:来宾解析与保护

面向来宾的工具会将 VMID 解析为 GuestFacts 记录(名称、节点、类型、标签、资源池),并评估令牌的授权规则:

{
  "guests": ["vmid:600-699", "tag:disposable", "pool:test-vms"],
  "actions": ["read"]
}

满足任意选择器条件的来宾即属于授权范围。然后,服务会检查保护规则并集:

  • 如果来宾出现在集群的 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/<cluster>.token。这是更安全的选项——文件会通过与 clusters.json 和 tokens.json 相同的强化加载器读取(0600、普通文件、归服务用户所有、O_NOFOLLOW),且凭据不会进入进程环境,因此不会出现在崩溃转储或 /proc/<pid>/environ 中。
  • token_secret_env:指定一个环境变量。可通过 systemd 单元中的 EnvironmentFile=-/etc/proxmoxmcp/secrets.env 使用(- 前缀表示文件缺失不会导致错误)。环境变量方式的安全性较低,因为凭据可从进程环境中读取。

两种方式都会通过 mecmcp-secret 加载到 OutboundSecret 中,该对象会在销毁时清零,并且既不实现 Debug,也不实现 Serialize。

Proxmox 端令牌:遵循最小权限原则,而非使用 root@pam

token_id 指定一个 Proxmox API 令牌,而该令牌在 Proxmox 端的 权限构成了本服务无法控制的第二道授权边界。 第 1 阶段和第 2 阶段(如上所述)会限制 MCP 调用方能执行的操作;但它们并不限制底层 Proxmox 凭据在请求到达集群后可以执行什么操作。若向本服务提供 root@pam!... 令牌,就等于取消了这第二道边界:root@pam 是 Proxmox 硬编码的超级用户,会完全绕过 ACL 检查——请参阅 Proxmox VE 自带的用户管理文档 ——因此,任何角色、路径范围限制,以及 clusters.json 中的任何配置都无法约束它。如果本服务存在错误、令牌被盗,或 tokens.json 中的 grant 范围过宽,请求就会默认放行并获得对整个集群的完全控制,而不是默认拒绝并限制在有限角色内。

请改为在 pve 域中创建一个专用的非 root 用户,并为其配置自定义角色,只授予本服务工具实际使用的权限:

# 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(应用时的操作);使用 start: true 时,Proxmox 不会检查 VM.PowerMgmt,因此此服务也要求 start_vm/start_container 范围和 low 层级
VM.Backupcreate_backup、restore_backup(应用时的操作)
VM.Cloneclone_vm
VM.Config.Diskresize_disk
VM.Config.CPU、VM.Config.Memoryupdate_container_resources(分别对应核心数与内存/交换空间)
VM.Config.Network客户机防火墙应用(对单个客户机调用 apply_firewall_change)。Proxmox 会在 /vms/{vmid} 上检查此权限
VM.Allocatecreate_vm、create_container、delete_vm/delete_container(应用时的 destroy_guest 操作),以及覆盖现有 VMID 时的 restore_backup
Datastore.AllocateSpacecreate_vm/create_container(磁盘分配)、create_backup、delete_backup
Datastore.AllocateTemplatedownload_iso(目标存储)
Sys.Modifyget_node_updates,授予范围为 /nodes/{node}。可选附加项,不包含在基本角色中——见下文
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 工具需要此权限,而 Proxmox 的 API2/Storage/Content.pm 删除处理程序会在存储上检查此权限,而不是在单个 ISO 卷上检查—— Proxmox 没有任何权限能在不同时授予“修改或移除此存储的定义”的情况下,授予“删除此 ISO”权限。在 / 上授予此权限(如基本角色所做)会使令牌成为集群中所有存储的管理员:它可以在整个集群中删除存储定义、重新指向存储,或 添加新的 NFS/CIFS/PBS 挂载,之后所有节点都会连接到该挂载。如果此 部署需要 delete_iso,请将授权范围限定为存放 ISO 的单个存储,而不是整个集群:

# 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 也是 get_node_updates 所需的权限:Proxmox 要求在 /nodes/{node} 上拥有 Sys.Modify,才能访问 GET /nodes/{node}/apt/update。基础角色中不包含此权限,没有它时,get_node_updates 会返回 Proxmox 的 403。如果此令牌必须查看待处理的更新,请另行授予一个作用域限定在该节点上的角色——但请注意,这仍会让该令牌成为该节点的节点管理员(可配置网络、初始化和擦除磁盘、使用 apt 等),而非只读查看权限:

# Optional: only if get_node_updates must work. Scoped to one node, not `/`.
pveum role add ProxmoxMcpNodeUpdates -privs "Sys.Modify"
pveum acl modify /nodes/<node> --users mcp-automation@pve --roles ProxmoxMcpNodeUpdates

上方角色中有意不包含 / 上的 Sys.Modify。应用集群和节点防火墙规则需要此权限(集群对象需要 /,节点防火墙需要 /nodes/{node});当 API2/Tasks.pm 正在停止调用者不拥有的任务时,stop_task 也需要此权限。此服务自身的任务始终属于它自己的令牌,因此停止这些任务无需额外权限。规划并批准防火墙变更只需读取权限(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 调用如果附加网络设备,很可能还需要 PVE 8 及更高版本中桥接器上的 SDN.Use。此角色列表尚未在由 SDN 管理的桥接器上测试;如果你的集群使用这类桥接器,请先针对临时访客测试一次规划和应用流程。

plan_proxmox_destroy、approve_proxmox_change_set 和 get_proxmox_change_set 除了访客解析读取操作(需要上文已列出的 VM.Audit)外,不会自行调用 Proxmox API——批准操作是在此服务自身的变更集存储中进行的本地记账。规划操作所需的 Proxmox 权限取决于上方哪一行列出了该操作的 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 <name> 创建令牌。明文令牌只会显示一次,之后无法找回。对于任何将批准变更集的令牌,请传入 --actor-type human——请参阅变更控制。

**重要提示:**没有 grant 键的令牌无法用于面向访客的工具。要授予对所有访客的读取权限:

"grant": {
  "guests": ["*"],
  "actions": ["read"]
}

CLI 标志

rust-proxmoxmcp 继承 mecmcp_runtime::cli::Cli 中的所有标志(传输、绑定、TLS、允许的主机/来源、审计),并且只新增一个:

  • --clusters-file <path> — 集群清单(默认值:/etc/proxmoxmcp/clusters.json)

对于可流式 HTTP,必须指定 --tokens-file 或 --allow-no-auth。后者仅允许通过环回接口发送未经身份验证的读取请求;写入工具仍会被拒绝。

运行 rust-proxmoxmcp --help 查看完整列表。

安装

从头构建 rust-proxmoxmcp LXC 的分步说明,请参阅 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 用于构建 Debian 13 LXC tarball。packaging/lxc/install.sh 是该 tarball 的 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
传输方式基于 SSH 的 NETCONFHTTPS XML-APIHTTPS RESTHTTPS RESTHTTPS RESTHTTPS REST
状态正式发布,v0.25.0正式发布,v0.14.0基础功能已构建,通过只读实时租户验收已投入生产预发布(v0.1.0-lab)正式发布,v0.11.0

这六个服务端都使用 mecmcp——支撑 mechub 各厂商 MCP 服务端的共享 Rust crate 系列。

将审计记录转发到事件存储

审计记录不会留在此主机上。此服务端遵循该系列的标准——审计转发标准。

如果一条审计记录只存在于生成它的机器上,那就不能算作审计追踪:它只是某台机器上的日志文件,而该机器的运维人员正是记录所涉及的一方。

发射(现已生效)
--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 会重命名文件并向进程发送信号;此后每次写入都会落入同一路径下的新 inode。重命名前写入的内容不会被截断,重命名后写入的内容也不会丢失——copytruncate 会复制文件,然后就地截断文件,这会丢弃这两个步骤之间写入的内容。

传输(已规定,尚未实现)

根据 SSDF 的合并证据契约,记录会直接写入 SSDF 的 ssdf.audit,作为哈希链式行,以便检测行是否被删除或编辑。相关事项跟踪于 mecmcp#292。

我们设计过一种成本更低的 syslog 路径,但最终予以否决:它虽然可行,但记录没有链接成链,而这里的其他每个环节从设计上都具备防篡改性——计划摘要绑定审批,审批会标明唯一主体,并且 token_verified_fields 会将有凭据背书的来源信息与声明的信息区分开来。在最后一跳不使用链式记录,会恰恰在审计员最需要保证的地方丢掉这份保证。相关理由已记录在标准中。

阅读结果

token_verified_fields 列出了由令牌背书的来源字段。同一组中的其他字段——client_name、model_id、session_id——由客户端声明,未经任何方式验证。不要将它们视为同等可信。

request_id 用于关联传输事件、处理程序事件以及(在 Junos 上)设备提交注释。

许可证

运维与安全

出站流量过滤

打包的单元文件声明了 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 的结果都意味着单元指令未经证实,应将控制措施转移到更外层——转移到实际能看到此工作负载数据包的层。NOT ENFORCED 和 NO POLICY 表示这些指令显然没有发挥作用;UNKNOWN 则表示没有进行测量,它们很可能仍在生效。不要把后一种情况当成前一种。

策略不会随运行时而改变(不过,该单元允许 RFC 1918 地址访问 Proxmox API 端点):

  1. 拒绝 169.254.0.0/16 和 fd00:ec2::254——云元数据;被攻陷的 HTTP 客户端可通过此路径窃取凭据
  2. 拒绝链路本地地址(fe80::/10)——任何受支持的目标都不会用到
  3. 拒绝本地子网,DNS 解析器除外——阻止横向移动,同时确保名称解析正常工作(目前此服务器的单元中尚未声明;如有需要,可通过 drop-in 添加)

具体机制取决于运行环境。请根据平台自身的文档进行配置,而不要照搬此处的配方——以下列出的是层级,而非操作说明:

运行时能看到此工作负载数据包的层
Proxmox LXC / VM每个来宾的接口防火墙
libvirt / KVM来宾接口上的 nwfilter
Kubernetes在支持该功能的 CNI 上配置 NetworkPolicy 出站规则
云实例来宾内部的数据包过滤器(同时覆盖两个元数据地址),以及用于其他所有流量的安全组
裸机、systemd 正常工作的 VM单元指令;本节不适用

无论选择哪种方案,都值得检查以下两个属性,因为这两种情况都很常见,都会导致控制措施看似存在、实际却未生效:

  • 有些层接受出站策略,却不执行该策略。 常见情况包括容器网络连接以及某些 CNI 实现。
  • 云元数据流量通常绕过云防火墙。 在 EC2 上,IMDS 流量在安全组和 NACL 层之下处理,因此那里的出站规则无法阻止此类流量。IPv6 端点也适用——fd00:ec2::254 属于 ULA,而非链路本地地址,因此很容易被误认为是“防火墙能够看到的普通路由流量”,但事实并非如此。必须在来宾内部实施控制,或彻底禁用 IMDS。请查阅云服务提供商当前有关元数据加固的指南;指南会有变化,配置错误也不会产生明显提示。

无论选择哪种方案,未经工作负载内部实际验证的规则都只是一个假设。请验证它,并在重启后再次验证——除非你已将内核防火墙规则设为持久化,否则它们不会持久保留。

基于 MIT 许可。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。