could not read Auth username from stdin 看起来像认证问题,其实并不是。OpenVPN 试图在标准输入上询问用户名,却发现没有任何可读内容,于是在真正联系服务器之前就停住了。账户完全可能是有效的;只是进程没有可以询问的对象。

因此,第一步有用的做法是确认客户端正在读什么,而不是重试连接或修改服务器端设置。请按顺序完成下面的检查,并在第一个失败处停下。

把错误当作本地输入失败来读

auth-user-pass 在没有文件参数时出现,客户端就期望弹出提示。在交互式 shell 中这个提示有效,因为进程拥有终端。而在 systemd、cron 任务或容器下,标准输入通常是空的,读取会立即返回你看到的错误。

消息还会指出失败的是哪个字段,用户名还是密码,这能说明读取走到了哪一步。缺少用户名意味着进程什么都读不到。用户名成功之后缺少密码,意味着输入流在两次读取之间结束了,这通常是文件只写了一半或管道被关闭造成的。

在改动其他任何东西之前,先确认客户端自己的视角。用同样的配置和同样的用户在前台运行它,观察提示是否出现。如果在你的 shell 里出现,而在服务管理器的环境下没有出现,差异在于输入来源,而不是凭据。快速比较 unit 文件与你的 shell 环境,通常能看出是哪一方提供了终端。

给服务一个受支持的凭据来源

受支持的无人值守来源,是作为参数传给 auth-user-pass 的凭据文件。文件按纯文本读取:第一行是用户名,第二行是密码。用绝对路径指向它,例如 /etc/openvpn/client/creds,这样服务账户的工作目录就无法改变该指令的含义。

权限和路径同样重要。文件必须能被运行客户端的账户读取,而不能被其他所有人读取,并且应当属于服务账户,或属于带有包含该服务的组的 root。以该账户身份检查可读性,不要打印内容。

相对路径是沉默的失败方式。配置文件可能在一个目录里,而 systemd 在另一个位置启动进程,于是你的 shell 里能找到的文件,对服务却找不到。把凭据移到固定位置,并用绝对路径引用。然后启动一次服务,确认使用的是同一条路径,而不是相信配置编辑器的显示。

如果部署需要一次性验证码或其他交互式挑战,就要判断客户端的工作流是否真的支持无人值守。带静态密码的文件无法回答会轮换的验证码,而重试一个无法回答的提示不会带来任何信息。

为什么 script-security 帮不上忙

script-security 控制的是客户端能否运行脚本、能向脚本传递多少内容,包括能否通过环境变量把密码交给脚本。它完全不涉及用户名和密码最初从哪里来。

因为消息提到 Auth,把它调高成常见条件反射,但它改变的是脚本权限而不是输入来源,并且扩大了客户端可以执行的范围。如果辅助脚本是设计的一部分,先修好输入来源,并把安全级别保持在部署所需的最低值。

管理接口是合理的替代方案。前端可以连接它,接收凭据请求,并用用户名和密码回答,一些 GUI 就是这样工作的。如果使用它,请绑定到本地地址,用管理密码文件保护,并限制谁可以连接,因为它是客户端的控制通道。

客户端读到文件之后,验证下一阶段

一旦客户端能读到凭据,接下来的失败就是与服务器之间的真实交换,其消息的含义不同。这一阶段的认证失败意味着服务器拒绝了账户;质询响应意味着服务器想要另一个因素。把这些当作新问题,而不是同一个问题重复出现。

按服务实际运行的方式测试整条路径:同一个账户、同一个工作目录、同一个 unit 环境。只在你的 shell 里启动才有效的连接,说明环境变量或终端仍然是流程的一部分。

然后把秘密放在该放的地方。命令行参数里的密码会出现在进程表中,调试输出里的密码会进入工单。使用受保护的文件或管理接口,把日志保持在不会回显凭据的级别,并在任何有访问权限的人离开时轮换文件。

检查清单:凭据的绝对路径、以服务账户身份可读、不需要终端、script-security 保持最低、仅在已绑定并认证时才使用管理接口,以及把结果中的服务器响应当作独立事件。

相关文章