ConnMan 工作一个下午之后就开始拒绝连接,而日志行会提到证书校验。这句话把人引向从未损坏的 CA 包。两种不同故障会产生几乎相同的失败形态:会话 Cookie 过期,或者服务器证书真的换了。大约一分钟就能把两者分开,而且只有一种与指纹有关。

两种故障,一行让人误解的日志

靠两点观察就能区分,而且都不需要再看第二遍。第一点是故障从什么时候开始:过期的 Cookie 出现在工作数小时或数天之后,而 换掉的证书则出现在网关变更之后立刻。

SignalExpired cookieChanged certificate
When it startedafter hours or days of workingright after a gateway change
Fingerprint from a fresh authenticateunchangeddifferent value
Usual log tailHTTP CONNECT 401 / unauthorizedsigner not found / verify failed
Repairreplace the cookie, restartre-pin deliberately, then restart

一分钟的判断就是下面这次 authenticate 运行。把它的 FINGERPRINT 与文件里的 OpenConnect.ServerCert 对比。指纹相同而 COOKIE 不同,就只换 Cookie。指纹不同说明服务器证书确实换了,那么要有意识地重新固定,并记录是谁批准的。

生成材料,而不是手工敲进去

与 NetworkManager 不同,ConnMan 没有导入 OpenConnect 配置文件的步骤。它读取一个预置文件,其中的值必须提前生成,而受支持的生成方式就是 OpenConnect 的 authenticate 模式。

sudo openconnect --csd-wrapper=/home/user/.cisco/csd-wrapper.sh \
  --authenticate --user myuser vpn.example.com

输出会打印四个值:POST、COOKIE、HOST 和 FINGERPRINT。带 sha256: 前缀的指纹就是服务器固定值。Cookie 则是会话产物,它的寿命由网关决定。要把两者当作不同种类的秘密:它们按不同的时间表失效,而且只有一个预期会变。

文件里两者都存,而它们的衰老方式不同

预置文件位于 /var/lib/connman-vpn/ 之下,由 VPN 守护进程按连接读取,而不是由托盘小程序读取。连接名放在全局段,协议细节放在提供方段。

[global]
Name = Company VPN

[provider_openconnect]
Type = OpenConnect
Name = Company Cisco VPN
Host = vpn.example.com
Domain = corp.example
OpenConnect.ServerCert = sha256:...
OpenConnect.Cookie = ...

要用门户实际提供服务的那个主机名。裸 IP 地址可能通过证书检查却仍然失败,因为设备按虚拟主机路由,并且期望那个名字。

systemctl status connman-vpn --no-pager   # is the daemon even installed and running
sudo systemctl restart connman-vpn       # after the file edit
journalctl -u connman-vpn --since '1 hour ago'  # after the connection attempt

日志实际显示了什么

托盘提示不会告诉你是哪一种故障。如果你读的是顺序而不是第一条吓人的行,日志会告诉你。陈旧的 Cookie 通常表现为 TLS 协商完成、随后 HTTP 层拒绝,所以关于证书的字眼才出现在授权失败旁边。

把新的 authenticate 运行结果与文件中的值对比。指纹一致而只有 Cookie 不同,你就有了答案,而且不必动信任设置。

刷新已经过期的那一项

重复 authenticate 命令,替换 OpenConnect.Cookie,然后重启守护进程。除非新输出显示了不同的值,否则不要碰指纹;那种情况下服务器证书确实被替换了,重新固定是应该有人记录的决定,而不是从错误信息里粘过来。

把刷新自动化是合理的:一个小脚本从标准输入读密码,运行 authenticate 模式,只重写 Cookie 那一行。凭据要放在脚本之外,并且只让运行它的账号可读;重写要做到原子性,这样失败的运行不会留下写了一半的文件。

如果每隔几小时就需要刷新,把这个间隔记下来。它属于网关的会话策略,也决定是自动化刷新,还是去申请更长的会话。

相关文章