OpenVPN push-peer-info:在不授予访问权限的情况下标记客户端
UV_ 字段随已认证的隧道传输,但该值仍然只是客户端的主张。把它接上,验证真正到达的内容,并把授权留在服务端。
部署标签是人们想到这个功能的常见原因:每条连接都应携带类似站点或角色的信息,而服务端应能读取它,而不必发明第二套凭据格式。社区版 OpenVPN 已经具备这一机制,而且它很小。真正让人吃苦头的是预期。在客户端配置里设置一个普通变量,在服务端上什么也不会发生;而一个确实到达的值,只证明客户端发送了它。
本文是一份面向可工作路径的操作手册,包括验证真正到达了什么,以及不留下半配置钩子的回滚。这里不需要新的证书、新的插件或协议扩展。
设置变量并启用路径
客户端配置里有两行起作用,而且两行都必需。没有第一行就没有可发送的内容;没有第二行客户端根本不会提供对端信息。顺序无关紧要,这一点值得知道,因为这两行常常落在生成配置的不同位置。重要的是,在客户端读取之前,无论什么重写配置,两行都要保留下来。
setenv UV_DEPLOY field-support
push-peer-info
UV_ 前缀不是装饰。普通 DEPLOY 变量只留在客户端进程里,永远不会被转发;而带前缀的形式才是协议为用户自定义字段保留的命名空间。保持已批准配置的其余部分不变,并在部署前确认你部署的客户端构建同时支持这两条指令。
一项实际检查应放在这里,而不是最后:确认该指令能到达客户端二进制文件,没有被某个包装层吞掉。会自行生成命令行的图形客户端,或会重新加载已保存配置的管理界面,可能丢弃它不认识的行。如果服务端什么都看不到,这就是第一个该检查的地方。
从服务端钩子读取该字段
在服务端,收到的对端信息会被校验并注入连接工作流的环境,这正是它无需你自己做任何协议工作就能从钩子读取的原因。服务端不需要解析控制通道,也不需要替你保存任何东西;值只是在连接建立期间存在。一个记录标签与已认证名称的最小 client-connect 脚本如下所示。
#!/bin/sh
# /etc/openvpn/server/hooks/label.sh
printf '%s %s\n' "$common_name" "${UV_DEPLOY:-unset}" \
>> /var/log/openvpn/deploy-labels.log
通过环境读取该变量,而不是解析日志,并且总是给它一个默认值。${UV_DEPLOY:-unset} 能让脚本对从不发送该字段的客户端也保持可用,这在分阶段部署中是常态。不要为了找名字就把整个环境倒进日志;里面远不止标签,还包括你并不打算留存的取值。
手动运行脚本时,它会针对所有内容报告 unset,因为手动 shell 不携带连接环境。这不是脚本的缺陷,也不是该字段缺失的证据。请通过真实连接测试,那是该变量唯一存在的上下文。
验证真正到达的内容
连接一个带标签的客户端和一个不带标签的客户端,然后读取文件。这一对就是测试:一行应携带该值,另一行应携带默认值,并且两者都应给出你所期望的同一个已认证身份。
tail -n 5 /var/log/openvpn/deploy-labels.log
# field-support deployed-client-07
# unset legacy-laptop-02
那些注释行是输出的形态,不是你服务端的转录;请读你自己的文件。如果第二个客户端也显示了值,说明该变量在环境中的其他地方被设置了,你读到的是过期设置,而不是对端字段。
也要检查该连接的服务端日志。对端信息可能出现在那里,这对调试很方便,也是让取值保持无趣的理由:不要出现人名、工单号,也不要出现任何你不希望日志读者看到的东西。
把标签当作主张,而不是身份
任何能编辑客户端配置的人都能改标签,而隧道经过认证并不会让标签具有权威性。传输保护的是途中的值,并不为这项主张背书。授权仍属于已认证身份和服务端持有的登记记录;在真正重要时,标签要与之比对。
这一区分决定了标签可以用在哪里。合适的用法是:把会话路由到正确的日志文件、在仪表盘里选择展示分组,或标记支持会话以便之后有人能找到。不合适的用法是:据此让设备进入子网、选择防火墙策略,或决定该会话能读哪个客户的数据。
在值到达路径、查询或 shell 命令之前先做校验。对部署标签来说,一份预期字符串的允许列表就足够了,而且不花什么成本。绝不要把对端提供的字符串插入 shell 命令行,并且把任何存储下来的观测限定在会话内,而不是绑定到 下一个客户端会复用的隧道地址。
case "${UV_DEPLOY:-}" in
field-support|field-qa|lab) : ;;
*) UV_DEPLOY=unknown ;;
esac
干净地回滚
移除该功能就是两次删除加一次验证。从客户端配置中删掉 setenv 和 push-peer-info 两行,然后在服务端移除或停用钩子及其日志目标。
grep -rn 'UV_DEPLOY\|push-peer-info' /etc/openvpn/
tail -n 3 /var/log/openvpn/deploy-labels.log
之后重新连接一个客户端,确认日志行不再出现。配置更改后仍在触发的钩子,通常是仍部署在测试设备上的第二份配置;现在找到它比审计时找到更省事。
最后留下的是一小块有边界的会话元数据,其来源有据可查:客户端主张它,服务端记录它,而访问控制不依赖它。这才是该功能有用的形态,而且不需要新的凭据就能做到。