VPN 守护进程需要特权网络操作,而普通应用打开出站套接字时完全不需要任何特权。大多数能力配置错误正是从这个缺口开始的:有人看到权限错误,就把整套权限都授出去,服务随后能跑起来,却没人能解释原因。下面的步骤从失败的操作出发,把它对应到某一项权限,然后再检查进程实际所处的上下文。以不受限制的 root 运行对某些部署是可行的答案,但它应当是决定,而不是默认。

从失败的操作开始

先读错误,再读教程。修改接口时的「操作不允许」、打开设备节点被拒绝、低端口绑定失败,是三个不同的问题,需要三种不同的授权。如果进程能启动,只在第一次改路由时失败,那也说明接口已经创建成功,这把答案范围缩小了很多。

停掉服务复现失败,然后如果实现允许,用同一用户在前台启动它。看得见的错误胜过靠猜的日志行,它也能让「能力问题」与「SELinux 或 AppArmor 拒绝」的区别一目了然。

把每项操作对应到一项权限

这张表只是起点,不能替代实现文档:

OperationUsual authorityWatch for
Create or configure tun/tapCAP_NET_ADMIN in the relevant namespaceCreating the device and opening it are separate steps
Add routes, rules, firewall entriesCAP_NET_ADMINSame capability, different namespace than you think
Open /dev/net/tunDevice permissions, not a capabilityGroup membership or a udev rule may be enough
Raw socketsCAP_NET_RAWNeeded by some user-space tunnel implementations
Bind a port below 1024CAP_NET_BIND_SERVICEIrrelevant for most modern tunnel ports

表里那两行正是人们最容易混淆的地方。打开 /dev/net/tun 是文件权限问题;要求内核配置你打开的东西,才是能力问题。授予 CAP_NET_ADMIN 并不能解决服务用户读不了的设备节点,修好节点也不会给你路由权限。在改动其中任何一项之前,两者都要检查。

内核 WireGuard 与用户态隧道程序的要求也不相同。内核模块路径通过 netlink 配置已有接口,而用户态实现可能还需要设备,某些传输方式下还需要原始套接字。

读懂上下文:服务、容器还是 shell

主机网络命名空间中的一项能力,影响范围可能远大于在刻意隔离的协议栈里执行同样的操作。容器运行时通常以收窄的边界集启动;给共享主机网络的容器加上 NET_ADMIN,比给拥有独立网络命名空间、且没有主机挂载的容器加上它,授权要大得多。

对 systemd 单元,要读实际生效的配置,而不是单元文件看起来写着的内容:systemctl show -p CapabilityBoundingSet -p AmbientCapabilities 报告的是有效值,而它可能由你忘记的 drop-in 设置。问的是有效值,永远不是设想值。ambient 能力很重要,因为非 root 服务没有它,就无法在 exec 之后保留能力。

以软件包或镜像支持的配置为基准。不要因为另一篇教程给别的工具列了原始套接字权限或特权模式就照搬;那套推理什么也迁移不过来,只会让你失去已经付出的隔离。拿不准时什么都别授,去读实现说明里与你需要的那一步操作对应的部分。

观察权限降下来之后会发生什么

许多守护进程以特权启动,配置好接口,然后切换到非特权用户并保留隧道。从那一刻起,进程就无法再加路由、重写防火墙规则或打开新的特权设备,直到某次重连恰好需要这些为止。之后的文件读取和钩子执行也可能因同样的原因失败。

要测整个生命周期,而不只是第一秒:启动、续期或重连,以及停止时的清理。第一次成功创建接口并不能证明之后每一项特权操作都还行得通,故障通常在生产环境里几小时后才出现。

能跨用户切换保持隧道打开的实现,通常配有持久化隧道和密钥的选项。在 systemd 一侧,等价的问题是单元的能力集能否挺过你配置的用户切换。

把授权和理由记录下来

写下确切的授权、它启用的操作,以及它修好的错误。部署说明里的一行——「单元网络命名空间内的 CAP_NET_ADMIN,用于重连后的路由变更」——就足以让下一个人不再扩大授权。

之后每次升级后都重读一遍有效集。厂商会加功能,镜像会改默认值,去年还多余的能力,可能没人注意就成了支撑项。

相关文章