用跳板机收口SSH:ProxyJump与agent转发

为什么需要一台跳板机

手上有三五台服务器的站长经常会遇到一个尴尬局面:安全组里 22 端口对外开着,每天被扫描器敲几千次;想收口只能开自己家的动态 IP,结果出差、换网络、手机热点全都要改一遍安全组。正确解法是留一台公网可访问的跳板机(bastion / jump host),其余机器的 22 端口只对这台跳板机的内网地址开放。

这篇文章记录我在 Debian 12 上搭这套东西的完整过程,包括 SSH 密钥与 agent 转发、跳板机的授权边界、fail2ban 与审计日志,以及几个「搭完才发现」的坑。目标不是教科书式的完美架构,而是一台单人维护的服务器群能真正用得下去的最小方案。

网络拓扑与安全组

我的实际拓扑是三台机器:一台公网跳板(jump.example.com,同时也是网站前端,跑 Nginx),两台业务机只在内网网段 10.0.0.0/24 里。安全组(或者 iptables / ufw)规则如下:

  • 跳板机:22 端口对 0.0.0.0/0 开放(必须,否则自己都连不上),但限定只允许公钥登录;
  • 业务机:22 端口只允许 10.0.0.11(跳板机内网地址);
  • 业务机的 Web 端口对公网开放,但只允许跳板机转发过来的流量(如果你在前面还有一层 CDN/负载均衡,这条可以更严格)。

先不要急着关掉业务机的公网 22 端口。正确顺序是:先把跳板机通路打通并验证,再关。关掉之后如果跳板挂了,你只能去云服务商的 VNC 控制台救场——所以跳板机本身要做最低限度的加固,但不要做过度定制。

跳板机上的最小加固

跳板机的 SSH 配置集中在一个 drop-in 文件里,不要直接改 /etc/ssh/sshd_config,方便回滚:

# /etc/ssh/sshd_config.d/10-bastion.conf
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowAgentForwarding yes
AllowTcpForwarding yes
GatewayPorts no
X11Forwarding no
ClientAliveInterval 120
ClientAliveCountMax 3
MaxAuthTries 3
LoginGraceTime 20
LogLevel VERBOSE

# 只允许这些用户登录
AllowUsers deploy ops

几个参数的用意:PasswordAuthentication no 是硬要求,跳板机公网可达,密码登录基本等于把门开着;AllowAgentForwarding yes 是为了做 agent 转发(下面细说);AllowTcpForwarding yes 是 -J 跳转的前提;LogLevel VERBOSE 会记录公钥指纹,方便审计是谁登录的。

改完先别 reload,用 sshd -t 校验,再 systemctl reload ssh。**reload 之前务必在另一个终端保持一个已登录的会话**,这样配置坏了还能救回来。

sshd -t && systemctl reload ssh
# 另开一个终端验证能登录,再关掉旧会话

不是「跳板机上有私钥」,而是 agent 转发

这是最容易做错的地方。很多人搭跳板机的做法是:把业务机的私钥也放在跳板机上,登录跳板后再用私钥登业务机。这样一来,跳板机一旦被攻破,所有业务机全部沦陷——跳板机从「收窄入口」变成了「单点提权器」。

正确做法是 SSH agent 转发:私钥只存在你本地电脑上,登录时用 -A 把 agent 的 socket 转发到跳板机,跳板机在需要时向你的本地 agent 请求签名,私钥本身从不出现在跳板机上。

# 本地 ~/.ssh/config
Host jump
    HostName jump.example.com
    User ops
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent yes

Host web01
    HostName 10.0.0.21
    User ops
    ProxyJump jump

Host db01
    HostName 10.0.0.22
    User ops
    ProxyJump jump

配好之后,本地执行 ssh web01 就会自动经由 jump 跳转,全程用本地的私钥,跳板机上没有任何业务机私钥。这也是 -J 参数的用法:ssh -J ops@jump.example.com ops@10.0.0.21。

注意 ForwardAgent yes 有安全代价:跳板机上的 root 可以劫持转发的 socket,冒充你向业务机发起认证。所以规则是:跳板机只给自己用,不给其他人开 root;用完退出会话,别挂着不关。如果跳板上有多人使用,就不要开 agent 转发,改成在跳板上放一份受限的密钥(用 command= 或 from= 限制用途)。

业务机上怎么信任代理

业务机的 authorized_keys 里要放你本地的公钥(不是跳板的公钥),因为 agent 转发过来的是你的密钥。同时用 from= 限制来源地址,即使密钥泄露也只能从内网跳板进来:

# 业务机 ~/.ssh/authorized_keys
from="10.0.0.11",no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAAC3Nza... ops@laptop

from= 的地址要用跳板的**内网地址**,因为连接是从跳板发起的,源地址是跳板的内网 IP,不是你的公网 IP。用 ssh -v 看到 Authenticated to 10.0.0.21 且 publickey 成功即为正常。

