在不给 OpenVPN root 的前提下授予 CAP_NET_ADMIN
以非特权用户启动的单元仍然需要正确的能力集。请按顺序检查单元、活动进程和 exec 边界。
服务启动了,日志却抱怨打不开 tun 设备,路由始终不出现。在你的 shell 里运行 capsh --print,cap_net_admin 就在那里,这让故障看起来不可能。shell 与单元之间的差别是权限过渡,而不是内核是否愿意授予该能力。按顺序做完下面四项检查,并在第一项能解释差异的地方停下。
把缺失的能力和隐藏的设备区分开
从日志行看,两种故障长得一样。没有 CAP_NET_ADMIN 的进程无法创建或配置隧道接口,而沙箱隐藏了 /dev/net/tun 的进程根本打不开它。增加能力并不能修复后一种情况。
在改动任何东西之前,先检查设备是否存在于服务的命名空间内。PrivateDevices=yes 是常见的加固行,它会把 tun 节点整个移除;而限制性的 ProtectSystem 可能让配置路径变成只读,读起来像权限错误。两者都会产生提到隧道的日志行,所以能力常被冤枉。先确认节点存在且可打开,再往下走。
弄清楚你在看哪一套能力集
Linux 维护 permitted、effective、inheritable、bounding 和 ambient 五套能力集,而能跨越非 root 用户 exec 的正是 ambient 集。二进制文件上的文件能力可以在 exec 时移交 permitted 与 effective 权限,但前提是链上没有任何环节阻断这次移交。
这正是 sudo 误导人的地方。通过 sudo -u 执行的命令会开启一次新的权限过渡,ambient 集随之消失,于是同样一项在 root 下成功的检查在那里失败。服务管理器又是一次过渡,除非单元明确写出,它不会授予任何 ambient 能力。
在单元里授权,而不是在包装脚本里
对一个需要创建设备并安装路由的隧道来说,最小服务段落长这样。
[Unit]
Description=OpenVPN client, office profile
After=network-online.target
[Service]
Type=notify
User=openvpn
Group=openvpn
NoNewPrivileges=yes
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
ExecStart=/usr/sbin/openvpn --config /etc/openvpn/office.confAmbientCapabilities 会在进程启动前把权限放进 ambient 集,CapabilityBoundingSet 则让进程无法持有该清单之外的任何东西。NoNewPrivileges=yes 阻止通过 setuid 或文件能力获取权限,而单元显式授予的能力依然生效。只有当服务必须绑定低位端口时才加 CAP_NET_BIND_SERVICE,也绝不要因为某个论坛帖这么做就加 CAP_SYS_ADMIN。
如果单元用 sudo 包住了二进制,就把包装去掉。systemd 已经会应用 User=,而那层多余的东西正是 ambient 集为空最可能的原因。
对照活动进程验证,而不是对照单元文件
先读管理器实际应用了什么,再读它启动的进程。
unit=openvpn-client@office
systemctl show -p User -p AmbientCapabilities "$unit"
systemctl show -p MainPID --value "$unit" | xargs -r getpcaps
grep -E '^Cap(Eff|Amb)' /proc/$(systemctl show -p MainPID --value "$unit")/status如果单元列出了该能力,而 CapEff 里仍然没有,那就是某个 drop-in 覆盖了设置,或者某个 ExecStartPre 步骤改变了环境。在认定主文件权威之前,先去 drop-in 目录里搜这个单元名,因为更晚读到的文件会悄悄胜出。如果单元和进程都显示该能力、隧道却仍然失败,就回到设备与路径检查,而不是放宽边界集。放宽会掩盖真正的原因,并让服务比原来拥有更多权限。
更倾向于在启动后主动降权
OpenVPN 可以带着权限启动,然后通过自身的 user 与 group 选项降到一个无特权账户,而 persist-key 与 persist-tun 让隧道在那之后仍然可用。这种安排缩小了进程持有权限的时间窗口,往往比让整个守护进程以无特权身份、靠常驻能力启动更合适。
无论选哪种模式,都要重启单元并在重启后重复这些检查,因为能从交互式 root shell 工作、却在开机时失败的服务,通常是能力、设备或文件权限上的差异。把单元设置记在进程能力输出的旁边,好让下一位审阅者看出是软件包更新还是 drop-in 移动了这条边界。