来源:notes/documents/跨跳板机操作内网主机的_SSH_双层跳转方法.md

跨跳板机操作内网主机的 SSH 双层跳转方法

场景

需要操作一台位于远程内网、没有公网出口的虚拟机。能直达的第一跳只有一台跳板机(飞牛 fnOS),目标虚拟机在它的局域网段里,只能从跳板机再跳一次。

这类「两层 SSH」的场景在运维里很常见,但直接写嵌套命令会立刻撞上两个问题:密码没法交互输入,以及引号被外层 shell 提前吃掉。

整体链路

本机
  │  第 1 跳:SSH,用 SSH_ASKPASS 自动喂密码(本次走 ZeroTier)
  ▼
跳板机 10.71.48.167 / 192.168.110.147
  │  第 2 跳:跳板机自带 sshpass,SSH/SCP 到内网
  ▼
目标虚拟机 192.168.110.91

第 1 跳:用 SSH_ASKPASS 实现非交互密码登录

自动化环境里没有 tty,ssh 弹不出密码提示,会直接失败。这时用 SSH_ASKPASS:让 ssh 去执行一个脚本拿密码。

# askpass 脚本的内容就是打印密码
printf '#!/bin/sh\nprintf "%%s\\n" "密码"\n' > /tmp/.rap.sh
chmod 700 /tmp/.rap.sh

export SSH_ASKPASS=/tmp/.rap.sh
export SSH_ASKPASS_REQUIRE=force   # 关键,OpenSSH 8.4+ 才支持
export DISPLAY=:0                  # 不设 DISPLAY 时可能不会启用 askpass

setsid -w ssh -n ... admin@跳板机 '命令'

三个细节:

  • SSH_ASKPASS_REQUIRE=force 是重点。默认情况下 ssh 只在「完全没有 tty」时才走 askpass,force 让它无条件走。
  • setsid 用于脱离当前控制终端,正好制造「没有 tty」的条件;-w 保证等待子进程结束再返回。
  • -n 把 stdin 重定向到 /dev/null,避免 ssh 反过来抢 stdin。

第 2 跳:复用跳板机上现成的 sshpass

先探测目标环境,发现跳板机自带 /usr/bin/sshpass,不需要额外安装:

export SSHPASS='密码'
sshpass -e ssh -n -o ... admin@目标机 '命令'

sshpass -e(从 SSHPASS 环境变量读密码)而不是 sshpass -p 密码。区别很实际:-p 会把密码写进命令行参数,任何用户 ps 一下就能看到;-e 不会。

关键技巧:base64 包装内层命令

命令要穿过两层 shell,引号和 $ 会被外层提前解释掉:

ssh 跳板机 "ssh 目标机 \"echo 密码 | sudo -S -p '' id\""   # 转义地狱

解决办法是把最内层要执行的脚本先 base64 编码,变成一个不含任何特殊字符的字符串,到对面再解码执行:

B64=$(base64 -w0 < 命令)
ssh 跳板机 "echo $B64 | base64 -d | bash"

base64 字符集只有 A-Za-z0-9+/=,无论套几层 SSH 都不会被改写。这样就能安全传递任意多行脚本,包括带单双引号、$()、heredoc 的内容。这是整个方案里最省事的一步。

封装成可复用脚本

把机制固化成两个小脚本,都从 stdin 读命令、自行 base64、再投递:

  • 单跳脚本:到跳板机
  • 双跳脚本:到内网虚拟机(在第 1 跳里再嵌第 2 跳)

使用方式:

cat <<'PAYLOAD' | ./vmrun.sh
hostname
ls -lh /opt/zerotier/bin/zerotier-one
PAYLOAD

在目标机上提权

目标机的 sudo 需要密码,而 stdin 已经被 base64 管道占用,所以单独喂一次:

echo '密码' | sudo -S -p '' bash 要执行的脚本

-S 从 stdin 读密码,-p '' 清空提示符,避免提示文字混进命令输出。

凭据收尾

所有临时凭据(askpass 脚本、SSHPASS、known_hosts)在每轮操作结束后立即删除,不留在磁盘上。

为什么不用 ProxyJump

ssh -J admin@跳板机 admin@目标机(ProxyJump)本来可以一次穿透,写法更短。没有采用的原因:

  • 两跳的密码提示会混在一起,在非 tty 环境下更难控制
  • 跳板机本来就有 sshpass,分层写更直观
  • 分层之后,哪一跳出问题一眼就能定位,排障成本更低

复用要点

  • 自动化环境里 ssh 登录优先用 SSH_ASKPASS + setsid,比 sshpass 更通用(不依赖额外软件),但要满足 SSH_ASKPASS_REQUIRE=forceDISPLAY 两个前提。
  • 跨多层 shell 传命令,优先用 base64 包装,能直接消灭转义问题,比拼引号可靠得多。
  • 优先用 sshpass -e 而不是 -p,避免密码出现在进程列表里。
  • 先探测跳板机有什么能力command -v sshpassping 是否可用等),往往能省掉安装步骤。这次也发现跳板机的 pingcap_net_raw,只能改用 TCP 探测。
  • 把机制固化成脚本再迭代。这次因为要反复「执行 → 报错 → 修复」,封装脚本让每轮重试只花几秒,而不是重敲一长串 ssh 参数。

相关

本文是 ZeroTier 部署过程的方法提炼,部署细节见 notes/documents/通过跳板机把_Deepin_25_虚拟机接入_ZeroTier_网络.md