关于 rust-proxmoxmcp
rust-proxmoxmcp 是用于管理多个 Proxmox VE 集群的 Rust MCP 服务,提供受权限范围约束的清单与运维工具,并通过计划、审批、应用流程管理变更,包含保护检查和审计日志。
主要功能
- 01多集群清单与访问范围
- 02查询虚拟机、节点、存储和防火墙
- 03计划、审批并应用变更集
- 04受保护来宾检查与限时豁免
- 05结构化审计日志及可选 PII 脱敏
- 06集群 CA 固定与处理中任务恢复
适用场景
- 01检查各集群的虚拟机和容器状态
- 02查看存储、备份和防火墙设置
- 03对 Proxmox 来宾应用已审批变更
- 04审计受保护基础设施上的操作
MCP 相关
MCP 服务为 Proxmox VE 集群提供受范围限制的查询和变更控制工具。破坏性变更遵循计划、审批、应用流程,审批绑定预览内容,并要求使用人类审批者令牌。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm ghcr.io/mechubsec/rustproxmoxmcp:0.12.0本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/mechubsec/rustproxmoxmcp:0.12.0stdio工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
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(调用目录之前):验证 Bearer 令牌,检查工具和集群范围。
- 阶段 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.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(应用时的操作);使用 start: true 时,Proxmox 不会检查 VM.PowerMgmt,因此此服务也要求 start_vm/start_container 范围和 low 层级 |
VM.Backup | create_backup、restore_backup(应用时的操作) |
VM.Clone | clone_vm |
VM.Config.Disk | resize_disk |
VM.Config.CPU、VM.Config.Memory | update_container_resources(分别对应核心数与内存/交换空间) |
VM.Config.Network | 客户机防火墙应用(对单个客户机调用 apply_firewall_change)。Proxmox 会在 /vms/{vmid} 上检查此权限 |
VM.Allocate | create_vm、create_container、delete_vm/delete_container(应用时的 destroy_guest 操作),以及覆盖现有 VMID 时的 restore_backup |
Datastore.AllocateSpace | create_vm/create_container(磁盘分配)、create_backup、delete_backup |
Datastore.AllocateTemplate | download_iso(目标存储) |
Sys.Modify | get_node_updates,授予范围为 /nodes/{node}。可选附加项,不包含在基本角色中——见下文 |
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
工具需要此权限,而 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 服务端。这些机制不会编译进发布版二进制文件。
同系列服务端
| 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 |
| 传输方式 | 基于 SSH 的 NETCONF | HTTPS XML-API | HTTPS REST | HTTPS REST | HTTPS REST | HTTPS 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 端点):
- 拒绝
169.254.0.0/16和fd00:ec2::254——云元数据;被攻陷的 HTTP 客户端可通过此路径窃取凭据 - 拒绝链路本地地址(
fe80::/10)——任何受支持的目标都不会用到 - 拒绝本地子网,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 许可。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。