如果业务机上还有自动化脚本要跑(部署、备份),给它单独一把密钥,并加 command= 锁死能执行的命令,别和人的密钥混用。

fail2ban 与登录审计

跳板机公网可达,即使只允许公钥登录也会被反复尝试。fail2ban 在这里主要作用是减少日志噪音和挡住 SSH 协议层的探测:

apt install fail2ban -y
cat >/etc/fail2ban/jail.d/sshd.local <<'EOF'
[sshd]
enabled = true
backend = systemd
maxretry = 3
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 10.0.0.0/24
systemctl enable --now fail2ban
fail2ban-client status sshd

ignoreip 一定要带上内网段,否则跳板自己(比如你从跳板发起的回环测试)可能被 ban 掉,那就要用 VNC 救场了。

审计方面,sshd 的 VERBOSE 日志会记录指纹,用一条命令按天统计登录来源:

journalctl -u ssh --since "today" --no-pager | grep 'Accepted'
# 想看失败的公钥指纹(判断是否有人在爆破特定账号)
journalctl -u ssh --since "today" --no-pager | grep -E 'Failed|refused' | tail -20

如果想让审计更正规,可以装 auditd 并加一条监控 authorized_keys 修改的规则,这样谁能改你的登录凭据就有记录:

cat >/etc/audit/rules.d/authkeys.rules <<'EOF'
-w /root/.ssh/authorized_keys -p wa -k authkeys
-w /home/ops/.ssh/authorized_keys -p wa -k authkeys
augenrules --load
ausearch -k authkeys -i | tail -20

端口收口后,别忘了这两个连带影响

把业务机的 22 端口对公网关掉之后,会有两件事立刻坏掉,第一次做的人基本都会踩。

第一件:监控与备份链路。如果你在另一台机器上跑备份脚本,用 rsync -e ssh 直连业务机公网 IP,现在就连不上了。修法是让备份机也走跳板,或者干脆改从内网跑。rsync 走跳板要显式指定 -e 参数里的 ProxyJump:

rsync -avz -e "ssh -J ops@jump.example.com" \
  /data/backup/ ops@10.0.0.21:/data/backup/

注意 -e 里的字符串会经过 shell 解析,路径里有空格要小心。

第二件:第三方服务商的白名单。有些服务(对象存储的跨区同步、外部监控探活、支付回调的固定 IP)需要往你这台机器发数据,如果它们是靠「来源 IP 白名单」限制的,你换了出口地址就得同步改。这类方往往要后台手工操作,提前一两天改完。

跳板机自身的可用性

跳板机是单点,所以要接受它可能挂掉的事实,做两件事降低影响。

一是**保持云控制台的 VNC 可用**,并且知道怎么用(大多数云厂商都有「远程连接」按钮,不需要 SSH)。这是我给自己留的最后一条路,所以我从不在跳板上做激进的内核参数调整。

二是**给跳板机做最简配置**。跳板机只跑 SSH 和必要的 Web 服务,不装数据库、不装 Docker、不跑定时任务。我见过有人把跳板机当「工具箱」用,装了十几样东西,结果跳板一挂所有业务都进不去。原则是:跳板的功能单一,任何能挪走的都挪到业务机上去。

另外,跳板上可以装一台轻量级的备用 NAS/端口转发工具(比如 frp 或 ssh 反向隧道)作为应急通道?不建议。反向隧道需要在业务机上提前建好并常驻,等于在业务机上多开一个入口,反而扩大攻击面。应急通道用云控制台就够,不用自己造。

验证清单

搭完之后按下面的清单逐项过,全过就算完工:

  1. 本地 ssh web01 能直接进入业务机(走 ProxyJump),且 ssh -v 显示用的是本地密钥;
  2. 跳板机上执行 ls ~/.ssh/ 确认**没有**任何业务机私钥;
  3. 从跳板机以外的公网 IP 直接 ssh 业务机公网IP,应超时或拒绝;
  4. 业务机 authorized_keys 的 from= 限制生效:从非跳板内网地址连接被拒;
  5. fail2ban-client status sshd 显示 running,且 ignoreip 包含内网段;
  6. 日志里能看到登录成功的指纹记录(说明 LogLevel VERBOSE 生效);
  7. 备份脚本改走内网后执行一次,确认成功;
  8. 云控制台 VNC 能打开并登录(最后一道保险)。

这套方案我用了大半年,成本是零(跳板就是原有的那台网站服务器),收益是从每天几千次 SSH 探测变成零,并且所有登录都集中在一个日志文件里,事后能查。相比装一整套堡垒机软件(JumpServer 之类),对个人站长的服务器规模来说,SSH 自带的 ProxyJump 加 agent 转发已经够用了。

Last modification:October 21st, 2026 at 10:26 pm

Leave a Comment