SSH安全加固与免密登录:企业级SSH管理方案

关注「Raymond运维」公众号,并设为「星标」,也可以扫描底部二维码加入群聊,第一时间获取最新内容,不再错过精彩内容。
一、概述
1.1 背景介绍
线上服务器被暴力破解SSH密码的事每个月都在发生。我们团队去年处理过一起安全事件,一台测试机用了默认22端口加弱密码,48小时内被植入挖矿程序,CPU跑满导致同网段业务受影响。事后复盘发现 /var/log/secure 里有超过20万次失败登录记录,全是字典攻击。
SSH是Linux服务器远程管理的核心通道,OpenSSH默认配置偏向兼容性而非安全性——允许root登录、允许密码认证、监听22端口。这些默认值在公网环境下等于敞开大门。生产环境必须做SSH加固,这不是可选项,是基线要求。
本篇覆盖端口修改、认证方式切换、密钥管理、fail2ban防护、证书认证等完整加固链路,所有配置均在CentOS 7/8/9和Ubuntu 20.04/22.04上线上验证过。
1.2 技术特点
-
基于非对称加密的身份认证:SSH密钥登录使用公私钥对,私钥不离开客户端,服务端只存公钥。即使服务端被入侵,攻击者拿到的公钥无法反推私钥。ed25519算法密钥长度仅68字节,比RSA-4096的800+字节短得多,签名验证速度快约30%。 -
支持多种认证方式灵活组合:密码认证、公钥认证、证书认证、GSSAPI认证、键盘交互认证,可以通过 AuthenticationMethods指令组合使用。比如要求”公钥+密码”双因素,配置为AuthenticationMethods publickey,password。 -
细粒度访问控制:通过 AllowUsers、AllowGroups、DenyUsers、DenyGroups四个指令控制谁能登录,配合Match块可以针对特定用户、IP、端口设置不同策略。比如允许运维组从跳板机登录,禁止其他所有来源。
1.3 适用场景
-
场景一:公网服务器加固。云主机直接暴露在公网,每天承受大量扫描和暴力破解。实测一台新开的阿里云ECS,开机2小时内就有来自全球的SSH登录尝试。必须改端口+禁密码+上fail2ban三件套。 -
场景二:多人运维团队密钥管理。团队10+人需要登录上百台服务器,用密码管理不现实。通过SSH密钥+跳板机+ProxyJump实现统一入口管理,人员离职时只需在跳板机删除公钥。 -
场景三:自动化运维免密通道。Ansible、SaltStack等自动化工具依赖SSH免密登录批量执行命令。CI/CD流水线部署也需要SSH免密推送代码到目标机器。密钥认证是自动化的基础。
1.4 环境要求
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
二、详细步骤
2.1 准备工作
2.1.1 系统检查
# 检查系统版本cat /etc/os-release# 检查当前SSH版本,低于7.4的建议升级ssh -V# 检查SSH服务状态systemctl status sshd# 检查当前SSH监听端口和连接数ss -tlnp | grep ssh# 检查当前登录的SSH会话,确认自己的连接信息who -u# 查看最近的SSH登录失败记录,评估当前风险# CentOS/RHELgrep "Failed password" /var/log/secure | tail -20# Ubuntu/Debiangrep "Failed password" /var/log/auth.log | tail -20
重要提醒:修改SSH配置前,务必保持当前SSH会话不断开,同时开一个新终端测试。配置改错了当前会话还能用来恢复,断开就只能去机房或用VNC了。我们团队的规矩是改SSH配置必须两人操作,一人改一人保持连接。
2.1.2 安装依赖
# CentOS/RHEL 安装sudo yum install -y epel-releasesudo yum install -y fail2ban fail2ban-systemd openssh-server openssh-clients# Ubuntu/Debian 安装sudo apt updatesudo apt install -y fail2ban openssh-server openssh-client# 确认fail2ban版本fail2ban-client --version
2.1.3 备份原始配置
# 备份SSH配置,带日期方便回溯sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)# 备份PAM相关SSH配置sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.bak.$(date +%Y%m%d)# 验证备份ls -la /etc/ssh/sshd_config.bak.*
2.2 核心配置
2.2.1 修改SSH监听端口
默认22端口是所有扫描器的第一目标。改成高位端口不能防住定向攻击,但能过滤掉99%的自动化扫描。实测改端口后,/var/log/secure 里的失败登录记录从每天几万条降到个位数。
# 编辑SSH配置文件sudo vim /etc/ssh/sshd_config# 找到 #Port 22,改为:Port 52222
SELinux环境额外操作(CentOS/RHEL默认开启SELinux):
# 检查SELinux状态getenforce# 如果是Enforcing,需要添加端口到SELinux策略sudo semanage port -a -t ssh_port_t -p tcp 52222# 验证端口已添加sudo semanage port -l | grep ssh# 输出应包含:ssh_port_t tcp 52222, 22
防火墙放行新端口:
# firewalld(CentOS 7+)sudo firewall-cmd --permanent --add-port=52222/tcpsudo firewall-cmd --reloadsudo firewall-cmd --list-ports# 或者iptables(旧系统)sudo iptables -A INPUT -p tcp --dport 52222 -j ACCEPTsudo iptables -D INPUT -p tcp --dport 22 -j ACCEPTsudo service iptables save# Ubuntu UFWsudo ufw allow 52222/tcpsudo ufw status
这个地方有个坑:先放行新端口再改配置重启sshd,顺序反了会把自己锁在外面。
2.2.2 禁用Root直接登录
root账户是暴力破解的首要目标,因为攻击者知道每台Linux都有root用户,只需要猜密码。禁用root登录后,攻击者还得猜用户名,难度指数级上升。
# /etc/ssh/sshd_config 中修改PermitRootLogin no
配套操作:确保有一个sudo权限的普通用户可用。
# 创建运维用户sudo useradd -m -s /bin/bash opsadminsudo passwd opsadmin# 加入wheel组(CentOS)或sudo组(Ubuntu)# CentOSsudo usermod -aG wheel opsadmin# Ubuntusudo usermod -aG sudo opsadmin# 验证sudo权限su - opsadminsudo whoami# 输出应为 root
2.2.3 禁用密码认证,仅允许密钥登录
密码认证的问题:再复杂的密码也可能被社工、钓鱼、撞库搞到。密钥认证从原理上杜绝了暴力破解——私钥文件不在网络上传输,服务端只做签名验证。
# /etc/ssh/sshd_config 中修改PasswordAuthentication noChallengeResponseAuthentication noUsePAM yesPubkeyAuthentication yes
这个参数改错了会导致所有用密码登录的人立刻无法连接,改之前确认密钥登录已经配好并测试通过。我见过不止一次有人先禁密码后配密钥,结果把自己锁在外面。
2.2.4 配置访问白名单
# /etc/ssh/sshd_config 中添加# 只允许特定用户登录AllowUsers opsadmin deployer monitor# 或者只允许特定组登录(二选一,不要同时配)# AllowGroups sshusers ops-team
说明:AllowUsers 和 AllowGroups 是白名单机制,配置后不在名单里的用户全部拒绝。如果用 AllowUsers,新增运维人员时记得加到这个列表里,否则加了账号也登不上。我们团队的做法是用 AllowGroups sshusers,新人加入sshusers组就行,不用每次改sshd_config。
# 创建SSH用户组sudo groupadd sshusers# 将允许登录的用户加入组sudo usermod -aG sshusers opsadminsudo usermod -aG sshusers deployer
2.2.5 设置登录超时和重试限制
# /etc/ssh/sshd_config 中修改LoginGraceTime 30MaxAuthTries 3MaxSessions 5MaxStartups 10:30:60ClientAliveInterval 300ClientAliveCountMax 3
参数说明:
-
LoginGraceTime 30:用户30秒内必须完成认证,否则断开。默认120秒太长了。 -
MaxAuthTries 3:单次连接最多尝试3次认证。超过后断开连接,配合fail2ban效果更好。 -
MaxSessions 5:单个连接最多5个会话复用。 -
MaxStartups 10:30:60:当未认证连接数达到10个时,以30%概率拒绝新连接;达到60个时100%拒绝。防止连接洪水攻击。 -
ClientAliveInterval 300:每300秒(5分钟)发一次心跳探测。 -
ClientAliveCountMax 3:连续3次心跳无响应则断开,即15分钟无响应自动断开。
2.2.6 SSH密钥对生成
# 推荐使用ed25519算法# ed25519比RSA-4096更安全,密钥更短,签名更快ssh-keygen -t ed25519 -C "opsadmin@company.com" -f ~/.ssh/id_ed25519# 如果目标系统OpenSSH版本低于6.5,不支持ed25519,退而求其次用RSA-4096ssh-keygen -t rsa -b 4096 -C "opsadmin@company.com" -f ~/.ssh/id_rsa# 查看生成的密钥ls -la ~/.ssh/# id_ed25519 私钥文件,权限必须是600# id_ed25519.pub 公钥文件,权限644即可
关于密钥密码(passphrase):
-
生产环境的个人密钥建议设置passphrase,防止私钥文件泄露后被直接使用 -
自动化场景(Ansible、CI/CD)的密钥不设passphrase,否则每次执行都要输密码 -
设了passphrase的密钥可以用ssh-agent缓存,避免反复输入
# 启动ssh-agent并添加密钥eval $(ssh-agent -s)ssh-add ~/.ssh/id_ed25519# 输入passphrase后,当前会话内不再需要重复输入# 查看已加载的密钥ssh-add -l
2.2.7 免密登录配置
# 方法一:ssh-copy-id(推荐,自动处理权限)ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 52222 opsadmin@192.168.1.100# 方法二:手动复制(ssh-copy-id不可用时)cat ~/.ssh/id_ed25519.pub | ssh -p 52222 opsadmin@192.168.1.100 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"# 测试免密登录ssh -p 52222 opsadmin@192.168.1.100# 如果登录失败,用verbose模式排查ssh -vvv -p 52222 opsadmin@192.168.1.100
权限要求(这个是最常见的坑):
# 服务端权限必须严格设置,多一个权限都不行chmod 700 ~/.sshchmod 600 ~/.ssh/authorized_keyschmod 600 ~/.ssh/id_ed25519chmod 644 ~/.ssh/id_ed25519.pub# 家目录权限不能大于755chmod 755 ~# 检查文件属主ls -la ~/.ssh/# 所有文件的owner必须是当前用户,不能是root
StrictModes yes(默认开启)会检查这些权限,权限不对直接拒绝密钥认证,而且日志里只写 Authentication refused: bad ownership or modes,不告诉你具体哪个文件有问题。
2.2.8 SSH Config配置多主机管理
管理几十上百台服务器时,记IP和端口不现实。SSH Config文件可以给每台机器起别名,配置不同的连接参数。
# 编辑客户端配置文件vim ~/.ssh/config
# 全局默认配置Host * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes IdentitiesOnly yes Compression yes# 跳板机Host jump HostName 203.0.113.10 Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519# 通过跳板机访问内网Web服务器Host web-prod-01 HostName 10.0.1.11 Port 22 User deployer IdentityFile ~/.ssh/id_ed25519 ProxyJump jumpHost web-prod-02 HostName 10.0.1.12 Port 22 User deployer IdentityFile ~/.ssh/id_ed25519 ProxyJump jump# 数据库服务器,限制只用特定密钥Host db-prod-* Port 22 User dbadmin IdentityFile ~/.ssh/id_ed25519_db ProxyJump jumpHost db-prod-01 HostName 10.0.2.21Host db-prod-02 HostName 10.0.2.22# 测试环境,直连Host test-* Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519Host test-web-01 HostName 192.168.100.11Host test-db-01 HostName 192.168.100.21
# 配置好后直接用别名连接ssh web-prod-01ssh db-prod-01# scp也能用别名scp app.jar web-prod-01:/opt/app/# rsync同样支持rsync -avz ./dist/ web-prod-01:/var/www/html/
说明:IdentitiesOnly yes 这个参数很关键。不加的话ssh会把 ~/.ssh/ 下所有密钥都试一遍,如果密钥多了,试到第3个还没成功就会被 MaxAuthTries 3 拦住,报 Too many authentication failures。加了这个参数后只用指定的密钥文件。
2.2.9 配置fail2ban防暴力破解
fail2ban监控SSH日志,发现短时间内多次登录失败就自动封禁IP。实测效果:部署后暴力破解尝试从每天5万+降到0(因为攻击IP在第5次尝试后就被ban了)。
# 创建SSH专用的fail2ban配置sudo vim /etc/fail2ban/jail.local
[DEFAULT]# 封禁时间3600秒(1小时),惯犯会递增bantime = 3600# 在600秒(10分钟)内findtime = 600# 失败5次就封禁maxretry = 5# 封禁动作:firewalld或iptablesbanaction = firewallcmd-ipset# 如果用iptables,改为:# banaction = iptables-multiport# 忽略的IP(运维跳板机IP,防止把自己封了)ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.1.0/24[sshd]enabled = trueport = 52222filter = sshdlogpath = /var/log/secure# Ubuntu用这个路径:# logpath = /var/log/auth.logmaxretry = 3bantime = 7200findtime = 300
# 启动fail2bansudo systemctl start fail2bansudo systemctl enable fail2ban# 查看SSH jail状态sudo fail2ban-client status sshd# 输出示例:# Status for the jail: sshd# |- Filter# | |- Currently failed: 2# | |- Total failed: 156# | `- File list: /var/log/secure# `- Actions# |- Currently banned: 3# |- Total banned: 47# `- Banned IP list: 185.234.xx.xx 103.145.xx.xx 45.148.xx.xx# 手动解封某个IP(比如同事输错密码被封了)sudo fail2ban-client set sshd unbanip 192.168.1.50# 手动封禁某个IPsudo fail2ban-client set sshd banip 1.2.3.4
2.2.10 SSH证书认证(CA签发方式)
密钥认证的问题:每台服务器的 authorized_keys 都要维护,100台服务器就是100份。人员变动时要逐台删除。SSH证书认证用CA统一签发,服务端只信任CA,不需要维护每台机器的authorized_keys。
# 1. 生成CA密钥对(在CA服务器上操作,通常是跳板机)ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "SSH User CA"# 2. 将CA公钥分发到所有服务器# 在每台服务器的 /etc/ssh/sshd_config 中添加:TrustedUserCAKeys /etc/ssh/ca_user_key.pub# 3. 把CA公钥复制到服务器sudo scp /etc/ssh/ca_user_key.pub target-server:/etc/ssh/ca_user_key.pub# 4. 为用户签发证书# -s 签发者标识# -I 证书ID(用于审计日志)# -n 允许登录的用户名列表# -V 有效期(+52w表示52周)ssh-keygen -s /etc/ssh/ca_user_key \ -I "opsadmin-cert-20250101" \ -n opsadmin,deployer \ -V +52w \ /home/opsadmin/.ssh/id_ed25519.pub# 生成的证书文件:/home/opsadmin/.ssh/id_ed25519-cert.pub# 5. 查看证书信息ssh-keygen -L -f /home/opsadmin/.ssh/id_ed25519-cert.pub# 6. 用户使用证书登录(自动识别,无需额外配置)ssh -p 52222 opsadmin@192.168.1.100
证书吊销:
# 生成吊销列表ssh-keygen -k -f /etc/ssh/revoked_keys -s /etc/ssh/ca_user_key /path/to/revoked_cert.pub# 在sshd_config中配置吊销列表RevokedKeys /etc/ssh/revoked_keys# 重载配置sudo systemctl reload sshd
2.3 启动和验证
2.3.1 配置检查和重载
# 检查配置文件语法(改完必做,语法错误会导致sshd无法启动)sudo sshd -t# 没有输出表示语法正确,有错误会显示具体行号# 用调试模式检查配置sudo sshd -T | head -50# 重载配置(不断开现有连接)sudo systemctl reload sshd# 如果reload不生效,再restart(会断开所有连接)# sudo systemctl restart sshd# 确认服务状态sudo systemctl status sshd
2.3.2 功能验证
# 1. 验证端口变更(新开终端测试,不要断开当前连接)ssh -p 52222 opsadmin@服务器IP# 2. 验证root登录已禁用ssh -p 52222 root@服务器IP# 预期输出:Permission denied (publickey).# 3. 验证密码登录已禁用ssh -p 52222 -o PubkeyAuthentication=no opsadmin@服务器IP# 预期输出:Permission denied (publickey).# 4. 验证密钥登录正常ssh -p 52222 -i ~/.ssh/id_ed25519 opsadmin@服务器IP# 预期:直接登录成功# 5. 验证fail2ban工作# 故意用错误密码尝试几次(在测试环境操作)sudo fail2ban-client status sshd# 6. 验证端口监听ss -tlnp | grep 52222# 预期输出:LISTEN 0 128 *:52222 *:* users:(("sshd",pid=xxxx,fd=3))
2.3.3 回滚方案
如果加固后出现问题,按以下步骤回滚:
# 恢复备份的配置sudo cp /etc/ssh/sshd_config.bak.$(date +%Y%m%d) /etc/ssh/sshd_config# 重启SSH服务sudo systemctl restart sshd# 如果SSH完全无法连接,通过以下方式恢复:# 1. 云服务器:通过控制台VNC登录# 2. 物理机:接显示器键盘直接操作# 3. 如果有IPMI/iLO/iDRAC远程管理卡,通过带外管理登录
三、示例代码和配置
3.1 完整配置示例
3.1.1 生产级sshd_config完整配置
这份配置在我们团队管理的300+台CentOS 7/8和Ubuntu 20.04/22.04服务器上跑了两年多,没出过认证相关的事故。每一行都有注释说明为什么这么配。
# 文件路径:/etc/ssh/sshd_config# 最后修改:2025-01-15# 说明:生产环境SSH加固配置# ============================================# 网络和协议# ============================================# 监听端口,改掉默认22Port 52222# 只监听IPv4,如果不用IPv6就关掉,减少攻击面AddressFamily inet# 绑定特定IP(多网卡服务器建议绑内网IP)# 如果只允许从内网跳板机连接:# ListenAddress 10.0.0.100# 如果需要公网访问:ListenAddress 0.0.0.0# 协议版本,只用2(OpenSSH 7.4+已经默认只支持2)Protocol 2# ============================================# 主机密钥# ============================================HostKey /etc/ssh/ssh_host_ed25519_keyHostKey /etc/ssh/ssh_host_rsa_key# 不用DSA和ECDSA# HostKey /etc/ssh/ssh_host_dsa_key# HostKey /etc/ssh/ssh_host_ecdsa_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,aes192-ctr,aes128-ctr# MAC算法MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com# ============================================# 认证配置# ============================================# 禁止root登录PermitRootLogin no# 启用公钥认证PubkeyAuthentication yesAuthorizedKeysFile .ssh/authorized_keys# 禁用密码认证PasswordAuthentication noPermitEmptyPasswords no# 禁用质询响应认证ChallengeResponseAuthentication no# 禁用基于主机的认证HostbasedAuthentication noIgnoreRhosts yes# 禁用GSSAPI(不用Kerberos就关掉,开着会导致连接慢)GSSAPIAuthentication noGSSAPICleanupCredentials no# 禁用X11转发(服务器不需要图形界面)X11Forwarding no# 禁用TCP转发(如果不需要SSH隧道)# AllowTcpForwarding no# 如果需要SSH隧道做端口转发,保持默认yesAllowTcpForwarding yes# 禁用Agent转发(除非明确需要)AllowAgentForwarding no# 关闭DNS反向解析(开着会导致连接慢2-5秒)UseDNS no# 使用PAMUsePAM yes# ============================================# 访问控制# ============================================# 只允许sshusers组的用户登录AllowGroups sshusers# 或者指定用户白名单(和AllowGroups二选一)# AllowUsers opsadmin deployer monitor# ============================================# 会话控制# ============================================# 认证超时30秒LoginGraceTime 30# 最大认证尝试次数MaxAuthTries 3# 最大会话数MaxSessions 5# 未认证连接限制MaxStartups 10:30:60# 客户端存活检测ClientAliveInterval 300ClientAliveCountMax 3# ============================================# 日志# ============================================SyslogFacility AUTHLogLevel VERBOSE# ============================================# 登录Banner# ============================================Banner /etc/ssh/banner.txtPrintMotd noPrintLastLog yes# ============================================# SFTP配置# ============================================Subsystem sftp /usr/libexec/openssh/sftp-server -l INFO -f AUTH# ============================================# 证书认证(可选)# ============================================# TrustedUserCAKeys /etc/ssh/ca_user_key.pub# RevokedKeys /etc/ssh/revoked_keys# ============================================# Match块:针对特定用户/组的特殊配置# ============================================# SFTP专用用户,限制在家目录Match Group sftponly ChrootDirectory /data/sftp/%u ForceCommand internal-sftp AllowTcpForwarding no X11Forwarding no PermitTunnel no# 部署用户,只允许从CI/CD服务器连接Match User deployer Address 10.0.0.50 AllowTcpForwarding no PermitOpen none
登录Banner文件:
# 文件路径:/etc/ssh/banner.txtcat > /etc/ssh/banner.txt << 'EOF'********************************************************************** WARNING: This system is for authorized users only. ** All activities on this system are logged and monitored. ** Unauthorized access will be prosecuted to the full extent of law.**********************************************************************EOF
3.1.2 批量分发SSH密钥脚本
管理几十台服务器时,手动一台台ssh-copy-id太慢。这个脚本批量分发公钥,支持密码认证(首次部署时用)和已有密钥认证两种模式。
#!/bin/bash# 文件名:distribute_ssh_keys.sh# 功能:批量分发SSH公钥到多台服务器# 依赖:sshpass(首次用密码分发时需要)# 用法:./distribute_ssh_keys.sh hosts.txtset -euo pipefail# ========== 配置区 ==========SSH_PORT=52222SSH_USER="opsadmin"PUB_KEY_FILE="$HOME/.ssh/id_ed25519.pub"LOG_FILE="/tmp/ssh_key_distribute_$(date +%Y%m%d_%H%M%S).log"TIMEOUT=10# =============================# 颜色输出RED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'NC='\033[0m'log() {local level=$1shiftlocal msg="[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"echo -e "$msg" | tee -a "$LOG_FILE"}usage() {echo"用法: $0 <主机列表文件>"echo""echo"主机列表文件格式(每行一个IP):"echo"192.168.1.101"echo"192.168.1.102"echo"10.0.1.11"exit 1}# 参数检查if [[ $# -ne 1 ]]; then usagefiHOST_FILE=$1if [[ ! -f "$HOST_FILE" ]]; thenlog"ERROR""主机列表文件不存在: $HOST_FILE"exit 1fiif [[ ! -f "$PUB_KEY_FILE" ]]; thenlog"ERROR""公钥文件不存在: $PUB_KEY_FILE"log"INFO""请先生成密钥: ssh-keygen -t ed25519"exit 1fi# 检查sshpass是否安装USE_PASSWORD=falseifcommand -v sshpass &>/dev/null; thenread -sp "输入SSH密码(如果目标机器已配置密钥登录,直接回车跳过): " SSH_PASSechoif [[ -n "$SSH_PASS" ]]; then USE_PASSWORD=truefifi# 统计TOTAL=0SUCCESS=0FAILED=0log"INFO""开始分发SSH公钥"log"INFO""公钥文件: $PUB_KEY_FILE"log"INFO""目标用户: $SSH_USER"log"INFO""SSH端口: $SSH_PORT"while IFS= read -r host; do# 跳过空行和注释 [[ -z "$host" || "$host" =~ ^# ]] && continue TOTAL=$((TOTAL + 1))log"INFO""[$TOTAL] 正在处理: $host"if$USE_PASSWORD; then# 使用密码分发if sshpass -p "$SSH_PASS" ssh-copy-id \ -i "$PUB_KEY_FILE" \ -p "$SSH_PORT" \ -o StrictHostKeyChecking=no \ -o ConnectTimeout=$TIMEOUT \"${SSH_USER}@${host}" 2>>"$LOG_FILE"; thenlog"INFO""${GREEN}成功${NC}: $host" SUCCESS=$((SUCCESS + 1))elselog"ERROR""${RED}失败${NC}: $host" FAILED=$((FAILED + 1))fielse# 使用已有密钥分发新密钥if ssh-copy-id \ -i "$PUB_KEY_FILE" \ -p "$SSH_PORT" \ -o StrictHostKeyChecking=no \ -o ConnectTimeout=$TIMEOUT \"${SSH_USER}@${host}" 2>>"$LOG_FILE"; thenlog"INFO""${GREEN}成功${NC}: $host" SUCCESS=$((SUCCESS + 1))elselog"ERROR""${RED}失败${NC}: $host" FAILED=$((FAILED + 1))fifidone < "$HOST_FILE"log"INFO""========== 分发完成 =========="log"INFO""总计: $TOTAL 成功: $SUCCESS 失败: $FAILED"log"INFO""详细日志: $LOG_FILE"if [[ $FAILED -gt 0 ]]; thenlog"WARN""有 $FAILED 台服务器分发失败,请检查日志"exit 1fi
# 使用方法chmod +x distribute_ssh_keys.sh# 准备主机列表cat > hosts.txt << 'EOF'192.168.1.101192.168.1.102192.168.1.10310.0.1.1110.0.1.12EOF# 执行分发./distribute_ssh_keys.sh hosts.txt
3.2 实际应用案例
案例一:基于跳板机的SSH ProxyJump多层跳转
场景描述:生产环境网络架构分三层——公网跳板机、DMZ区应用服务器、内网数据库服务器。运维人员从办公网络连接跳板机,再跳转到内网服务器。数据库服务器只允许从应用服务器网段访问。
网络拓扑:
办公网络(172.16.0.0/16) | v跳板机(公网: 203.0.113.10, 内网: 10.0.0.1) | v应用服务器(10.0.1.0/24) | v数据库服务器(10.0.2.0/24)
SSH Config配置:
# ~/.ssh/config# 跳板机(一跳)Host jump HostName 203.0.113.10 Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519# 跳板机上开启Agent转发,用于二次跳转 ForwardAgent yes# 应用服务器(二跳,通过跳板机)Host app-01 HostName 10.0.1.11 User deployer IdentityFile ~/.ssh/id_ed25519 ProxyJump jumpHost app-02 HostName 10.0.1.12 User deployer IdentityFile ~/.ssh/id_ed25519 ProxyJump jump# 数据库服务器(三跳,通过应用服务器)Host db-master HostName 10.0.2.21 User dbadmin IdentityFile ~/.ssh/id_ed25519_db ProxyJump app-01Host db-slave HostName 10.0.2.22 User dbadmin IdentityFile ~/.ssh/id_ed25519_db ProxyJump app-01
使用效果:
# 直接连接数据库服务器,SSH自动完成两次跳转ssh db-master# 实际路径:本机 -> jump(203.0.113.10) -> app-01(10.0.1.11) -> db-master(10.0.2.21)# 通过跳板机做端口转发,本地访问远程数据库ssh -L 3307:10.0.2.21:3306 app-01# 然后本地用 mysql -h 127.0.0.1 -P 3307 连接# 通过跳板机传文件到内网服务器scp backup.sql db-master:/tmp/
案例二:多环境SSH Config管理与自动切换
场景描述:团队管理开发、测试、预发布、生产四套环境,共200+台服务器。不同环境用不同的密钥和用户,需要一套清晰的管理方案。
目录结构:
~/.ssh/├── config # 主配置文件,include其他配置├── config.d/│ ├── 00-defaults.conf # 全局默认配置│ ├── 10-dev.conf # 开发环境│ ├── 20-test.conf # 测试环境│ ├── 30-staging.conf # 预发布环境│ └── 40-prod.conf # 生产环境├── id_ed25519 # 默认密钥├── id_ed25519_prod # 生产环境专用密钥├── id_ed25519_db # 数据库专用密钥└── known_hosts
主配置文件:
# ~/.ssh/config# 使用Include指令加载分环境配置(OpenSSH 7.3+支持)Include config.d/*.conf
全局默认配置:
# ~/.ssh/config.d/00-defaults.confHost * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes IdentitiesOnly yes Compression yes# 连接复用,同一台服务器的多个SSH会话共用一个TCP连接 ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 600# 首次连接自动接受主机密钥(仅限内网环境,公网建议去掉)# StrictHostKeyChecking accept-new
生产环境配置:
# ~/.ssh/config.d/40-prod.conf# 生产环境 - 通过跳板机访问Host prod-jump HostName 203.0.113.10 Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519_prodHost prod-web-* User deployer IdentityFile ~/.ssh/id_ed25519_prod ProxyJump prod-jumpHost prod-web-01 HostName 10.0.1.11Host prod-web-02 HostName 10.0.1.12Host prod-web-03 HostName 10.0.1.13Host prod-db-* User dbadmin IdentityFile ~/.ssh/id_ed25519_db ProxyJump prod-jumpHost prod-db-master HostName 10.0.2.21Host prod-db-slave-01 HostName 10.0.2.22Host prod-db-slave-02 HostName 10.0.2.23
# 创建socket目录(连接复用需要)mkdir -p ~/.ssh/sockets# 使用效果ssh prod-web-01 # 连接生产Web服务器ssh prod-db-master # 连接生产数据库主库# 查看当前活跃的连接复用ls ~/.ssh/sockets/# 手动关闭某个复用连接ssh -O exit prod-web-01
案例三:SSH密钥自动轮换脚本
场景描述:安全合规要求SSH密钥每90天轮换一次。手动操作容易遗漏,写个脚本自动化处理。
#!/bin/bash# 文件名:rotate_ssh_keys.sh# 功能:自动轮换SSH密钥并分发到目标服务器# 建议配合crontab每季度执行一次set -euo pipefailKEY_DIR="$HOME/.ssh"KEY_TYPE="ed25519"KEY_COMMENT="$(whoami)@$(hostname)-$(date +%Y%m%d)"BACKUP_DIR="$KEY_DIR/archived_keys"HOST_FILE="$HOME/.ssh/managed_hosts.txt"LOG_FILE="/var/log/ssh_key_rotation_$(date +%Y%m%d).log"log() {echo"[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"}# 创建备份目录mkdir -p "$BACKUP_DIR"# 1. 备份旧密钥if [[ -f "$KEY_DIR/id_${KEY_TYPE}" ]]; then BACKUP_NAME="id_${KEY_TYPE}_$(date +%Y%m%d_%H%M%S)" cp "$KEY_DIR/id_${KEY_TYPE}""$BACKUP_DIR/$BACKUP_NAME" cp "$KEY_DIR/id_${KEY_TYPE}.pub""$BACKUP_DIR/${BACKUP_NAME}.pub"log"旧密钥已备份到: $BACKUP_DIR/$BACKUP_NAME"fi# 2. 生成新密钥(不设passphrase,自动化场景用)ssh-keygen -t "$KEY_TYPE" -C "$KEY_COMMENT" -f "$KEY_DIR/id_${KEY_TYPE}" -N "" -qlog"新密钥已生成: $KEY_DIR/id_${KEY_TYPE}"# 3. 分发新公钥到所有服务器(用旧密钥认证)if [[ -f "$HOST_FILE" ]]; thenwhile IFS=: read -r host port user; do port=${port:-52222} user=${user:-opsadmin}log"分发到: ${user}@${host}:${port}"if ssh-copy-id -i "$KEY_DIR/id_${KEY_TYPE}.pub" \ -p "$port" \ -o ConnectTimeout=10 \ -o IdentityFile="$BACKUP_DIR/$(ls -t $BACKUP_DIR/id_${KEY_TYPE}_* 2>/dev/null | head -1)" \"${user}@${host}" 2>>"$LOG_FILE"; thenlog"成功: ${host}"elselog"失败: ${host} - 需要手动处理"fidone < "$HOST_FILE"fi# 4. 验证新密钥可用log"验证新密钥..."if [[ -f "$HOST_FILE" ]]; thenwhile IFS=: read -r host port user; do port=${port:-52222} user=${user:-opsadmin}if ssh -p "$port" -o ConnectTimeout=5 -o BatchMode=yes \"${user}@${host}""echo ok" &>/dev/null; thenlog"验证通过: ${host}"elselog"验证失败: ${host} - 紧急!请检查"fidone < "$HOST_FILE"filog"密钥轮换完成"
# managed_hosts.txt 格式:主机:端口:用户cat > ~/.ssh/managed_hosts.txt << 'EOF'192.168.1.101:52222:opsadmin192.168.1.102:52222:opsadmin10.0.1.11:22:deployer10.0.1.12:22:deployerEOF
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 性能优化
-
使用ed25519替代RSA密钥:ed25519密钥长度只有68字节,RSA-4096是800+字节。实测签名速度ed25519比RSA-4096快约30%,在批量SSH操作(Ansible管理500台机器)时差异明显。Ansible playbook跑完全量主机,ed25519密钥比RSA-4096快了约12秒(总耗时从98秒降到86秒)。
# 生成ed25519密钥ssh-keygen -t ed25519 -C "ops@company.com"# 如果已有RSA密钥,生成新的ed25519密钥后逐步替换ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "ops@company.com" -
开启连接复用(ControlMaster):同一台服务器的多个SSH会话共用一个TCP连接,省去重复的TCP握手和密钥交换。实测第二次连接耗时从1.2秒降到0.1秒。对于频繁ssh/scp操作的场景提升巨大。
# ~/.ssh/config 中配置Host * ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 600# 创建socket目录mkdir -p ~/.ssh/socketschmod 700 ~/.ssh/sockets -
关闭DNS反向解析和GSSAPI:sshd默认会对客户端IP做DNS反向解析,如果DNS服务器响应慢或不可达,每次连接会卡5-30秒。GSSAPI认证同理,不用Kerberos就关掉。
# 服务端 /etc/ssh/sshd_configUseDNS noGSSAPIAuthentication no# 客户端 ~/.ssh/config(双向都关)Host * GSSAPIAuthentication no -
启用压缩传输:在带宽有限的网络环境下(比如跨地域机房),开启压缩可以减少传输数据量。实测传输日志文件(文本类数据压缩率高)速度提升40-60%。但在局域网高带宽环境下,压缩反而增加CPU开销,建议关闭。
# 低带宽环境开启Host slow-network-* Compression yes# 局域网环境关闭Host lan-* Compression no
4.1.2 安全加固
-
限制SSH访问来源IP:即使改了端口、禁了密码,也建议在防火墙层面限制只允许特定IP段访问SSH端口。纵深防御,多一层保护。
# firewalld:只允许办公网络和跳板机IP访问SSHsudo firewall-cmd --permanent --zone=public --remove-service=sshsudo firewall-cmd --permanent --new-zone=ssh-restricted 2>/dev/null || truesudo firewall-cmd --permanent --zone=ssh-restricted --add-source=172.16.0.0/16sudo firewall-cmd --permanent --zone=ssh-restricted --add-source=203.0.113.10/32sudo firewall-cmd --permanent --zone=ssh-restricted --add-port=52222/tcpsudo firewall-cmd --reload# iptables方式sudo iptables -A INPUT -p tcp --dport 52222 -s 172.16.0.0/16 -j ACCEPTsudo iptables -A INPUT -p tcp --dport 52222 -s 203.0.113.10/32 -j ACCEPTsudo iptables -A INPUT -p tcp --dport 52222 -j DROPsudo service iptables save -
定期审计SSH登录日志:每周检查一次异常登录记录,关注非工作时间登录、陌生IP登录、频繁失败尝试。
# 查看成功登录记录grep "Accepted" /var/log/secure | awk '{print $1,$2,$3,$9,$11}' | sort | uniq -c | sort -rn | head -20# 查看失败登录统计(按IP排序)grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20# 查看非工作时间(22:00-08:00)的登录grep "Accepted" /var/log/secure | awk '{split($3,t,":"); if(t[1]>=22 || t[1]<8) print}' -
SSH密钥指纹验证:首次连接新服务器时,SSH会提示确认主机指纹。生产环境不要无脑yes,应该提前通过安全渠道获取服务器指纹并核对。
# 在服务器上查看主机密钥指纹ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub# 客户端连接时核对指纹# The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established.# ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.# 核对一致后输入yes -
禁用弱加密算法:默认配置包含一些老旧的加密算法(如arcfour、3des-cbc),存在已知漏洞。只保留安全的算法。
# /etc/ssh/sshd_configKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctrMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com# 验证当前使用的算法ssh -vv -p 52222 opsadmin@192.168.1.100 2>&1 | grep "kex:"
4.1.3 高可用配置
-
跳板机高可用:跳板机是单点,挂了所有人都连不上内网服务器。生产环境至少部署两台跳板机,用DNS轮询或keepalived做VIP漂移。
# SSH Config中配置备用跳板机Host jump HostName jump-vip.company.com Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519# 连接超时后自动尝试备用 ConnectTimeout 5# 或者用Match块配置fallback# 主跳板机Host jump-primary HostName 203.0.113.10 Port 52222# 备用跳板机Host jump-backup HostName 203.0.113.11 Port 52222 -
SSH服务端口探活:用监控系统定期检测SSH端口是否可达,sshd进程是否存活。
# 简单的SSH端口探活脚本nc -z -w 3 192.168.1.100 52222 && echo"SSH OK" || echo"SSH DOWN"# 或者用ssh命令探活(更准确,验证到协议层)ssh -o ConnectTimeout=3 -o BatchMode=yes -p 52222 opsadmin@192.168.1.100 "echo ok" 2>/dev/null -
备份策略:SSH配置文件和密钥是关键资产,必须纳入备份。
-
/etc/ssh/sshd_config和/etc/ssh/ssh_host_*主机密钥:纳入系统配置备份 -
用户 ~/.ssh/目录:纳入用户数据备份 -
CA密钥(如果用证书认证):离线备份,存放在保险柜级别的安全位置
4.2 注意事项
4.2.1 配置注意事项
改SSH配置前必须保持一个活跃会话不断开。 这是铁律,违反一次就可能要跑机房。我亲眼见过同事改错sshd_config后restart,所有SSH连接断开,最后开车去机房用显示器键盘恢复的。
-
修改sshd_config后先用 sshd -t检查语法,再reload而不是restart -
改端口时先在防火墙放行新端口,再改配置重启,顺序不能反 -
禁用密码认证前,必须确认密钥登录已经配好并测试通过 -
AllowUsers和AllowGroups是白名单,配了之后不在名单里的用户全部被拒绝,包括root -
Match块必须放在sshd_config文件末尾,Match块之后的配置都属于这个Match块的作用域
4.2.2 常见错误
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
systemctl status sshd
ss -tlnp 检查端口 |
|
|
|
telnet IP PORT
|
|
|
|
IdentitiesOnly yes 指定密钥 |
|
|
|
UseDNS no,客户端设 GSSAPIAuthentication no |
|
|
|
ssh-keygen -R 主机IP
|
|
|
|
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys |
4.2.3 兼容性问题
-
版本兼容:ed25519密钥需要OpenSSH 6.5+,ProxyJump指令需要7.3+,Include指令需要7.3+。CentOS 6自带的OpenSSH 5.3不支持这些特性,需要升级或用ProxyCommand替代ProxyJump。 # CentOS 6上用ProxyCommand替代ProxyJumpHost internal-server HostName 10.0.1.11 ProxyCommand ssh -W %h:%p jump -
平台兼容:macOS自带的OpenSSH版本通常较新(Ventura自带8.6),但ssh-copy-id需要额外安装( brew install ssh-copy-id)。Windows 10/11自带OpenSSH客户端,但版本可能较旧,建议用Git Bash或WSL。 -
组件依赖:fail2ban在CentOS 7上依赖EPEL源;CentOS 8/9的fail2ban需要python3-systemd包;Ubuntu直接apt安装即可。semanage命令需要安装policycoreutils-python-utils包。
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
# CentOS/RHEL 查看SSH认证日志sudo tail -f /var/log/secure# Ubuntu/Debian 查看SSH认证日志sudo tail -f /var/log/auth.log# 用journalctl查看sshd日志(systemd系统通用)sudo journalctl -u sshd -f# 只看最近1小时的SSH日志sudo journalctl -u sshd --since "1 hour ago"# 过滤失败登录sudo journalctl -u sshd | grep -i "failed\|error\|denied"# 查看fail2ban日志sudo tail -f /var/log/fail2ban.log
日志级别调整:排查问题时临时调高日志级别,排查完改回来。
# /etc/ssh/sshd_config# 正常运行用VERBOSE,排查问题临时改为DEBUG3LogLevel VERBOSE# LogLevel DEBUG3# 改完reloadsudo systemctl reload sshd
DEBUG3级别会记录每一步认证细节,包括尝试了哪些密钥、为什么拒绝等。日志量很大,排查完务必改回VERBOSE,否则磁盘会被撑满。
5.1.2 常见问题排查
问题一:Permission denied (publickey) —— 密钥认证失败
这是最常见的SSH问题,原因有很多种,按排查优先级列出:
# 1. 客户端用verbose模式连接,看具体卡在哪一步ssh -vvv -p 52222 -i ~/.ssh/id_ed25519 opsadmin@192.168.1.100# 关注这些关键行:# "Offering public key: /home/user/.ssh/id_ed25519 ED25519" -> 客户端发送了密钥# "Server accepts key: /home/user/.ssh/id_ed25519 ED25519" -> 服务端接受了密钥# "Authentication succeeded (publickey)" -> 认证成功# 如果看到 "No more authentication methods to try" 说明服务端拒绝了所有密钥
# 2. 检查服务端权限(最常见的原因)ls -la ~/# 家目录权限不能大于755,如果是777就会被拒绝ls -la ~/.ssh/# .ssh目录必须是700ls -la ~/.ssh/authorized_keys# authorized_keys必须是600# 修复权限chmod 755 ~chmod 700 ~/.sshchmod 600 ~/.ssh/authorized_keys# 3. 检查authorized_keys内容cat ~/.ssh/authorized_keys# 确认公钥完整,没有换行符截断# 每个公钥必须是一行,不能有折行# 4. 检查文件属主ls -la ~/.ssh/authorized_keys# owner必须是当前用户,不能是rootchown $(whoami):$(whoami) ~/.ssh/authorized_keys# 5. 检查SELinux上下文(CentOS/RHEL)ls -Z ~/.ssh/authorized_keys# 应该是 unconfined_u:object_r:ssh_home_t:s0# 如果不对,恢复上下文:restorecon -Rv ~/.ssh/
问题二:SSH连接慢,登录要等5-30秒
# 原因1:DNS反向解析(最常见)# 服务端对客户端IP做反向DNS查询,DNS服务器不可达时会等到超时# 解决:sudo grep "UseDNS" /etc/ssh/sshd_config# 如果是yes或者没配(默认yes),改为no# UseDNS no# 原因2:GSSAPI认证超时# 客户端尝试GSSAPI认证,没有Kerberos环境时会超时# 解决(客户端):ssh -o GSSAPIAuthentication=no -p 52222 opsadmin@192.168.1.100# 或者在~/.ssh/config中全局关闭# Host *# GSSAPIAuthentication no# 原因3:systemd-logind响应慢# CentOS 7上偶发,dbus通信超时# 诊断:sudo journalctl -u systemd-logind --since "10 minutes ago"# 解决:sudo systemctl restart systemd-logind# 用time命令量化连接耗时time ssh -p 52222 opsadmin@192.168.1.100 "exit"# 正常应该在1秒以内
问题三:Connection refused —— 连接被拒绝
# 1. 检查sshd是否在运行sudo systemctl status sshd# 如果是dead/failed状态,查看原因sudo journalctl -u sshd --no-pager | tail -30# 2. 检查监听端口ss -tlnp | grep sshd# 确认sshd在监听正确的端口# 3. 检查配置文件语法sudo sshd -t# 如果有语法错误,sshd可能启动失败# 4. 检查防火墙sudo firewall-cmd --list-all# 或sudo iptables -L -n | grep 52222# 5. 检查SELinux是否阻止了非标准端口sudo semanage port -l | grep ssh# 如果新端口不在列表里:sudo semanage port -a -t ssh_port_t -p tcp 52222# 6. 检查TCP Wrappers(/etc/hosts.allow 和 /etc/hosts.deny)cat /etc/hosts.deny# 如果有 sshd: ALL 会拒绝所有SSH连接
问题四:fail2ban误封了合法IP
# 查看当前被封禁的IP列表sudo fail2ban-client status sshd# 解封特定IPsudo fail2ban-client set sshd unbanip 172.16.1.50# 查看封禁原因(在fail2ban日志中搜索)sudo grep "172.16.1.50" /var/log/fail2ban.log# 将合法IP加入白名单(永久生效)# 编辑 /etc/fail2ban/jail.local# ignoreip = 127.0.0.1/8 10.0.0.0/8 172.16.0.0/16# 重启fail2ban使白名单生效sudo systemctl restart fail2ban
5.1.3 调试模式
# 在前台以调试模式启动sshd(不影响正在运行的sshd)# 监听在不同端口避免冲突sudo /usr/sbin/sshd -d -p 52223# 客户端连接调试端口ssh -vvv -p 52223 opsadmin@192.168.1.100# 两边的输出对照看,能精确定位认证失败的原因# 检查sshd加载的完整配置(排查配置覆盖问题)sudo sshd -T# 检查特定用户从特定IP连接时的有效配置(Match块生效情况)sudo sshd -T -C user=deployer,host=10.0.0.50,addr=10.0.0.50
5.2 性能监控
5.2.1 关键指标监控
# SSH连接数监控ss -tnp | grep ":52222" | wc -l# 当前活跃SSH会话数who | wc -l# sshd进程资源占用ps aux | grep sshd | grep -v grep# SSH认证失败频率(最近1小时)sudo journalctl -u sshd --since "1 hour ago" | grep -c "Failed"# fail2ban封禁统计sudo fail2ban-client status sshd | grep "Currently banned"# SSH端口连接状态分布ss -tn | grep ":52222" | awk '{print $1}' | sort | uniq -c
5.2.2 监控指标说明
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5.2.3 Prometheus监控规则
# prometheus_ssh_rules.yml# 文件路径:/etc/prometheus/rules/ssh_rules.ymlgroups:-name:ssh_securityinterval:30srules:# SSH认证失败率告警-alert:SSHAuthFailureHighexpr:rate(ssh_auth_failures_total[5m])>10for:5mlabels:severity:warningannotations:summary:"SSH认证失败率过高 ({{ $labels.instance }})"description:"5分钟内SSH认证失败率超过10次/秒,可能遭受暴力破解"# SSH活跃连接数告警-alert:SSHConnectionsHighexpr:ssh_active_connections>100for:2mlabels:severity:warningannotations:summary:"SSH连接数过高 ({{ $labels.instance }})"description:"SSH活跃连接数 {{ $value }},超过阈值100"# fail2ban封禁数告警-alert:Fail2banBannedIPsHighexpr:fail2ban_banned_ips{jail="sshd"}>20for:5mlabels:severity:criticalannotations:summary:"fail2ban封禁IP数过多 ({{ $labels.instance }})"description:"SSH jail当前封禁 {{ $value }} 个IP,可能正在遭受大规模攻击"# sshd进程存活检测-alert:SSHDProcessDownexpr:node_systemd_unit_state{name="sshd.service",state="active"}!=1for:1mlabels:severity:criticalannotations:summary:"sshd服务异常 ({{ $labels.instance }})"description:"sshd服务未在运行状态,远程管理通道中断"
配合node_exporter的textfile collector采集SSH指标:
#!/bin/bash# 文件名:ssh_metrics.sh# 功能:采集SSH相关指标,输出为Prometheus格式# 配合crontab每分钟执行:* * * * * /opt/scripts/ssh_metrics.shMETRICS_DIR="/var/lib/node_exporter/textfile_collector"METRICS_FILE="$METRICS_DIR/ssh_metrics.prom"mkdir -p "$METRICS_DIR"# 活跃SSH连接数ACTIVE_CONN=$(ss -tnp | grep -c ":52222" 2>/dev/null || echo 0)# 当前登录用户数LOGGED_USERS=$(who | wc -l)# 最近5分钟认证失败次数FAIL_COUNT=$(sudo journalctl -u sshd --since "5 minutes ago" 2>/dev/null | grep -c "Failed" || echo 0)# fail2ban封禁IP数BANNED_IPS=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $NF}' || echo 0)cat > "$METRICS_FILE.tmp" << EOF# HELP ssh_active_connections Current number of SSH connections# TYPE ssh_active_connections gaugessh_active_connections $ACTIVE_CONN# HELP ssh_logged_users Current number of logged in users# TYPE ssh_logged_users gaugessh_logged_users $LOGGED_USERS# HELP ssh_auth_failures_5m SSH authentication failures in last 5 minutes# TYPE ssh_auth_failures_5m gaugessh_auth_failures_5m $FAIL_COUNT# HELP fail2ban_banned_ips Number of IPs banned by fail2ban# TYPE fail2ban_banned_ips gaugefail2ban_banned_ips{jail="sshd"} $BANNED_IPSEOFmv "$METRICS_FILE.tmp""$METRICS_FILE"
5.3 备份与恢复
5.3.1 备份策略
#!/bin/bash# 文件名:backup_ssh_config.sh# 功能:备份SSH服务端和客户端配置# 建议每周执行一次,保留最近12周的备份BACKUP_BASE="/data/backup/ssh"DATE=$(date +%Y%m%d_%H%M%S)BACKUP_DIR="$BACKUP_BASE/$DATE"KEEP_WEEKS=12mkdir -p "$BACKUP_DIR"# 备份服务端配置sudo cp -a /etc/ssh/sshd_config "$BACKUP_DIR/"sudo cp -a /etc/ssh/ssh_config "$BACKUP_DIR/" 2>/dev/nullsudo cp -a /etc/ssh/banner.txt "$BACKUP_DIR/" 2>/dev/null# 备份主机密钥(恢复时需要,否则所有客户端会报host key changed)sudo cp -a /etc/ssh/ssh_host_* "$BACKUP_DIR/"# 备份fail2ban配置sudo cp -a /etc/fail2ban/jail.local "$BACKUP_DIR/" 2>/dev/null# 备份CA密钥(如果有)sudo cp -a /etc/ssh/ca_user_key* "$BACKUP_DIR/" 2>/dev/nullsudo cp -a /etc/ssh/revoked_keys "$BACKUP_DIR/" 2>/dev/null# 设置备份文件权限sudo chmod 600 "$BACKUP_DIR"/ssh_host_*sudo chmod 600 "$BACKUP_DIR"/ca_user_key 2>/dev/null# 清理过期备份find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +$((KEEP_WEEKS * 7)) -exec rm -rf {} \;echo"SSH配置备份完成: $BACKUP_DIR"ls -la "$BACKUP_DIR/"
5.3.2 恢复流程
-
停止服务(如果sshd还在运行):
# 不要直接stop,先确认有其他方式访问服务器(VNC/IPMI/控制台)sudo systemctl stop sshd -
恢复配置文件:
# 找到最近的备份ls -lt /data/backup/ssh/ | head -5# 恢复配置RESTORE_DIR="/data/backup/ssh/20250115_020000"sudo cp "$RESTORE_DIR/sshd_config" /etc/ssh/sshd_configsudo cp "$RESTORE_DIR"/ssh_host_* /etc/ssh/# 恢复权限sudo chmod 600 /etc/ssh/ssh_host_*_keysudo chmod 644 /etc/ssh/ssh_host_*_key.pubsudo chmod 644 /etc/ssh/sshd_config -
验证配置:
sudo sshd -t -
重启服务:
sudo systemctl start sshdsudo systemctl status sshdss -tlnp | grep sshd
六、总结
6.1 技术要点回顾
-
端口+认证双重加固:改默认端口过滤自动化扫描,禁密码认证杜绝暴力破解。实测改端口后扫描日志从每天数万条降到个位数,禁密码后暴力破解彻底归零。 -
ed25519是当前最优密钥算法:比RSA-4096更安全、密钥更短(68字节 vs 800+字节)、签名验证更快(约30%)。除非目标系统OpenSSH低于6.5,否则一律用ed25519。 -
fail2ban是必备防护组件:配合MaxAuthTries形成两层防线——MaxAuthTries限制单次连接尝试次数,fail2ban在多次连接失败后封禁IP。两者配合效果远大于单独使用。 -
SSH Config + ProxyJump实现高效多主机管理:用别名替代IP+端口,用ProxyJump实现透明跳转,用ControlMaster实现连接复用。管理200+台服务器和管理2台一样方便。 -
证书认证是大规模环境的终极方案:超过50台服务器时,逐台维护authorized_keys不现实。CA签发证书后服务端只需信任CA公钥,人员变动只需吊销证书,不用逐台操作。 -
配置变更必须有回滚方案:改SSH配置前备份、保持活跃会话、先放行新端口再改配置、用sshd -t检查语法。这些流程每一步都不能省。
6.2 进阶学习方向
-
SSH证书认证与HashiCorp Vault集成
-
Vault可以作为SSH CA,动态签发短期证书(比如有效期8小时),实现”用完即废”的零信任模式 -
学习资源:HashiCorp Vault官方文档 SSH Secrets Engine章节 -
实践建议:先在测试环境搭建Vault,配置SSH Secrets Engine,体验动态证书签发流程 -
基于FIDO2/U2F硬件密钥的SSH认证
-
OpenSSH 8.2+支持FIDO2安全密钥(如YubiKey),私钥存储在硬件中无法导出,比软件密钥更安全 -
学习资源:OpenSSH 8.2 Release Notes,Yubico官方SSH配置指南 -
实践建议:购买一个YubiKey 5系列,配置 ssh-keygen -t ed25519-sk生成硬件绑定密钥 -
Teleport/Boundary等零信任SSH网关
-
替代传统跳板机,提供会话录制、RBAC权限控制、审计日志、MFA集成等企业级功能 -
学习资源:Teleport官方文档,Gravitational GitHub仓库 -
实践建议:用Docker快速部署Teleport试用版,体验Web Terminal和会话回放功能
6.3 参考资料
-
OpenSSH官方文档 – sshd_config所有参数的权威说明 -
Mozilla SSH安全指南 – Mozilla内部SSH加固标准,推荐的加密算法列表 -
fail2ban官方Wiki – fail2ban配置详解和自定义filter编写 -
SSH Mastery (Michael W Lucas) – SSH进阶书籍,覆盖证书认证、端口转发等高级主题 -
CIS Benchmark for Linux – CIS安全基线中SSH加固章节
附录
A. 命令速查表
# ===== 密钥管理 =====ssh-keygen -t ed25519 -C "comment"# 生成ed25519密钥ssh-keygen -t rsa -b 4096 -C "comment"# 生成RSA-4096密钥ssh-keygen -lf ~/.ssh/id_ed25519.pub # 查看密钥指纹ssh-keygen -R 192.168.1.100 # 删除known_hosts中的主机记录ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 52222 user@host # 分发公钥# ===== 连接和调试 =====ssh -p 52222 user@host # 指定端口连接ssh -vvv user@host # 详细调试模式ssh -J jump user@internal-host # 通过跳板机连接ssh -L 3307:db-host:3306 user@jump # 本地端口转发ssh -D 1080 user@host # SOCKS代理# ===== 服务管理 =====sudo sshd -t # 检查配置语法sudo sshd -T # 显示完整有效配置sudo systemctl reload sshd # 重载配置(不断连接)sudo systemctl restart sshd # 重启服务(断开所有连接)# ===== fail2ban =====sudo fail2ban-client status sshd # 查看SSH jail状态sudo fail2ban-client set sshd unbanip 1.2.3.4 # 解封IPsudo fail2ban-client set sshd banip 1.2.3.4 # 封禁IP# ===== 证书认证 =====ssh-keygen -s ca_key -I cert_id -n user -V +52w user.pub # 签发证书ssh-keygen -L -f cert.pub # 查看证书信息# ===== 权限设置 =====chmod 700 ~/.ssh # .ssh目录权限chmod 600 ~/.ssh/authorized_keys # authorized_keys权限chmod 600 ~/.ssh/id_ed25519 # 私钥权限chmod 644 ~/.ssh/id_ed25519.pub # 公钥权限chmod 755 ~ # 家目录权限上限
B. 配置参数详解
sshd_config 关键参数速查:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
客户端 ~/.ssh/config 常用参数:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C. 术语表
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
微
信
群
WeChat group
为了方便大家更好的交流运维等相关技术问题,创建了微信交流群,需要加群的小伙伴们可以扫一扫下面的二维码加我为好友拉您进群(备注:加群)。

代
码
仓
库
| 代码仓库 | 网址 |
| Github | https://github.com/raymond999999 |
| Gitee | https://gitee.com/raymond9 |
博
客
Blog
| 博客 | 网址 |
|
|
https://blog.csdn.net/qq_25599925 |
| 稀土掘金 | https://juejin.cn/user/4262187909781751 |
| 知识星球 | https://wx.zsxq.com/group/15555885545422 |
| 阿里云社区 | https://developer.aliyun.com/profile/snzh3xpxaf6sg |
| 腾讯云社区 | https://cloud.tencent.com/developer/user/11823619 |
| 华为云社区 | https://developer.huaweicloud.com/usercenter/mycommunity/dynamics |
访问博客网站,查看更多优质原创内容。
夜雨聆风