把 RADIUS 接入 OpenVPN:插件还是脚本钩子?
旧的 RADIUS 插件绑定在它编译时所针对的服务器构建上。auth-user-pass-verify 钩子取代了它,但把信任移进了你的脚本。
在比当前构建更旧的讨论串里,插件仍被推荐;而在昨天编译的服务器上,它加载时就会失败。这不是用路径就能修好的配置错误。插件和脚本钩子在不同层解决同一个问题,二者切换会改变需要安装什么、什么以服务器权限运行,以及一次失败的登录究竟告诉你什么。
迷思:插件是受支持的路径
插件是加载进 OpenVPN 进程的共享对象,因此必须与该进程暴露的接口匹配。构建会变,打包会变,为旧接口编译的库会直接拒绝加载。服务器配置里没有任何东西能弥合这个差距,所以“直接装个 radiusplugin”的建议最终换来一台起不来的服务器。
脚本钩子则站在进程之外。OpenVPN 执行一个程序,把凭据交给它,再读回裁决。正是这种解耦让它在升级中存活,也正是这一点让它以插件从未有过的方式成为你的责任。
两种做法在构造上都不更安全。一种在守护进程里运行外来代码;另一种手握凭据,在旁边运行你的代码。正确的问题是:这两种风险里,你究竟能运营哪一种。
插件把什么绑在了一起
插件自带配置文件,包含 RADIUS 主机、共享密钥、重试次数和超时,服务器配置用一行引用它。凭据处理留在服务器进程内部,也就是说密码从未跨过管理员必须加固的那道进程边界。
代价是耦合。每次 OpenVPN 升级都可能带来不兼容的接口,每个重新打包的发行版都可能产出不再匹配的库。一旦发生,失败既响亮又立刻,而这正是这套设计唯一的仁慈。
如果这类插件还能在你的构建上加载,它可能是更小的运维面。确认它是针对哪个接口构建的,把它钉在你测过的构建上,并把任何升级都当作需要完整登录测试的变更,而不是例行包更新。
脚本钩子究竟信任什么
钩子通过 auth-user-pass-verify 和一种投递机制配置:环境变量或临时文件。用环境变量形式时,OpenVPN 要求 script-security 3,这是文档中允许向脚本暴露密码的级别。该级别是对默认值的刻意削弱,应被视为附带理由的授权。
脚本触碰的一切都继承那份信任。它必须归 root 所有,不可被组或其他用户写入,以绝对路径引用,并且没有任何不可信值能到达 shell 的地方。给每个变量加引号,拒绝意外字符,绝不用提供的用户名拼命令。一个每次登录尝试都以 root 运行的脚本,正因为这四条性质,值得审一次。
ls -l /usr/local/sbin/radius-auth.sh
stat -c '%U %a' /usr/local/sbin/radius-auth.sh
文件投递形式避开了环境继承问题,通过权限受限的临时文件把凭据交给脚本。如果你的客户端支持,这通常是更好的默认值,尽管失败时多一个要清理的对象。
退出码决定你会不会“失败即放行”
脚本的退出状态就是裁决:零接受,非零拒绝。出问题的模式是一个 shell 脚本,只在 RADIUS 回复包含特定接受令牌时接受,其余一律返回失败,因为这样把三件不同的事揉进了一个答案。密码错误、服务器不可达、回复畸形,全都变成同一种拒绝。
即使钩子只能返回接受或拒绝,也要在脚本里把这些情况分开。单独记录后端结果,对超时和传输错误告警,并在监控里明确:认证失败是因为服务挂了,不是因为用户打错字。
两个习惯让其余部分保持清醒。如果 RADIUS 是权威,它就应该是唯一校验密码的地方,因为影子表会给同一个问题造出两个答案,而第二个是没人盯着的攻击面。还要记住钩子在每次重试时都会被再次调用,所以慢脚本加上没耐心的客户端,会变成密码猜测的放大器——除非你在 RADIUS 一侧加上延迟或策略。
证书消失时你放弃了什么
这种形态的许多部署完全关掉客户端证书校验,只留下用户名和密码作为客户端的全部主张。随该设置消失的是基于证书的身份。通道仍然加密,但唯一命名客户端的东西是 RADIUS 的裁决。任何下游原本依赖证书主题的东西,现在都必须改依赖用户名。
保留控制通道保护,使未认证的对端无法探测或淹没握手;当按用户配置依赖通用名时,使用 username-as-common-name。托管网关往往用另一种方式解决:在与会 RADIUS 讲 EAP-TLS 的网关上终止证书认证,那种情况下这些服务器指令一条都不适用。要复制你自己平台文档里的模型,而不是邻家产品的。关键指令差异足够大,混用会得到一份能解析、然后谁都认证不了的配置。