一个原本能启动的配置现在因选项错误而失败,而解析器引用的那一行在屏幕上看起来完全合理。陷阱就在这里:配置文件是物理行的列表,每行包含一个选项关键字及其参数,而被编辑器折行的长行在你查看字节之前看起来与两行毫无区别。网络、证书和隧道都与这类失败无关。屏幕上看起来完全相同的两行可能只差一个字节。

按顺序执行下面的检查,并在第一个失败处停下。每一项都比下一项更省事,而且各自排除一种不同的解释。

一行,一条指令

解析器读取一行,把第一个词当作选项,把其余部分当作该选项的参数。因此像 cert server.crt port 1194 这样的行并不是连续的两条指令。它是一个带三个参数的 cert 选项,而第三个并不是该选项接受的某个文件名。把错误理解为缺少换行是对的;把它理解为缺少指令就不对了。

# malformed: one option, three arguments
cert server.crt port 1194

# intended: two options, one argument each
cert server.crt
port 1194

那两条修正后的行是片段,不是配置。它们存在只是为了说明边界该放在哪里。配置里的每一条普通指令都遵循同样的规则,而把两条接在一起的行要么产生参数错误,要么产生该选项没有预期的值。

证明换行是真的

编辑器为了显示而折行。有些编辑器在粘贴时还会插入硬换行,有些不会,所以相同的视觉效果可能来自两个非常不同的文件。在改动任何东西之前,先显示行尾并数清行数。测量只需一条命令;猜测要花一晚上。

cat -A /etc/openvpn/server/lab.conf | sed -n '10,20p'
awk 'NR>=10 && NR<=20 {printf "%d:%s\n", NR, $0}' /etc/openvpn/server/lab.conf

在第一个输出里,每个真实的换行都会显示为 $ 标记,因此被折行但没有被分割的行,在屏幕折行的地方不会显示任何标记。第二条命令会打印带编号的行,这是比较解析器报告的内容与文件实际内容的最快方法。

多余的空格也不会产生指令。缩进只是外观,以空白开头的行仍然是同一个选项。如果错误消息指向某个行号,就读那一行本身,而不是它所属的段落。

引用的是路径,不是指令列表

引号用于包含空格的参数,而不是用来给选项分组。带空格的证书路径需要自己的引号;两条指令共用一对引号仍然是一条错误行。

cert "/etc/openvpn/pki/lab certs/server.crt"
key "/etc/openvpn/pki/lab certs/server.key"

检查选项名的同时也要检查排版,因为拼接出来的行可能产生一个看似可信、实际从未存在过的记号。端口指令是 port;用别的东西表达同一想法的配置即使在修正行边界之后也会失败。当某个名字看起来陌生时,把它与你所运行版本的选项列表比较,而不是与论坛上的示例比较。

把内联块当作块

配置可以内联携带证书和密钥,而这些块是数据,不是配置。块内的 PEM 行不是可以展平、重新缩进或拼接的选项,块标记必须留在各自的行上。

把内联块当作不透明区域:修正它周围的选项行,让它的内容逐字节保持原样,并且绝不要把它粘贴到公开的支持帖里。如果某个格式化工具重写了这个块,请从你的 PKI 恢复,而不是手工修补。

修最小的那一段,而不是模板

诱人的做法是用某个“能用”的教程里的配置替换整个文件。那等于用一组没有人审阅过的策略改动来交换一行错误:不同的 DH 参数、压缩设置、加密算法选择、重复的监听器。原来的问题很小且局部,而替换两者都不是。

把配置复制到一个受保护的工作文件,在那份副本里修正行边界,然后让服务的正常所有者加载它。接着启动实例,从头读取第一个错误(如果有的话)。

openvpn --config /etc/openvpn/server/lab.conf --verb 3 --log /tmp/parse.log
grep -n 'Options error' /tmp/parse.log

当解析阶段通过后,下一个失败会是关于文件、证书或加密算法,而不是词汇,这才是真正的进展。保留工作副本,并写一行关于连接了什么的备注;同样的粘贴还会在同一团队的其他人身上再次发生。

相关文章