为什么需要一台跳板机
手上有三五台服务器的站长经常会遇到一个尴尬局面:安全组里 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@laptopfrom= 的地址要用跳板的**内网地址**,因为连接是从跳板发起的,源地址是跳板的内网 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 sshdignoreip 一定要带上内网段,否则跳板自己(比如你从跳板发起的回环测试)可能被 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 反向隧道)作为应急通道?不建议。反向隧道需要在业务机上提前建好并常驻,等于在业务机上多开一个入口,反而扩大攻击面。应急通道用云控制台就够,不用自己造。
验证清单
搭完之后按下面的清单逐项过,全过就算完工:
- 本地
ssh web01能直接进入业务机(走 ProxyJump),且ssh -v显示用的是本地密钥; - 跳板机上执行
ls ~/.ssh/确认**没有**任何业务机私钥; - 从跳板机以外的公网 IP 直接
ssh 业务机公网IP,应超时或拒绝; - 业务机
authorized_keys的from=限制生效:从非跳板内网地址连接被拒; fail2ban-client status sshd显示 running,且ignoreip包含内网段;- 日志里能看到登录成功的指纹记录(说明 LogLevel VERBOSE 生效);
- 备份脚本改走内网后执行一次,确认成功;
- 云控制台 VNC 能打开并登录(最后一道保险)。
这套方案我用了大半年,成本是零(跳板就是原有的那台网站服务器),收益是从每天几千次 SSH 探测变成零,并且所有登录都集中在一个日志文件里,事后能查。相比装一整套堡垒机软件(JumpServer 之类),对个人站长的服务器规模来说,SSH 自带的 ProxyJump 加 agent 转发已经够用了。