不用图形界面,从 Linux shell 登录 Cisco Secure Client
here-document 不是登录协议。记录真实的提示顺序,顺序会变时用 expect,并让口令远离 argv 与历史记录。
人们相信一个四行的 here-document 就是完整的非交互登录,这种信念一直撑到网关给出两个组、打印横幅,或者在口令之后索要令牌。这时同一个脚本会把口令敲进一个正在等数字的菜单。Linux 上的命令行客户端可以脚本化,但前提是喂给它真正在等的东西,而不是教程假设的东西。
固定的 here-document 是个神话
Cisco 的 Linux 客户端会把一个小程序装到 /opt/cisco/anyconnect/bin/vpn,Secure Client 5 依然自带它。只要 vpnagentd 服务在跑,-s 标志就按客户端提问的顺序从标准输入读取答案。这个顺序在不同服务器之间并不固定。
组菜单会多一行。登录横幅再多一行。口令之后的令牌提示会直接打断四行脚本,因为第五行从来就不该是令牌。失败是安静的:客户端收到错误的字符串,连接尝试结束,脚本什么有用的信息都不报。
对同一个网关和同一个版本手动跑一次客户端,按提示出现的顺序记下来。这份记录就是脚本必须满足的规格。
客户端到底在等什么
常见的单组情形下,顺序是用户名、口令,有时还有一次横幅确认。只有当服务器真的给出菜单时,here-document 的第一行才会成为菜单序号,这就是同一个脚本在这里能用、到下一个站点就失败的原因。
/opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net <<EOF
0
alice
$VPN_PASSWORD
y
EOF
把它读成对四个可能问题的四个回答,而不是固定协议。零是菜单选择,不是口令,末尾的 y 接受许多网关根本不会发来的横幅。
顺序不可预测时改用 expect
提示顺序会变时,别再数行,改为等提示。expect 脚本启动 connect 命令,等用户名提示,发送,等口令提示,发送,然后等已连接状态。每一次等待都有自己的超时。
把超时当成失败并退出。在意外提示之后继续送输入的脚本,会很乐意把口令敲进 shell、菜单,或者任何恰好在读的地方。登录脚本就是这样变成事故的。
expect 还会给你一份可以抹掉秘密之后再记录的对话,网关升级后改了提示时,这比退出码更值钱。
秘密总得有个地方待着
这个问题的每个版本都有同一个弱点:口令必须以客户端能读的形式存在。当作参数传会让它出现在进程表里。通过管道回显常常把它写进 shell 历史。共享跳板机上的其他本地用户都能读到这两处。
install -m 600 /dev/null ~/.vpn-secret
umask 077; VPN_PASSWORD=$(cat ~/.vpn-secret) /opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net
属于该账户、权限为 600 的文件是最低要求,而且它仍是磁盘上的明文秘密。secret-tool lookup 从 Linux 钥匙环读取,在 macOS 上同样的活由 security find-generic-password -w 完成。组织支持证书或 SAML 认证的地方优先用它,因为存下来的口令加 MFA 仍然没法在没有人参与的情况下完成登录。
先确认隧道,再自己负责断开
不要相信没有错误文本。向客户端询问状态,并独立检查接口:
/opt/cisco/anyconnect/bin/vpn state
ip -brief address
状态应当读到 Connected,隧道接口应当有地址。然后请求一个隧道本该到达的资源,因为已连接却没有路由是真实会发生的结果,而且很容易被漏掉。
用 vpn disconnect 断开,并让启动隧道的脚本自己负责收尾。失败的任务把会话留在打开状态,会占掉一个许可席位,在下一个人眼里还像是认证问题。
工作顺序:确认 vpnagentd,记录真实的提示顺序,选择 here-document 或 expect,让秘密远离 argv 与历史,验证状态和一个资源,然后在同一个脚本里断开。