2.11 SSH 深入——隧道、跳板机与配置管理
预计阅读时间:15 分钟
📖 目录
学习目标
学完本章后,你将能够:
- 编写结构化的
~/.ssh/config文件管理多台服务器 - 使用
-L、-R、-D实现本地/远程/动态端口转发 - 通过 ProxyJump 穿透多层网络访问内网服务器
- 理解 SSH 代理转发的原理与安全风险并正确使用
- 搭建 SSH 证书认证体系管理大规模服务器集群
核心知识
- SSH 配置文件(
~/.ssh/config)——按 Host 别名分组,支持通配符,集中管理所有 SSH 连接参数 - 本地端口转发(
-L)——将远程端口映射到本机,用于访问内网服务 - 远程端口转发(
-R)——将本机端口暴露到远程,用于穿透 NAT - 动态端口转发(
-D)——创建 SOCKS5 代理隧道,加密所有应用层流量 - ProxyJump(
-J)——通过跳板机透明连接目标主机,私钥不离开本机 - 代理转发(Agent Forwarding)——在跳板机上借用本地 SSH Agent 完成二次认证
- 证书认证(Cert Auth)——由 CA 统一签发用户证书,替代分散的
authorized_keys管理 - 多路复用(ControlMaster)——复用同一 TCP 连接减少握手开销
知识关联
- 前置知识:1.3:第一次进入命令行 第一次进入命令行(SSH 概念入门)、1.7:用户与权限管理 用户与权限管理
- 后续影响:SSH 隧道是 6.1:容器底层原理 容器底层原理、5.10:WireGuard VPN WireGuard VPN、3.1:Docker 容器入门 Docker 远程管理的基础网络能力
- 配套技术:4.1:Ansible 自动化 Ansible 自动化依赖 SSH 免密登录;5.1:Linux 安全加固 安全加固中 SSH 是最重要的攻击面之一
原理讲解
SSH 协议从建立连接到数据传输经历三个阶段:
| 阶段 | 协议层 | 作用 | 使用的加密技术 |
|---|---|---|---|
| 传输层握手 | TCP → SSH | 协商密钥交换算法、主机密钥认证、建立加密隧道 | Diffie-Hellman(密钥交换)、RSA/Ed25519(主机身份) |
| 用户认证 | SSH 内部 | 验证用户身份(密码、公钥、证书、GSSAPI 等) | RSA/Ed25519(签名验证) |
| 连接复用 | SSH 通道 | 在同一加密隧道上承载多个 channel(shell、exec、forwarded connections) | 对称加密(AES/ChaCha20) |
端口转发的工作原理:SSH 服务器在内部维护一个转发表,监听指定端口并将进入的数据包通过已建立的加密通道转发到目标地址。本地转发(-L)在客户端监听,远程转发(-R)在服务器端监听,动态转发(-D)在客户端创建一个 SOCKS 代理,由 SOCKS 协议告知目标地址。
ProxyJump 与 ProxyCommand 的对比:ProxyJump(SSH 7.3+)是内置的简化写法,本质上是让 SSH 先连到跳板机,再通过 -W 标志将 stdin/stdout 代理到目标主机。与传统的 ssh -W %h:%p jumper ProxyCommand 效果相同,但更简洁且支持多跳。
SSH Agent 的工作原理:ssh-agent 是一个长期运行的后台进程,加载私钥后通过 Unix socket(通常是 $SSH_AUTH_SOCK)对外提供签名服务。Agent Forwarding 将本地的 socket 文件描述符转发到远程,让远程 SSH 客户端可以请求本地 agent 签名——密钥本身从不离开本机。
SSH 握手完整流程与 known_hosts
一次 SSH 连接从 TCP 建立到登录成功,要经历以下步骤:
- TCP 三次握手连接 22 端口;
- 双方交换版本字符串(如
SSH-2.0-OpenSSH_9.6); - 算法协商:客户端发送支持的 KEX 算法、主机密钥类型、加密算法、MAC、压缩算法列表,服务器选双方交集,得出一组会话算法;
- 密钥交换:通过 Curve25519-sha256 等 ECDH 算法协商出会话密钥,服务器用主机私钥对交换材料签名,证明自己持有对应的主机公钥;
- 主机验证:客户端校验服务器主机公钥——首次连接时提示指纹,之后与
~/.ssh/known_hosts比对,不一致则警告"REMOTE HOST IDENTIFICATION HAS CHANGED"(疑似中间人攻击); - 用户认证(公钥 / 密码 / 证书 / OTP);
- 建立 channel,进入交互会话。
known_hosts 条目格式为 主机名 算法 公钥,启用 HashKnownHosts yes 后以 |1|... 哈希形式存储,防止主机列表泄露。验证指纹的命令:ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub 生成的指纹应与首次连接提示一致——这是防中间人攻击的第一道闸门。StrictHostKeyChecking=no 会静默接受任何主机密钥,等于放弃这道防线,仅建议在一次性容器环境使用。
SSH 配置指令速查
| 指令 | 作用 | 常用值 |
|---|---|---|
Host | 匹配段,支持通配符 * ? | prod-*、10.* |
HostName | 实际连接地址 | IP 或域名 |
User / Port | 默认用户 / 端口 | deploy / 2222 |
IdentityFile | 指定私钥路径 | ~/.ssh/id_ed25519 |
ProxyJump | 跳板机链(可逗号分隔多跳) | bastion、user@host:port |
ServerAliveInterval | 保活探测间隔(秒) | 60 |
ConnectTimeout | 连接超时(秒) | 10 |
ControlMaster / ControlPersist | 连接复用开关与保持时间 | auto / 10m |
ForwardAgent / ForwardX11 | 代理转发 / X11 图形转发 | no / yes |
StrictHostKeyChecking | 主机密钥校验策略 | yes / accept-new / no |
Compression | 链路压缩(低带宽友好) | yes |
配置匹配规则:先出现的段落优先生效,所以 Host * 通配段通常放最底部做默认值。调试时用 ssh -G 别名 查看某个别名最终生效的完整参数,避免"配置没生效"的困惑。
示例代码
~/.ssh/config 结构化配置
# ~/.ssh/config — 按环境分组的配置示例
# 全局默认
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Compression yes
StrictHostKeyChecking accept-new
# 跳板机
Host bastion
HostName bastion.example.com
User admin
Port 2222
IdentityFile ~/.ssh/bastion_ed25519
# 生产环境
Host prod-*
User deploy
ProxyJump bastion
ForwardAgent no
# 内网机器通配
Host 10.*
User ops
IdentityFile ~/.ssh/ops_ed25519
# 别名示例
Host web01
HostName 192.168.1.10
User deploy
ProxyJump bastion
ssh web01
# 输出:
# Last login: Thu Jul 30 09:15:22 2026 from 10.0.0.1
# [deploy@web01 ~]$
本地端口转发(-L)
# 将远程 MySQL (127.0.0.1:3306) 映射到本地 3307
ssh -L 3307:127.0.0.1:3306 user@db-server
# 另一个终端连接
mysql -h 127.0.0.1 -P 3307 -u root -p
远程端口转发(-R)
# 将本地开发服务 (:80) 暴露到公网服务器 8080 端口
ssh -R 8080:127.0.0.1:80 user@public-server
# 公网用户访问
curl http://public-server:8080
动态转发(-D)SOCKS 代理
# 创建 SOCKS5 代理隧道
ssh -D 1080 user@remote-server
# 浏览器配置
firefox --proxy-server='socks5://127.0.0.1:1080'
curl --socks5 127.0.0.1:1080 https://checkip.amazonaws.com
# 输出:
#
ProxyJump 多跳连接
# 单跳
ssh -J user@bastion:2222 deploy@10.0.1.50
# 多跳
ssh -J user@bastion,user@internal-gw deploy@10.0.2.100
# 等价 config 配置
Host internal-*
ProxyJump bastion
User deploy
SSH 证书认证
# 1. 创建 CA(安全机器上操作)
ssh-keygen -t ed25519 -f ~/ssh-ca -C "SSH CA for ops team"
# 2. 服务器信任 CA
echo "TrustedUserCAKeys /etc/ssh/ca.pub" >> /etc/ssh/sshd_config
cp ~/ssh-ca.pub /etc/ssh/ca.pub
systemctl restart sshd
# 3. 签发用户证书
ssh-keygen -s ~/ssh-ca -I "alice@ops" -n "deploy,admin" -V "+52w" ~/alice.pub
# 4. 用户将 alice-cert.pub 放入 ~/.ssh/ 即可登录
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
# 输出:
# ssh-ed25519-cert-v01@openssh.com
# Type: ssh-ed25519-cert-v01@openssh.com
# Key ID: "alice@ops"
# Principals: deploy, admin
# Valid from: 2026-07-30T00:00:00
# Valid to: 2027-07-29T00:00:00
SSH 安全加固
# /etc/ssh/sshd_config — 生产环境推荐配置
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
AllowUsers deploy admin
ClientAliveInterval 300
ClientAliveCountMax 0
AllowTcpForwarding yes
AllowAgentForwarding no
Match User backup
ForceCommand /usr/local/bin/rsync --server
sshd -t && systemctl reload sshd
# 输出: (无输出表示语法正确)
隧道模式选型对照
| 模式 | 监听位置 | 方向 | 典型场景 | 命令 |
|---|---|---|---|---|
本地转发 -L | 客户端 | 拉取远程内网服务 | 访问被防火墙挡住的 MySQL / Redis / 管理面板 | ssh -L 3307:内网DB:3306 jump |
远程转发 -R | 服务器 | 暴露本地服务到公网 | 内网开发机让公网同事访问、NAT 穿透 | ssh -R 8080:127.0.0.1:80 vps |
动态转发 -D | 客户端 | 全流量 SOCKS5 | 加密上网、临时代理、绕过地域限制 | ssh -D 1080 vps |
提示:-L / -R 是"点对点映射",只代理指定端口;-D 是"整机代理",浏览器、git、curl 等应用配合 SOCKS5 即可透明使用。隧道与连接共用一个 SSH 会话,断开即失效——需要长期驻留时用 autossh -M 0 自动重连。
ssh-agent 使用
# 启动 agent 并加载私钥
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l # 列出已加载密钥
ssh-add -t 8h ~/.ssh/id_ed25519 # 8 小时后自动忘记密钥
# 免密码批量执行
ssh web01 'hostname'
ssh web02 'hostname'
# 临时使用指定密钥(不污染 agent)
ssh -i ~/.ssh/ci_key user@server
# systemd 用户会话自动启动 agent
# ~/.config/systemd/user/ssh-agent.service
# [Unit]
# Description=SSH key agent
# [Service]
# ExecStart=/usr/bin/ssh-agent -a %t/ssh-agent.socket
# Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
# [Install]
# WantedBy=default.target
systemctl --user enable --now ssh-agent.service
# 安全提示:Agent Forwarding(ssh -A)只在信任的中间机上开启
scp / sftp / rsync 对比与选择
| 工具 | 传输方向 | 增量 / 断点 | 压缩 | 典型场景 |
|---|---|---|---|---|
scp | 单文件 / 目录(双向) | 不支持 | 手动 -C | 一次性小文件拷贝 |
sftp | 交互式(双向) | 不支持 | 无 | 需要浏览远端目录树的交互操作 |
rsync | 单向同步 | 支持(差异传输 + 断点续传) | -z | 备份、大批量同步、增量部署 |
# scp 基础用法
scp -P 2222 backup.tar.gz user@server:/data/
scp -r ./configs/ user@server:/etc/myapp/
scp user@server:/var/log/app.log .
# sftp 交互会话
sftp user@server
# sftp> ls /data
# sftp> get /data/app.log
# sftp> put backup.tar.gz /data/
# sftp> bye
# rsync 是主力:增量 + 压缩 + 保持属性 + 断点续传
rsync -avzP --delete ./web/ deploy@server:/var/www/
# -a 归档(权限/时间/链接) -v 详细 -z 压缩 -P 进度+断点续传
# --delete 删除远端多余文件(务必先 --dry-run 演练)
# rsync 走 SSH 跳板
rsync -avz -e "ssh -J bastion" ./data/ user@internal:/data/
# 只打印将传输的文件,不实际执行
rsync -av --dry-run ./src/ user@server:/src/
SSH 加固进阶:fail2ban 联动
# 安装 fail2ban
sudo apt install fail2ban
# /etc/fail2ban/jail.local
# [sshd]
# enabled = true
# port = ssh
# maxretry = 3 # 5 分钟内失败 3 次
# bantime = 3600 # 封禁 1 小时
# findtime = 300 # 统计窗口 5 分钟
sudo systemctl enable --now fail2ban
# 查看封禁状态
sudo fail2ban-client status sshd
# 输出: Banned IP list: 203.0.113.7
# 误封解封
sudo fail2ban-client set sshd unbanip 203.0.113.7
# 手动封禁
sudo fail2ban-client set sshd banip 198.51.100.9
# Debian 12+ 默认 nftables 后端,可见规则:
# nft list ruleset | grep fail2ban
多因素认证:TOTP + SSH
# 服务端安装 google-authenticator
sudo apt install libpam-google-authenticator
google-authenticator # 生成密钥,按提示扫描二维码
# 输出:
# Your new secret key is: XXXXXXXXXXXXXXXXXXXX
# 二维码 / emergency scratch codes(务必离线保存)
# /etc/pam.d/sshd 文件顶部加入:
# auth required pam_google_authenticator.so nullok
# sshd_config 中启用键盘交互并组合因子:
# KbdInteractiveAuthentication yes
# PasswordAuthentication no
# AuthenticationMethods publickey,keyboard-interactive
sudo systemctl reload sshd
# 效果:公钥 + 6 位动态码双因子,密码认证保持关闭
# 验证:ssh user@server 先验证密钥,再提示输入 6 位验证码
# 多服务器共享同一密钥:把 ~/.google_authenticator 分发到各服务器
# 注意:密钥文件权限必须为 600,属主为登录用户
常见错误
| 错误表现 | 根因 | 正确做法 |
|---|---|---|
Bad owner or permissions on ~/.ssh/config | ~/.ssh/config 或 ~/.ssh/ 目录权限过大 | chmod 600 ~/.ssh/config && chmod 700 ~/.ssh |
| 端口转发成功但连接被拒绝 | 转发目标地址写成了 localhost,在远端语境下指向了跳板机自身 | 确认目标服务监听地址,转发地址使用实际监听地址而非 127.0.0.1 |
Permission denied (publickey) 但密钥已上传 | 公钥未追加到 authorized_keys,或 ~/.ssh 权限不对 | 使用 ssh-copy-id 自动处理,或手动检查 authorized_keys 权限为 600 |
ProxyJump 报 unknown option | 客户端 SSH 版本低于 7.3,不支持 -J 参数 | 升级 OpenSSH 或改用 -o ProxyCommand="ssh -W %h:%p jump" |
Agent Forwarding 在跳板机上报 ssh-agent: no such file or directory | 跳板机未启动 ssh-agent,或 $SSH_AUTH_SOCK 未正确传递 | 跳板机上 eval $(ssh-agent) 启动 agent;客户端用 ssh -A 明确开启转发 |
最佳实践
| 建议 | 原理 | 示例 |
|---|---|---|
| 始终使用 Ed25519 密钥 | 性能比 RSA 快 3 倍,安全性更高,密钥更短 | ssh-keygen -t ed25519 |
| 用 ProxyJump 代替 Agent Forwarding | 私钥不离开本机,减少被窃取的风险 | Host target\n ProxyJump bastion |
| 配置文件按环境分组 + 通配符 | 减少重复配置,新加主机只需改一行 | Host prod-*; Host 10.*; Host *.internal |
| 启用 ControlMaster 复用连接 | 避免重复 TCP 握手,批量操作速度提升 5-10 倍 | ControlMaster auto; ControlPersist 10m |
| 修改 sshd_config 后先开逃生终端再 reload | 防止配置错误导致自己被锁在门外 | 第二个窗口 ssh -o ConnectTimeout=5 localhost 确认可登录 |
练习题
- (概念)SSH 本地转发(-L)和远程转发(-R)的核心区别是什么?分别适用于什么场景?
- (概念)ProxyJump 相比于传统的 Agent Forwarding 有什么安全优势?
- (实操)编写一个
~/.ssh/config,包含:全局保活间隔 60 秒;跳板机别名jump指向jump.example.com:2222,用户admin;内网10.*.*.*均通过jump跳转,用户ops,密钥~/.ssh/ops_ed25519。 - (实操)通过一条 SSH 命令在本地开启
1080端口的 SOCKS5 代理,并用curl测试是否正常工作。 - (🔍 挑战)某公司有 200 台 Linux 服务器,之前用
ssh-copy-id逐台分发公钥。现在需要实现:集中证书管理、用户证书有效期 90 天自动失效、支持吊销离职员工证书。请描述完整的 SSH 证书认证实施方案。
点击查看答案
- 本地转发(
-L 本地端口:目标地址:目标端口 跳板)在客户端监听,把远程服务映射到本地,适用于访问内网服务。远程转发(-R 远程端口:目标地址:目标端口 跳板)在服务器端监听,把本地服务暴露到远程,适用于穿透 NAT。 - ProxyJump 在客户端完成身份认证,私钥和 agent socket 始终留在本地。Agent Forwarding 将
$SSH_AUTH_SOCK传递到跳板机,跳板机上的 root 用户可以通过该 socket 冒充你连接其他服务器。 - 参考本章「示例代码」中
~/.ssh/config一节的配置。 ssh -D 1080 user@remote-server,然后curl --socks5 127.0.0.1:1080 https://checkip.amazonaws.com,输出应为 remote-server 的公网 IP。- 在一台安全机器上创建 CA 密钥对;在所有服务器
/etc/ssh/sshd_config中添加TrustedUserCAKeys /etc/ssh/ca.pub并重启 sshd;为每个员工签发带-V "+12w"(90 天)有效期的用户证书;员工离职时在 CA 侧用-z递增序列号签发同一公钥的-V "-1s"使之前证书失效。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释本地转发(-L)、远程转发(-R)、动态转发(-D)的区别 | 尝试向他人讲解 |
| 命令操作 | 能不查文档配置 ~/.ssh/config 实现多台服务器的便捷管理 | 在终端实际执行 |
| 原理掌握 | 能说出 SSH 握手流程和 known_hosts 防中间人攻击的原理 | 画出握手流程图 |
| 故障排查 | 能独立排查 SSH 连接超时、密钥认证失败等常见问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么 ProxyJump 比手动嵌套 SSH 更安全可靠 | 对比不同跳板方式 |
本章总结
SSH 远不止一条 ssh user@host 命令。本章从 ~/.ssh/config 的配置管理切入,深入讲解了三种端口转发的使用场景与原理,再到 ProxyJump 多层穿透、证书认证等企业级能力。掌握这些技术后,你将能从「能用 SSH 登录」进阶为「用 SSH 构建安全的远程网络」。
速查表
| 功能 | 命令 / 配置 | 速记口诀 |
|---|---|---|
| 配置别名 | ~/.ssh/config 中 Host + HostName | 配置写好,输入减少 |
| 本地转发 | ssh -L 本地端口:目标:端口 跳板 | 远程拉到本地 |
| 远程转发 | ssh -R 远程端口:本地:端口 跳板 | 本地推到远程 |
| SOCKS 代理 | ssh -D 1080 服务器 | 一条隧道,全流量加密 |
| 跳板机 | ssh -J 跳板 目标 或 ProxyJump | 一步穿透,密钥留本地 |
| 代理转发 | ssh -A 或 ForwardAgent yes | 借用本地 Agent |
| 证书认证 | ssh-keygen -s CA -I "id" -n "user" -V "+52w" key.pub | CA 统一签发,到期自动失效 |
| 多路复用 | ControlMaster auto; ControlPersist 10m | 复用连接,提速 N 倍 |
SSHFP DNS 记录
SSHFP 记录将 SSH 服务器公钥指纹发布到 DNS,客户端首次连接时可通过 DNS 验证服务器身份,防止中间人攻击:
# 生成 SSHFP 记录
ssh-keygen -r myserver.example.com
# 输出示例(添加到 DNS 区域文件)
myserver.example.com IN SSHFP 4 2 123456789abcdef...
# 客户端启用 SSHFP 验证(~/.ssh/config)
Host myserver
VerifyHostKeyDNS yes
# 或全局启用
# /etc/ssh/ssh_config 中添加:
# VerifyHostKeyDNS yes
Mosh——移动 Shell 替代方案
Mosh(Mobile Shell)基于 UDP 传输,解决 SSH 在高延迟/不稳定网络下的卡顿问题:
# 安装
sudo apt install mosh # Ubuntu
brew install mosh # macOS
# 连接(使用 UDP 60000-61000 端口)
mosh user@server
# 指定端口
mosh -p 2222 user@server
# 使用 SSH 密钥认证
mosh --ssh="ssh -i ~/.ssh/id_ed25519" user@server
| SSH | Mosh | |
|---|---|---|
| 传输层 | TCP | UDP(SSP 协议) |
| 漫游 | 断线需重连 | 自动重连,IP 变化无感 |
| 延迟体验 | 每按键等 RTT | 本地预测渲染,即时反馈 |
| 端口转发 | 支持 | 不支持 |
| Agent 转发 | 支持 | 不支持 |
学习路径
- 下一步 → 6.1:容器底层原理 容器底层原理:SSH 隧道是理解容器网络的基础
- 进阶 → 5.10:WireGuard VPN WireGuard VPN:对比 SSH 隧道与 VPN 的适用场景
- 实践 → 4.1:Ansible 自动化 Ansible 自动化运维:大规模自动化依赖证书认证 + ControlMaster 优化
- 安全 → 5.1:Linux 安全加固 Linux 安全加固:SSH 是最常见的攻击入口
延伸阅读
- 官方文档:
man ssh_config、man sshd_config、man ssh-keygen— OpenSSH 手册页是最权威的参考 - RFC 4251:SSH Protocol Architecture — 协议设计的完整技术细节
- 推荐章节:5.1:Linux 安全加固 Linux 安全加固、5.10:WireGuard VPN WireGuard VPN、4.1:Ansible 自动化 Ansible 自动化运维
- 推荐书籍:《SSH Mastery》by Michael W. Lucas — 最全面的 SSH 实践指南