SSH 服务器完全指南 / 第11章 跳板机与堡垒机
第11章 跳板机与堡垒机
11.1 跳板机与堡垒机概念
跳板机(Jump Host)
通过中间服务器间接访问目标服务器,常用于网络隔离环境。
[运维人员] → [跳板机] → [内网服务器]
外网 DMZ/公网 内网
堡垒机(Bastion Host / PAM)
堡垒机在跳板机基础上增加了审计和访问控制功能:
[运维人员] → [堡垒机] → [内网服务器]
│
├── 身份认证
├── 权限控制
├── 会话录制
├── 命令审计
└── 告警通知
| 功能 | 跳板机 | 堡垒机 |
|---|---|---|
| 网络跳转 | ✅ | ✅ |
| 访问控制 | 基础 | 细粒度 |
| 会话审计 | ❌ | ✅ |
| 命令录制 | ❌ | ✅ |
| 工单集成 | ❌ | ✅ |
11.2 ProxyJump(推荐方式)
基本语法
# 方式一:-J 参数
ssh -J user@jumphost user@target
# 方式二:ProxyJump 配置
# ~/.ssh/config
Host target
ProxyJump user@jumphost
# 多跳
ssh -J user@jump1,user@jump2,user@jump3 user@target
工作原理
[本地] → [跳板机] → [目标服务器]
代理连接 最终目标
ProxyJump 在本地完成密钥认证,
只在跳板机上建立 TCP 转发。
配置文件示例
# ~/.ssh/config
# 跳板机
Host bastion
HostName bastion.example.com
User jumpuser
Port 22
IdentityFile ~/.ssh/id_bastion
# 内网服务器(通过跳板机)
Host internal-*
User admin
ProxyJump bastion
IdentityFile ~/.ssh/id_internal
# 具体内网服务器
Host internal-web
HostName 10.0.0.10
Host internal-db
HostName 10.0.0.20
Port 5432
Host internal-cache
HostName 10.0.0.30
使用:
# 直接使用别名,自动通过跳板机
ssh internal-web
ssh internal-db
ssh internal-cache
多层跳板
# ~/.ssh/config
Host dmz-jump
HostName dmz.example.com
User dmz-user
Host app-jump
HostName 10.0.1.100
User app-user
ProxyJump dmz-jump
Host db-server
HostName 10.0.2.50
User dba
ProxyJump dmz-jump,app-jump
文件传输通过跳板机
# scp 通过跳板机
scp -o ProxyJump=bastion localfile.txt user@internal:/tmp/
# rsync 通过跳板机
rsync -avz -e "ssh -J bastion" ./src/ user@internal:/dst/
# sftp 通过跳板机
sftp -o ProxyJump=bastion user@internal
11.3 ProxyCommand(旧方式)
ProxyCommand 是 ProxyJump 之前的方式,更灵活但配置更复杂:
# ~/.ssh/config
# 使用 netcat(推荐简单场景)
Host internal
HostName 10.0.0.10
ProxyCommand ssh bastion -W %h:%p
# 使用 netcat(如果跳板机没有 -W 支持)
Host internal
ProxyCommand ssh bastion nc %h %p 2>/dev/null
# 使用 socat
Host internal
ProxyCommand ssh bastion socat - TCP:%h:%p
ProxyCommand 变量
| 变量 | 说明 |
|---|---|
%h | 目标主机名 |
%p | 目标端口 |
%r | 远程用户名 |
%l | 本地主机名 |
ProxyCommand vs ProxyJump
| 特性 | ProxyJump | ProxyCommand |
|---|---|---|
| 语法 | 简单 | 复杂 |
| 性能 | 更好(原生支持) | 稍差 |
| 灵活性 | 跳转场景 | 任意代理命令 |
| 推荐 | ⭐⭐⭐ | ⭐⭐(兼容旧版本) |
11.4 堡垒机配置
搭建简易堡垒机
步骤一:配置堡垒机 sshd
# /etc/ssh/sshd_config (堡垒机)
# 基本设置
Port 22
Protocol 2
UsePAM yes
# 认证
PubkeyAuthentication yes
PasswordAuthentication no
MaxAuthTries 3
LoginGraceTime 60
# 会话记录
ForceCommand /usr/local/bin/bastion-shell.sh
# 功能限制
AllowTcpForwarding local
X11Forwarding no
PermitTunnel no
GatewayPorts no
# 日志
LogLevel VERBOSE
SyslogFacility AUTH
步骤二:创建堡垒机 Shell
#!/bin/bash
# /usr/local/bin/bastion-shell.sh
# 记录会话信息
LOG_DIR="/var/log/bastion"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/session-$(whoami)-$(date +%Y%m%d-%H%M%S).log"
# 记录连接信息
echo "=== Session Start ===" >> "$LOG_FILE"
echo "User: $(whoami)" >> "$LOG_FILE"
echo "From: $SSH_CLIENT" >> "$LOG_FILE"
echo "Date: $(date)" >> "$LOG_FILE"
echo "Command: $SSH_ORIGINAL_COMMAND" >> "$LOG_FILE"
echo "========================" >> "$LOG_FILE"
if [ -n "$SSH_ORIGINAL_COMMAND" ]; then
# 执行转发的命令
echo "Executing: $SSH_ORIGINAL_COMMAND" >> "$LOG_FILE"
eval "$SSH_ORIGINAL_COMMAND" 2>&1 | tee -a "$LOG_FILE"
else
# 交互式会话
exec script -q -f -a "$LOG_FILE"
fi
echo "=== Session End ===" >> "$LOG_FILE"
# 设置权限
sudo chmod 755 /usr/local/bin/bastion-shell.sh
步骤三:会话录制
# 使用 script 命令录制终端会话
# 已在 bastion-shell.sh 中实现
# 使用 ttyrec 录制(支持回放)
sudo apt install ttyrec
# 录制
ttyrec /var/log/bastion/session-$USER-$(date +%s).ttyrec
# 回放
ttyplay /var/log/bastion/session-xxx.ttyrec
11.5 集中化访问控制
使用 authorized_keys 限制跳板机用户
# 堡垒机上 ~/.ssh/authorized_keys
# 只允许通过跳板机连接内网服务器
from="10.0.0.0/8",command="/usr/local/bin/bastion-shell.sh",permitopen="10.0.0.0/8:22",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3... admin@workstation
使用 SSH 证书 + 堡垒机
# 1. 所有用户向 CA 申请证书
# 2. 堡垒机信任 CA
# 3. 内网服务器也信任 CA
# 用户证书
ssh-keygen -s ca_user_key -I "admin-bastion" -n "admin" -V "+8h" id_work.pub
# 堡垒机配置
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
# 内网服务器配置
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
11.6 自动化堡垒机部署
Ansible 部署
# deploy-bastion.yml
---
- name: Deploy bastion host
hosts: bastion
become: true
vars:
bastion_users:
- admin
- operator1
- operator2
tasks:
- name: Install required packages
apt:
name:
- openssh-server
- auditd
- script
state: present
- name: Configure sshd
template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
validate: "sshd -t -f %s"
notify: restart sshd
- name: Create bastion shell script
template:
src: bastion-shell.sh.j2
dest: /usr/local/bin/bastion-shell.sh
mode: '0755'
- name: Create bastion users
user:
name: "{{ item }}"
shell: /usr/local/bin/bastion-shell.sh
groups: bastion
append: yes
loop: "{{ bastion_users }}"
- name: Deploy CA public key
copy:
src: ca_user_key.pub
dest: /etc/ssh/ca_user_key.pub
mode: '0644'
- name: Configure auditd rules
copy:
content: |
-w /usr/local/bin/bastion-shell.sh -p x -k bastion-shell
-w /var/log/bastion -p wa -k bastion-log
dest: /etc/audit/rules.d/bastion.rules
notify: restart auditd
handlers:
- name: restart sshd
systemd:
name: sshd
state: restarted
- name: restart auditd
systemd:
name: auditd
state: restarted
11.7 多人共享跳板机管理
问题分析
多人共享跳板机时的常见问题:
| 问题 | 风险 |
|---|---|
| 共享 root 密码 | 无法追踪个人操作 |
| 共享 SSH 密钥 | 密钥泄露影响所有人 |
| 无操作审计 | 出了问题无法追溯 |
| 无权限隔离 | 所有人权限相同 |
解决方案
# 1. 每个人使用独立账户
# 2. 使用 sudo 限制权限
# 3. 使用 SSH 证书控制访问
# 4. 启用会话录制和审计
# /etc/sudoers.d/bastion-operators
# 运维人员可以 sudo 到目标用户
operator1 ALL=(appuser) NOPASSWD: /usr/bin/ssh internal-*
operator2 ALL=(appuser) NOPASSWD: /usr/bin/ssh internal-*
11.8 场景实战
场景一:通过跳板机连接数据库
# ~/.ssh/config
Host db-jump
HostName bastion.example.com
User dba
Host internal-mysql
HostName 10.0.0.50
User dba
ProxyJump db-jump
LocalForward 3306 localhost:3306
RequestTTY no
# 使用
ssh internal-mysql -fN
mysql -h 127.0.0.1 -P 3306 -u root -p
场景二:Ansible 通过跳板机管理内网
# ansible.cfg
[ssh_connection]
ssh_args = -o ProxyJump=[email protected] -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
# inventory
[internal]
web01 ansible_host=10.0.0.10
web02 ansible_host=10.0.0.11
db01 ansible_host=10.0.0.20
场景三:Git 通过跳板机
# ~/.ssh/config
Host github.com
HostName github.com
User git
ProxyCommand ssh bastion -W %h:%p
IdentityFile ~/.ssh/id_github
扩展阅读
下一章: 第12章 自动化运维集成 → 学习 sshpass、expect、Ansible 等自动化工具。