SSH 服务器完全指南 / 第15章 最佳实践与规范
第15章 最佳实践与规范
15.1 生产环境 SSH 安全清单
部署 SSH 服务器到生产环境前,请逐项检查:
高优先级(必须)
| # | 检查项 | 标准 |
|---|
| 1 | 禁用密码认证 | PasswordAuthentication no |
| 2 | 禁止 root 密码登录 | PermitRootLogin prohibit-password 或 no |
| 3 | 使用强密钥类型 | Ed25519 或 RSA 4096 |
| 4 | 禁用空密码 | PermitEmptyPasswords no |
| 5 | 文件权限正确 | 私钥 600,.ssh 目录 700 |
| 6 | 强加密算法 | 只允许现代算法 |
| 7 | 限制认证尝试 | MaxAuthTries 3 |
| 8 | 服务端配置语法验证 | sshd -t 通过 |
中优先级(强烈推荐)
| # | 检查项 | 标准 |
|---|
| 9 | 安装 Fail2Ban | 配置自动封禁 |
| 10 | 限制用户/组访问 | AllowGroups 白名单 |
| 11 | 禁用不需要的功能 | X11/TCP 转发 |
| 12 | 启用心跳保活 | ClientAliveInterval 300 |
| 13 | 关闭 DNS 反向解析 | UseDNS no |
| 14 | 关闭 GSSAPI | GSSAPIAuthentication no |
| 15 | 日志级别 INFO | LogLevel INFO |
| 16 | 配置 Include 拆分 | 模块化管理 |
低优先级(推荐)
| # | 检查项 | 标准 |
|---|
| 17 | 使用 SSH 证书 | 集中化密钥管理 |
| 18 | 配置登录横幅 | Banner /etc/ssh/banner.txt |
| 19 | 审计日志 | auditd 集成 |
| 20 | 定期密钥轮换 | 自动化密钥更换 |
自动化检查脚本
#!/bin/bash
# ssh-security-audit.sh
CONFIG="/etc/ssh/sshd_config"
PASS=0
FAIL=0
WARN=0
check() {
local desc="$1"
local expected="$2"
local actual="$3"
if [ "$actual" = "$expected" ]; then
echo "✅ $desc"
((PASS++))
else
echo "❌ $desc (期望: $expected, 实际: $actual)"
((FAIL++))
fi
}
warn_check() {
local desc="$1"
local expected="$2"
local actual="$3"
if [ "$actual" = "$expected" ]; then
echo "✅ $desc"
((PASS++))
else
echo "⚠️ $desc (建议: $expected, 实际: $actual)"
((WARN++))
fi
}
echo "=============================="
echo " SSH 安全审计报告"
echo " 日期: $(date)"
echo " 服务器: $(hostname)"
echo "=============================="
echo ""
# 配置语法
echo "--- 配置检查 ---"
if sshd -t 2>/dev/null; then
check "配置语法" "正确" "正确"
else
check "配置语法" "正确" "错误"
fi
# 获取生效配置
get_conf() {
sshd -T 2>/dev/null | grep -i "^$1 " | awk '{print $2}'
}
# 认证检查
echo ""
echo "--- 认证安全 ---"
check "密码认证" "no" "$(get_conf passwordauthentication)"
check "空密码" "no" "$(get_conf permitemptypasswords)"
check "Root 密码登录" "prohibit-password|no" "$(get_conf permitrootlogin)"
check "公钥认证" "yes" "$(get_conf pubkeyauthentication)"
warn_check "最大认证尝试" "3" "$(get_conf maxauthtries)"
# 功能限制
echo ""
echo "--- 功能限制 ---"
warn_check "X11 转发" "no" "$(get_conf x11forwarding)"
warn_check "TCP 转发" "no" "$(get_conf allowtcpforwarding)"
# 网络配置
echo ""
echo "--- 网络配置 ---"
warn_check "DNS 反向解析" "no" "$(get_conf usedns)"
warn_check "GSSAPI" "no" "$(get_conf gssapiauthentication)"
warn_check "心跳保活" "300" "$(get_conf clientaliveinterval)"
# 日志
echo ""
echo "--- 日志配置 ---"
warn_check "日志级别" "INFO" "$(get_conf loglevel)"
# 密钥检查
echo ""
echo "--- 主机密钥 ---"
for key in /etc/ssh/ssh_host_*_key; do
[ -f "$key" ] || continue
PERM=$(stat -c %a "$key")
if [ "$PERM" = "600" ]; then
echo "✅ $key 权限正确 (600)"
((PASS++))
else
echo "❌ $key 权限错误 ($PERM)"
((FAIL++))
fi
done
# 检查弱密钥
if [ -f /etc/ssh/ssh_host_dsa_key ]; then
echo "❌ 发现 DSA 主机密钥(已不安全)"
((FAIL++))
fi
# 检查 Fail2Ban
echo ""
echo "--- Fail2Ban ---"
if systemctl is-active fail2ban &>/dev/null; then
check "Fail2Ban 状态" "active" "active"
else
warn_check "Fail2Ban 状态" "active" "inactive"
fi
# 总结
echo ""
echo "=============================="
echo " 审计结果"
echo " 通过: $PASS"
echo " 失败: $FAIL"
echo " 警告: $WARN"
echo "=============================="
if [ $FAIL -gt 0 ]; then
echo "⛔ 存在安全问题,请立即修复!"
exit 1
elif [ $WARN -gt 0 ]; then
echo "⚠️ 存在建议改进项"
exit 0
else
echo "🎉 所有检查通过!"
exit 0
fi
15.2 密钥生命周期管理
密钥生成规范
| 密钥用途 | 类型 | 位数 | 密码保护 | 存储位置 |
|---|
| 个人管理 | Ed25519 | 256 | ✅ 强密码 | ~/.ssh/ |
| CI/CD 部署 | Ed25519 | 256 | ❌ (自动化) | 密钥管理系统 |
| 服务账户 | Ed25519 | 256 | ❌ | 受限目录 |
| 证书 CA | Ed25519 | 256 | ✅ 强密码 | 离线/HSM |
| 兼容旧系统 | RSA | 4096 | ✅ 强密码 | ~/.ssh/ |
密钥轮换策略
# 密钥轮换周期建议
# ┌─────────────────┬──────────┐
# │ 密钥类型 │ 轮换周期 │
# ├─────────────────┼──────────┤
# │ 个人用户密钥 │ 每年 │
# │ 服务账户密钥 │ 每季度 │
# │ CI/CD 密钥 │ 每月 │
# │ 临时访问密钥 │ 每次 │
# │ SSH CA 密钥 │ 每 2 年 │
# └─────────────────┴──────────┘
自动化密钥轮换
#!/bin/bash
# rotate-ssh-keys.sh
SERVER="$1"
USER="${2:-deploy}"
KEY_DIR="$HOME/.ssh"
NEW_KEY="$KEY_DIR/id_ed25519_$(date +%Y%m%d)"
if [ -z "$SERVER" ]; then
echo "Usage: $0 <server> [user]"
exit 1
fi
echo "=== SSH 密钥轮换 ==="
echo "服务器: $SERVER"
echo "用户: $USER"
echo ""
# 1. 生成新密钥
echo "[1/5] 生成新密钥..."
ssh-keygen -t ed25519 -f "$NEW_KEY" -N "" -C "$USER@$(hostname)-$(date +%Y%m%d)" -q
# 2. 部署新密钥
echo "[2/5] 部署新密钥..."
ssh-copy-id -i "$NEW_KEY.pub" "$USER@$SERVER" 2>/dev/null
if [ $? -ne 0 ]; then
echo "❌ 部署失败"
rm -f "$NEW_KEY" "$NEW_KEY.pub"
exit 1
fi
# 3. 验证新密钥
echo "[3/5] 验证新密钥..."
ssh -i "$NEW_KEY" -o BatchMode=yes "$USER@$SERVER" "echo OK" 2>/dev/null
if [ $? -ne 0 ]; then
echo "❌ 新密钥验证失败"
exit 1
fi
# 4. 更新配置
echo "[4/5] 更新本地配置..."
if [ -f "$KEY_DIR/id_ed25519" ]; then
mv "$KEY_DIR/id_ed25519" "$KEY_DIR/id_ed25519.old.$(date +%Y%m%d)"
mv "$KEY_DIR/id_ed25519.pub" "$KEY_DIR/id_ed25519.pub.old.$(date +%Y%m%d)"
fi
mv "$NEW_KEY" "$KEY_DIR/id_ed25519"
mv "$NEW_KEY.pub" "$KEY_DIR/id_ed25519.pub"
# 5. 清理服务器上的旧密钥
echo "[5/5] 清理服务器旧密钥..."
echo "(保留旧密钥 7 天后删除)"
# 可以添加自动化清理逻辑
echo ""
echo "✅ 密钥轮换完成"
echo "旧密钥已备份为 id_ed25519.old.$(date +%Y%m%d)"
密钥清理
#!/bin/bash
# cleanup-old-keys.sh
KEY_DIR="$HOME/.ssh"
DAYS=30
echo "=== 清理 $DAYS 天前的旧密钥 ==="
find "$KEY_DIR" -name "*.old.*" -mtime +$DAYS -type f | while read file; do
echo "删除: $file"
rm -f "$file"
done
echo "清理完成"
15.3 审计日志规范
日志收集架构
[SSH 服务器] → [rsyslog/Filebeat] → [集中日志平台]
/var/log/auth.log ELK/Splunk/Graylog
配置集中日志
# /etc/rsyslog.d/ssh.conf
# 将 SSH 日志发送到远程服务器
auth,authpriv.* @logserver.example.com:514
# 或使用 TCP(更可靠)
auth,authpriv.* @@logserver.example.com:514
auditd SSH 审计规则
# /etc/audit/rules.d/ssh.rules
# 监控 SSH 配置文件变更
-w /etc/ssh/sshd_config -p wa -k ssh_config_change
-w /etc/ssh/sshd_config.d/ -p wa -k ssh_config_change
# 监控主机密钥
-w /etc/ssh/ssh_host_ -p wa -k ssh_hostkey_change
# 监控 authorized_keys
-w /home/ -p wa -k ssh_key_change -F dir=/home
-w /root/.ssh/authorized_keys -p wa -k ssh_key_change
# 监控 SSH 登录事件
-w /usr/sbin/sshd -p x -k ssh_daemon
# 查看审计日志
sudo ausearch -k ssh_config_change
sudo ausearch -k ssh_key_change
# 生成审计报告
sudo aureport --auth --summary
关键日志事件
| 事件 | 日志模式 | 优先级 |
|---|
| 认证成功 | Accepted publickey/password | INFO |
| 认证失败 | Failed password/publickey | WARNING |
| 无效用户 | Invalid user | WARNING |
| Root 登录 | Accepted publickey for root | NOTICE |
| 配置变更 | sshd_config 修改 | ALERT |
| 主机密钥变更 | 主机密钥修改 | ALERT |
| 服务启动/停止 | sshd 启动/停止 | INFO |
日志告警
#!/bin/bash
# ssh-alert.sh - SSH 安全告警
LOG="/var/log/auth.alert.log"
THRESHOLD=10 # 10 分钟内失败次数
WINDOW=600
# 检查失败登录
FAILURES=$(journalctl -u sshd --since "-${WINDOW} seconds" | \
grep "Failed password" | wc -l)
if [ $FAILURES -ge $THRESHOLD ]; then
TOP_IP=$(journalctl -u sshd --since "-${WINDOW} seconds" | \
grep "Failed password" | \
awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -1)
echo "⚠️ SSH 暴力破解告警" | tee -a "$LOG"
echo "时间: $(date)" | tee -a "$LOG"
echo "失败次数: $FAILURES (最近 ${WINDOW}秒)" | tee -a "$LOG"
echo "主要攻击源: $TOP_IP" | tee -a "$LOG"
# 发送告警(邮件/Slack/企业微信)
# mail -s "SSH Brute Force Alert" admin@example.com < "$LOG"
fi
15.4 运维规范
SSH 操作规范
| 规范 | 说明 |
|---|
| 禁止共享密钥 | 每人使用独立密钥 |
| 禁止密码认证 | 生产环境必须使用密钥 |
| 禁止 root 直登 | 使用普通用户 + sudo |
| 保持备用连接 | 修改配置时保持一个连接 |
| 记录操作日志 | 重要操作需要记录 |
| 使用跳板机 | 所有访问通过跳板机 |
| 密钥定期轮换 | 至少每年一次 |
| 离职立即撤销 | 员工离职当天撤销所有访问 |
SSH 配置管理规范
# 1. 版本控制
# 将 sshd_config 纳入 Git 管理
cd /etc/ssh
git init
git add sshd_config sshd_config.d/
git commit -m "Initial SSH config"
# 2. 变更流程
# a. 在测试环境验证
# b. 代码审查
# c. 灰度发布(先部分服务器)
# d. 全量发布
# e. 验证生效
# 3. 回滚准备
# 始终保留备份
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)
运维工具链
┌──────────────────────────────────────────────┐
│ SSH 运维工具链 │
├──────────────────────────────────────────────┤
│ │
│ 密钥管理 访问控制 审计日志 │
│ ├─ ssh-keygen ├─ 堡垒机 ├─ auditd │
│ ├─ SSH CA ├─ AllowGroups ├─ rsyslog │
│ ├─ Vault ├─ Match 块 ├─ ELK │
│ └─ 自动轮换 └─ 证书认证 └─ 告警 │
│ │
│ 自动化 安全防护 监控 │
│ ├─ Ansible ├─ Fail2Ban ├─ 端口监控 │
│ ├─ 批量脚本 ├─ iptables ├─ 连接数 │
│ └─ CI/CD └─ 端口敲门 └─ 认证日志 │
│ │
└──────────────────────────────────────────────┘
15.5 合规性要求
常见安全标准对 SSH 的要求
| 标准 | SSH 要求 |
|---|
| 等保 2.0 | 密码复杂度、登录失败锁定、访问控制、审计日志 |
| PCI DSS | 加密传输、密钥管理、访问日志、定期轮换 |
| ISO 27001 | 访问控制、密钥生命周期、安全配置 |
| CIS Benchmark | 具体的安全配置参数 |
CIS Benchmark 核心检查项
# CIS SSH 检查项(摘要)
# 1. 加密配置
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
# 2. 认证配置
MaxAuthTries 4
PermitEmptyPasswords no
PermitUserEnvironment no
# 3. 访问控制
PermitRootLogin no
AllowGroups ssh-users
Protocol 2
# 4. 日志
LogLevel INFO
15.6 生产环境推荐配置模板
# /etc/ssh/sshd_config
# ================================
# 生产环境 SSH 配置模板
# 版本: 2.0
# 更新日期: 2025-01-01
# ================================
# ===== 网络设置 =====
Port 22
AddressFamily inet
ListenAddress 0.0.0.0
# ===== 主机密钥 =====
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# ===== 加密算法 =====
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256
# ===== 认证设置 =====
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
MaxAuthTries 3
LoginGraceTime 60
AuthenticationMethods publickey
# ===== 用户访问控制 =====
PermitRootLogin no
AllowGroups ssh-users
DenyUsers root
MaxSessions 10
MaxStartups 10:30:60
# ===== 会话管理 =====
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive yes
# ===== 功能限制 =====
X11Forwarding no
AllowTcpForwarding no
PermitTunnel no
GatewayPorts no
AllowStreamLocalForwarding no
PermitUserEnvironment no
# ===== 日志 =====
SyslogFacility AUTH
LogLevel INFO
# ===== PAM =====
UsePAM yes
# ===== 性能 =====
UseDNS no
GSSAPIAuthentication no
# ===== 杂项 =====
PrintMotd no
PrintLastLog yes
Banner /etc/ssh/banner.txt
# ===== 子配置 =====
Include /etc/ssh/sshd_config.d/*.conf
# ===== 条件配置 =====
# 部署用户(CI/CD 专用)
Match User deploy
AllowGroups deploy
ForceCommand /opt/deploy/entry.sh
AllowTcpForwarding no
X11Forwarding no
PermitTTY no
PermitRootLogin no
# SFTP 用户
Match Group sftp-only
ChrootDirectory /home/%u
ForceCommand internal-sftp -l INFO
AllowTcpForwarding no
X11Forwarding no
PermitTTY no
PermitRootLogin no
# 管理员(仅从管理网段)
Match Group admins Hostaddress 10.0.100.0/24
AllowTcpForwarding local
X11Forwarding yes
MaxSessions 20
PermitRootLogin prohibit-password
15.7 持续改进
定期审计计划
| 频率 | 检查内容 |
|---|
| 每日 | 查看登录日志、Fail2Ban 状态 |
| 每周 | 检查活跃用户和密钥 |
| 每月 | 运行安全审计脚本、更新配置 |
| 每季度 | 密钥轮换、清理过期账户 |
| 每年 | 全面安全评估、更新安全策略 |
自动化监控
#!/bin/bash
# ssh-monitor-cron
# 放入 crontab
# */5 * * * * /opt/scripts/ssh-monitor-cron.sh
LOG="/var/log/ssh-monitor.log"
# 检查 SSH 服务状态
if ! systemctl is-active sshd &>/dev/null; then
echo "[CRITICAL] $(date) SSH 服务停止!" >> "$LOG"
systemctl restart sshd
fi
# 检查 Fail2Ban 状态
if ! systemctl is-active fail2ban &>/dev/null; then
echo "[WARNING] $(date) Fail2Ban 服务停止!" >> "$LOG"
systemctl restart fail2ban
fi
# 检查当前连接数
CONN=$(ss -tnp | grep :22 | wc -l)
if [ $CONN -gt 100 ]; then
echo "[WARNING] $(date) SSH 连接数过多: $CONN" >> "$LOG"
fi
# 检查磁盘空间(日志文件)
LOG_SIZE=$(du -sm /var/log/auth.log 2>/dev/null | awk '{print $1}')
if [ "${LOG_SIZE:-0}" -gt 1000 ]; then
echo "[WARNING] $(date) auth.log 文件过大: ${LOG_SIZE}MB" >> "$LOG"
fi
15.8 本章总结
SSH 安全加固核心原则
1. 最小权限原则
- 只开放必需的访问
- 使用 AllowGroups 限制用户
- 禁用不需要的功能
2. 纵深防御
- 防火墙 + SSH 配置 + Fail2Ban
- 密钥认证 + 多因素认证
- 跳板机 + 审计日志
3. 默认拒绝
- PasswordAuthentication no
- PermitRootLogin no
- AllowTcpForwarding no
4. 可审计性
- 集中日志收集
- 操作记录
- 定期审计
5. 自动化
- 配置管理(Ansible)
- 密钥轮换(脚本)
- 安全检查(CI/CD)
扩展阅读
🎉 恭喜你完成了 SSH 服务器完全指南的全部 15 章内容!
建议将本教程作为参考手册,在实际工作中按需查阅相关章节。
SSH 安全是一个持续改进的过程,定期审计和更新配置是保持安全的关键。