密码无效是容器问题,而不是密码规则问题
被拒绝的凭据可能从未到达网关。先比较输入容器、认证阶段和账户本身。
看到密码无效的提示,通常会得出一个结论:VPN 不喜欢标点符号。这个结论通常是错的,因为该消息往往来自本地处理,而任何网关都还没看过账户。两种设计传递同一个密码,图形输入框和凭据文件,它们在不同地方失败。在把本来没问题的密码简化之前,先比较这些容器。
错误发生在网关之前
本地校验、被锁定的密钥库、缺失的代理以及格式错误的凭据文件,都会产生用户感受到的同一次拒绝。它们都与认证服务器无关。网关自身的拒绝是另一回事,通常伴随不同的消息和不同的时机。它发生在隧道已经存在、凭据已经传输之后。
所以第一个问题不是哪些字符合法,而是这次尝试停在了哪里。从未打开过连接的客户端没有被任何人拒绝。把你观察到的阶段记下来,因为后续每一步都取决于这个分类。
这个区别也改变了由谁来修。容器问题属于设备及其所有者,而账户被拒绝属于运行认证后端的人。
比较容器,而不是字符
图形密码框、钥匙串条目、两行凭据文件和自动化密钥,都以不同的信封承载同一个值。信封有自己的规则,而这些规则通常才是出问题的地方。值本身很少是问题;值的传输才是。把这两个概念分开,诊断就会更短。
| Where the value stops | What you see | What you check | Who owns the fix |
|---|---|---|---|
| GUI field or managed profile | A validation message before any connection opens | The selected authentication mode and whether the field holds the secret or the username | The device owner or the profile author |
| Keychain or secret agent | A prompt that keeps reappearing, or an agent error | Whether the agent is unlocked and holds an entry for this profile | The user who owns the keychain |
| Two-line credential file | A prompt for a password that was already supplied | Line count, line order and whether a trailing newline is present | Whoever generated the file |
| Shell argument or environment | Works interactively but fails in automation | Whether the shell expanded, trimmed or logged the value | Whoever wrote the automation |
Community OpenVPN 2.6 客户端选项 描述了 auth-user-pass 使用的那个两行文件,包括密码行缺失时的行为:客户端会转而提示输入。这个细节解释了一类它向我要一个我已经提供的密码的报告。它也告诉你,空的第二行是一种有意义的状态,而不是无害的格式选择。
引号又是另一层:普通凭据文件里的引号会作为数据保存,而 shell 会在程序看到任何内容之前把它们去掉。同一个字符在一个地方无害,在另一个地方却有结构意义。表格无法告诉你客户端用了哪种容器,所以在选择某一行之前,先读客户端自己的配置。
换行同样是结构性的。以行分隔字段的文件无法承载包含换行的值,这是格式的限制,而不是对密码强度的判断。请使用部署所支持的投递机制,而不是为了凑合一个临时文件去裁剪密钥。
在不让 shell 破坏的前提下生成凭据文件
最安全的本地做法是让密钥不出现在 shell 历史和进程参数列表中。以交互方式读取两个字段,然后写入客户端期望的那两行。其余一切都可以留在便于审阅的配置文件里。
read -r VPN_USER
read -rs VPN_PASS
printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS" > /etc/openvpn/client/creds
chmod 600 /etc/openvpn/client/creds
openvpn --config /etc/openvpn/client/office.conf --auth-user-pass /etc/openvpn/client/creds
前两条命令先读取用户名,再在不回显的情况下读取密码;第三条写入这两行;第四条限制文件权限;最后一条用该文件启动客户端。注意 read -r 会保留反斜杠,这正是省略后会把一个能用的密码变成失败密码的那类细节。
避免用未加引号的变量或充满 shell 展开的 here-document 来生成文件。两者都可能在字符到达文件之前改变它们,由此产生的失败看起来像 VPN 拒绝,因为磁盘上的密码确实与你所知道的密码不同。如果你怀疑这一点,请比较文件的字节长度与预期值,而不要打印密钥本身。差一两个字节正是引号事故的特征。
从第一条错误读出阶段
保留一次尝试中的第一条具体消息,然后对它分类。本地校验错误、缺失的私钥、不可用的密钥代理以及网关认证失败,是四种不同的结果,有四位不同的负责人。只有其中一种与账户有关。
如果网关确实在拒绝一份投递正确的凭据,请与管理员一起检查账户、认证后端以及任何必需的挑战。通用的认证失败不会指出被禁止的字符。
通过正确的机制恢复已批准的值,而不是把它缩减成字母和数字。密码策略属于运营方,而悄悄绕过它的变通办法并不是修复。它还会向后来者隐藏真正的缺陷,那个人会遇到同样的错误,却用着更弱的密码,也没有更好的主意。