type
Post
status
Published
date
Jun 5, 2026
slug
jumpserver-cli-auto-login
summary
把 JumpServer 登录流程拆成凭据读取和终端交互两层:zsh 从密码管理器安全读取 TOTP seed 与可选密码,expect 只负责响应 SSH 登录过程中的 password 和 Code 提示。
tags
SSH
JumpServer
zsh
Bitwarden
expect
category
技术分享
icon
password
AI summary
Last edited time
Jun 5, 2026 02:26 AM
JumpServer 的命令行登录通常不只是一次
ssh:先走 SSH 公钥或密码认证,再输入 MFA 动态验证码,某些环境还会额外进入 password prompt。每次手动复制验证码既慢,也容易在多个跳板机之间输错。我的处理方式是把流程拆成两层:
zsh负责准备凭据:检查依赖、解锁密码库、读取 TOTP seed、必要时读取 SSH password。
expect负责终端交互:启动ssh,遇到 password prompt 就发送密码,遇到 MFA Code prompt 就生成并发送当前 TOTP,最后把终端控制权交还给用户。
这样做的好处是:敏感信息不写进脚本,交互逻辑也不会和密码管理器逻辑混在一起。
前置条件
这套方案依赖以下工具:
还需要安装并登录 Bitwarden CLI:
如果使用 Bitwarden SSH Agent,还要确保 SSH 配置里显式指定 agent 和 key,避免 agent 里多把 key 被逐个尝试,提前触发服务端的
MaxAuthTries。注意:这里的
<jumpserver-host>、<ssh-user>、<jumpserver-key> 都应该替换成你自己的环境值,不要把真实地址和账号提交到公开仓库。命令入口
最终希望日常只执行一个函数:
这个入口有三个参数含义:
<ssh-host-alias>:本机~/.ssh/config里的 Host 别名。
<bitwarden-item-name>:Bitwarden 中保存 JumpServer 登录信息的 item 名。
-bw-password:表示 SSH password 也从 Bitwarden item 中读取;如果目标环境只需要公钥和 MFA,可以不传。
zsh 负责读取凭据
核心函数先检查依赖,再处理 Bitwarden 状态:
这里有几个关键点:
bw sync --force:避免桌面端刚更新了密码,但 CLI 还读到旧缓存。
_bw_get_exact_item:校验返回的 item name 必须完全等于请求值,避免 Bitwarden 的模糊匹配拿错条目。
_bw_totp_secret_from_json:读取.login.totp,同时兼容otpauth://...?secret=...这种格式。
_bw_password_from_json:优先读.login.password,也可以兼容自定义字段。
避免 Bitwarden 模糊匹配
bw get item <name> 在某些情况下可能返回相近名称的条目。对 JumpServer 来说,这很危险:拿错 TOTP seed 会生成错误验证码,而且错误不一定直观。因此我会加一层精确校验:
从 Bitwarden 解析 TOTP seed
Bitwarden 的 TOTP 字段可能是纯 base32 secret,也可能是
otpauth:// URL。下面的 helper 会统一转成 oathtool 可用的 base32 seed:脚本里不要保存当前 6 位验证码。验证码是短时值,应该在 expect 收到 Code prompt 的那一刻再生成,避免过期。
expect 只处理 SSH 交互
expect 部分保持简单:只做终端自动输入,不直接访问 Bitwarden。这里不要用
expect -f - 直接喂 heredoc。那样 expect 的 stdin 会被脚本内容占用,后面的 interact 可能无法继续接管当前终端,表现为发送验证码后会话直接退出。password 的处理
有些 JumpServer 环境会先走公钥,再进入 SSH password prompt。不要把 password 硬编码在
.zshrc 里,可以从 Bitwarden item 的 .login.password 或自定义字段读取:常见问题
too many authentication failures
如果 Bitwarden SSH Agent 里有多把 key,OpenSSH 默认可能逐个尝试。服务端在正确 key 被尝试之前就可能达到
MaxAuthTries,返回 too many authentication failures。解决方法是在对应 Host 中配置:
Code prompt 匹配不到
不同 JumpServer 版本的 MFA 提示文字可能不同。可以先手动执行一次
ssh <host-alias>,观察提示文本,再调整 expect 分支里的 pattern。例如:
如果你的提示是
Verification code:,就改成对应 pattern。发送验证码后会话退出
优先检查是否使用了
expect -f - 加 heredoc。更稳的做法是写入临时 expect 脚本文件,再用 expect -f "$expect_script" 执行,最后 rm -f 清理。安全边界
这套方案的原则是:
.zshrc只保存函数和占位式配置,不保存密码、TOTP seed、真实主机信息。
- TOTP seed 和 password 只从密码管理器读取。
- expect 不直接调用密码管理器,减少交互层复杂度。
- SSH config 使用 Host 别名,文章、脚本示例和公开文档只展示别名和占位符。
- 调试日志不要上传包含 HostName、User、公钥指纹、验证码、密码库 item 名的原始输出。
最终使用
配置完成后,日常登录就变成:
命令会自动完成 Bitwarden 同步、TOTP seed 读取、可选 password 读取和 JumpServer MFA 输入。成功后会进入正常 SSH 会话,后续操作仍然由当前终端接管。