OpenVPN 如何解析证书与密钥:内联 PEM 还是文件路径
同一条指令携带的或是文件路径,或是嵌入的 PEM 对象。在移动证书之前,先弄清楚已加载的配置用的是哪一种。
提示说 OpenVPN 无法加载内联证书,而你记得的文件名却给出相反的说法。解析器实际收到什么,比文件系统里存在什么更重要,因为 cert 指令接受两种全然不同的值。一种是它打开的文件路径;另一种是随配置一起送进来的 PEM 文本。在弄清运行中的客户端拿到的是哪一种之前,任何修复都只是猜测。
两种存储形态,同一条指令
cert、key 和 ca 三条指令都可以接受路径或内联块。当值是路径时,OpenVPN 打开该文件并从磁盘读取对象。当配置中含有由 cert 标签界定的块时,同一条指令改为使用这两个标签之间的内容,不再涉及单独的磁盘文件。
两种形态最终产生同一种对象,因此一个能正常工作的部署可以在两者之间切换而无需其他改动。携带内嵌客户端证书和私钥的配置文件是自足的,把它复制到另一台机器上时不必再复制证书目录。
反过来则不然。把 client.crt 放在一个自带数据的配置文件旁边不会有任何变化,因为配置里没有任何东西引用那个文件。这就是明明存在的文件看起来被忽略的最常见原因。
内联块如何变成对象
内联内容会从配置中抽出,作为文本交给 PEM 解析器。该解析器期待常规边界:一行 BEGIN、base64 载荷,以及与之匹配的一行 END。如果这个块因复制失败而被截断、被下载页面包进 HTML,或在把短横线规范化过的编辑器里打开,解析器就会在一段看起来还在的内容上失败。
<cert>
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</cert>
错误措辞之所以误导人,原因也在这里。内联一词描述的是加载器去哪里查找,而不是它在那里找到了什么。配置里点名的文件与内嵌其中的对象,在底层字节不可用时可能产生完全相同的报错。
要确认的是已加载的配置实际使用哪种形态,而不是编辑器碰巧打开了哪个文件:
grep -nE '^(ca|cert|key) |^<(ca|cert|key)>' client.ovpn
路径引用是如何解析的
路径值相对于客户端的工作目录打开,而不是相对于配置文件所在的目录。在 systemd 下,该工作目录由 unit 决定;而从你的 shell 交互运行时继承的是完全不同的另一个。这就是同一个配置文件手动测试能用、作为服务却失败的原因。
相对路径也带着同样的歧义。看到裸露的文件名时,应当先问生产环境中进程的当前目录是什么,而不是假设它与你终端里的一致。
对于单独存放的证书,在向上排查之前,先用离线解析确认对象本身:
openssl x509 -in client.crt -noout -subject -issuer -dates
openssl pkey -in client.key -noout -check
这些命令证明文件可读且结构有效。它们并不能证明对端信任签发者、证书仍在有效期内,或者私钥确实属于这张证书。
解析错误能说明什么,不能说明什么
解析失败把问题定位到一个输入和一个阶段。它不会告诉你客户端用的是两种存储形态中的哪一种,也不会告诉你这个对象是否会被接受。那是两个独立的问题,各自需要独立的证据。
解析成功之后,下一个失败属于更靠后的阶段。信任被拒、用途不匹配或密钥不匹配由对端或 TLS 层报告,而不是由加载器报告。反复替换同一个文件不会让那个错误挪位。
排查期间请把敏感的那一半留在公开报告之外。可以分享指令名、报错文本和块的结构,但要移除密钥材料以及任何可识别的载荷。openvpn(8) 手册记录了文件指令的两种形式,以及它们背后的工作目录行为。