Skip to main content
Glama
JohnGilligan2

ufw-mcp

ufw-mcp:面向 VoIP 服务器的最小权限 UFW 放行

提案。此处内容尚未应用于任何服务器。

计划主机:ufw-mcp.example.com(与 itglue-mcp、proxmox-mcp、network-mcp 一致)。与 network-mcp 分开的服务器,因为 network-mcp 的只读保证由 AST 强制实施,增加写入路径会破坏那个使其可以放心交给五名工程师的性质。与 tactical-rmm-mcp / tactical-rmm-audit-mcp 的拆分方式相同。

它自动化的内部命令

根据 Dan 的说法,并在 shell 历史中得到确认,每个地址两条规则:

sudo ufw allow from <ip> to any port 5060 proto tcp
sudo ufw allow from <ip> to any port 5060 proto udp

在运营商的地址段上批量完成,然后用 ufw status | grep 5060 | grep -c tcp / -c udp 验证。该工具与此形态一致:包装脚本只处理一个地址(因此特权代码保持很小),MCP 工具循环处理批量,并以同样的方式返回前后计数。

Related MCP server: OPNsense MCP

与手动输入命令相比,该工具增加了什么

当放行会静默地毫无作用时,它会拒绝执行。

UFW 采用首条匹配生效,而 ufw allow 是追加操作。在这些服务器上有 ~3,600 条规则,其中 ~2,150 条 DENY 条目占据顶部。因此,如果地址已被拒绝,放行规则会落在其下方,命令成功执行,规则出现在 ufw status 中,但没有任何数据包能通过。

这不是假设。2026-08-17 在只读状态下实际发现:

core1   [2145] DENY IN 198.51.100.36   # FraudWatch 2026-07-15 example-isp-NY /32
            [3371] 5060/tcp ALLOW IN 198.51.100.36     <- dead
            [3372] 5060/udp ALLOW IN 198.51.100.36     <- dead

core2   [2145] DENY IN 198.51.100.36   # FraudWatch 2026-07-15 example-isp-NY /32
            [2150] DENY IN 198.51.100.42
            [2914] 5060/tcp ALLOW IN 198.51.100.42     <- dead
            [2915] 5060/udp ALLOW IN 198.51.100.42     <- dead
            [3440] 5060/tcp ALLOW IN 198.51.100.36     <- dead

198.51.100.42 在两个核心之间也不一致:在 core1 上正常,在 core2 上失效。该客户会表现为间歇性故障。

包装脚本会检测到这种情况,并以退出码 4 退出,同时给出违规行,而不是报告成功。存在一个 override-deny 参数,用于插入到 DENY 规则之上,但它有意不作为默认值:取代欺诈封锁应当是一个决定,而不是副作用。

审计:谁添加或撤销了什么

三层,全部以来自已验证 JWT 的 Entra 身份为键,绝不使用调用者提供的参数。正是这一性质使审计轨迹可信:它经过签名校验,而非自我声明。network-mcp 已经在 server.py::_actor() 中实现了这一点,同样的代码会沿用过来。

  1. 在规则本身中。 comment "mcp by:approver@example.com <ts>"。审计信息就存在于该规则中,因此 ufw status 永远会标明每条规则是谁添加的,无需关联日志,任何在机器上但从未听说过 MCP 服务器的人都能看到。

  2. 在 VoIP 服务器上。 logger -t ufw-mcp 写入该机器的 syslog,因此记录存在于变更发生之处。

  3. 在 MCP 服务器上。 一个仅追加的审计文件,包含调用者、操作、目标、主机和结果。它必须位于命名卷上,否则 Portainer 重新部署会将其抹掉,考虑到 push-to-deploy 会重建容器,这是一个现实中的陷阱。

Entra 登录日志是第四个独立的记录,记录谁进行了身份验证。

撤销操作以相同方式记录,且 ufw-mcp-revoke 只删除带有 mcp by: 标签的规则,因此它实际上无法删除 APIBAN 或 FraudWatch 条目。

机器上的最小权限

该账户不是超级用户。它是一个普通用户,只被允许调用一个特权操作:

sudo useradd --create-home --shell /bin/bash --comment 'ufw allow automation' nocufw

没有 -G sudo,没有附加组。然后执行 visudo -f /etc/sudoers.d/ufw-mcp:

Cmnd_Alias UFW_MCP = /usr/local/sbin/ufw-mcp-allow, \
                     /usr/local/sbin/ufw-mcp-revoke, \
                     /usr/local/sbin/ufw-mcp-list
nocufw ALL=(root) NOPASSWD: UFW_MCP
Defaults!UFW_MCP !requiretty

绝不要使用 NOPASSWD: /usr/sbin/ufw *。ufw 还有 reset、disable 和 delete,而且 sudoers 的参数通配符通常可以被转义绕过。安全边界是脚本本身;sudoers 只决定允许哪个二进制文件。

把这个变成 root 账户的陷阱: 包装脚本必须归 root 所有,nocufw 不可写,也不能位于 nocufw 可写的目录中。使用 install -o root -g root -m 0755 安装。在两台服务器上用 sudo -l -U nocufw 验证整个授权。

与 relay 账户不同,它确实需要一个真正的 shell,因为 MCP 工具通过 SSH 运行 sudo ufw-mcp-allow ...,而 OpenSSH 通过用户的 shell 执行远程命令。shell 本身不是特权;sudoers 那一行才是。

apiban2ufw.sh:2026-08-17 读取,它改变了一件事

每 4 分钟 从 /etc/cron.d/ns_apiban 以 root 身份运行一次。

好消息:它不会清空或重写任何东西。 它将数据源与 ufw status | grep APIBAN | grep DENY 进行 diff,因此它只考虑带有自己注释的规则。它添加增量,并移除已从数据源中消失的条目。我们的 ALLOW 规则对它不可见,也不会被覆盖。关于排除列表的担忧是没有根据的,账户/sudoers 的设计保持不变。

关键之处: 它添加时使用

/usr/sbin/ufw insert 1 deny from $line to any comment "From APIBAN $dateVar"

insert 1,而不是追加。 因此 APIBAN 每四分钟就会把新的拒绝规则放在最顶部。这意味着“在位置 1 插入我们的放行规则”并不是一个持久的位置:它高于我们写入时已存在的所有规则,但低于 APIBAN 之后添加的任何规则。

后果,包装脚本现在会明确说明这一点:

  • 拒绝来自 APIBAN -> 在其上方插入放行规则可以生效,直到 apiban.org 再次列出该地址,然后会静默失效。持久的修复在数据源上游,而不是在 UFW 中。

  • 拒绝是 手动 / FraudWatch -> 在其上方插入是持久的,因为没有东西会重新添加它。

example-isp-NY 的两条拒绝都是 FraudWatch,而不是 APIBAN,因此它们属于持久的情况。

一个值得了解的潜在 bug,今天不归我们修:remove_ips_ufw 按规则规格删除(ufw delete deny from $line/32),而不是按注释删除。如果某个地址同时出现在 APIBAN 数据源和手动拒绝中,一次数据源移除可能会误删手动规则。

构建前仍待解决的问题

  1. 决定移除欺诈封锁是否在范围内。 添加有范围的放行规则和删除 FraudWatch DENY 是不同级别的权限。建议该工具只附带 allow + revoke-own-rules,而解除被欺诈封锁的地址仍保持手动操作,或获得一个更严格的独立用户组。

  2. 手动修复上述五条失效规则,独立于本项目。

撤销功能已移除,2026-08-18

这是 John 的决定,与设计会议上 Alice 的担忧一致:一级用户可能移除 SIP 中继或 Inteliquent 地址。在两个层面都进行了移除,因为只移除工具会让特权仍然存在:

  • 没有 ufw_revoke_ip 工具,且 ufw.py 中没有 REVOKE 常量,因此执行器实际上无法运行该路径

  • 两台交换机上的 sudoers 授权被限制为 ufw-mcp-allow 和 ufw-mcp-list

  • /usr/local/sbin/ufw-mcp-revoke 已从两台交换机上删除

变更后已验证:sudo -l -U nocufw 列出两个路径,以 nocufw 身份调用撤销包装脚本会被拒绝,allow/list 仍然正常。

如果该工具或常量重新出现,tests/test_no_revoke.py 会失败,因此重新启用是一个需要深思熟虑的行为。正确做法是恢复全部四项:常量、工具、sudoers 条目、包装脚本文件。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enforces fine-grained, context-aware access control on MCP tool calls, with a tamper-evident, replayable audit log that records denials and verifies every decision.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables remote agents to securely read firewall and NAT rules, inspect routing tables and logs, and execute safety-gated mutation plans through a bearer-authenticated MCP endpoint.
    366 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables policy-governed MCP interactions with deterministic authorization, tenant isolation, minimized PII exposure, and human approval gates for sensitive mutations, while producing structured audit events.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to govern downstream tool servers by enforcing default-deny authority and attestation on every tool call, with live monitoring, approval, and mid-session revocation.
    3
    Apache 2.0