乐于分享
好东西不私藏

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

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
  • 细粒度访问控制:通过 AllowUsersAllowGroupsDenyUsersDenyGroups 四个指令控制谁能登录,配合 Match 块可以针对特定用户、IP、端口设置不同策略。比如允许运维组从跳板机登录,禁止其他所有来源。

1.3 适用场景

  • 场景一:公网服务器加固。云主机直接暴露在公网,每天承受大量扫描和暴力破解。实测一台新开的阿里云ECS,开机2小时内就有来自全球的SSH登录尝试。必须改端口+禁密码+上fail2ban三件套。
  • 场景二:多人运维团队密钥管理。团队10+人需要登录上百台服务器,用密码管理不现实。通过SSH密钥+跳板机+ProxyJump实现统一入口管理,人员离职时只需在跳板机删除公钥。
  • 场景三:自动化运维免密通道。Ansible、SaltStack等自动化工具依赖SSH免密登录批量执行命令。CI/CD流水线部署也需要SSH免密推送代码到目标机器。密钥认证是自动化的基础。

1.4 环境要求

组件
版本要求
说明
操作系统
CentOS 7+/Ubuntu 20.04+
RHEL系和Debian系均适用,配置路径一致
OpenSSH
7.4+
7.4开始支持ed25519密钥,8.0+支持证书认证增强
fail2ban
0.10+
用于防暴力破解,EPEL源或apt直接安装
firewalld/iptables
系统自带
用于限制SSH访问来源IP
Python
3.6+
fail2ban依赖,CentOS 7需手动安装python3

二、详细步骤

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_PASSWORDthen# 使用密码分发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 常见错误

错误现象
原因分析
解决方案
Permission denied (publickey)
公钥未添加到authorized_keys,或文件权限不对
检查~/.ssh/目录700、authorized_keys文件600、家目录不超过755
Connection refused
sshd未启动或端口不对
systemctl status sshd

 检查服务状态,ss -tlnp 检查端口
Connection timed out
防火墙未放行端口或网络不通
telnet IP PORT

 测试端口连通性,检查firewalld/iptables规则
Too many authentication failures
客户端尝试了太多密钥,超过MaxAuthTries
在~/.ssh/config中加 IdentitiesOnly yes 指定密钥
SSH连接后卡住5-10秒
DNS反向解析超时或GSSAPI认证超时
服务端设 UseDNS no,客户端设 GSSAPIAuthentication no
Host key verification failed
服务器重装后主机密钥变了
ssh-keygen -R 主机IP

 删除旧指纹,重新确认
Bad owner or modes
.ssh目录或文件权限过大
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 监控指标说明

指标名称
正常范围
告警阈值
说明
SSH活跃连接数
0-50
>100
超过100可能是暴力破解或连接泄漏
认证失败次数/小时
0-10
>50
大量失败说明有暴力破解行为
fail2ban封禁IP数
0-5
>20
大量封禁说明正在遭受攻击
SSH连接延迟
<1s
>5s
延迟高需要排查DNS/GSSAPI/网络问题
sshd进程CPU使用率
<5%
>30%
CPU高可能是密钥交换风暴或DDoS
sshd进程内存使用
<50MB
>200MB
内存异常增长需要排查连接泄漏

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 恢复流程

  1. 停止服务(如果sshd还在运行):

    # 不要直接stop,先确认有其他方式访问服务器(VNC/IPMI/控制台)sudo systemctl stop sshd
  2. 恢复配置文件

    # 找到最近的备份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
  3. 验证配置

    sudo sshd -t
  4. 重启服务

    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 进阶学习方向

  1. SSH证书认证与HashiCorp Vault集成

    • Vault可以作为SSH CA,动态签发短期证书(比如有效期8小时),实现”用完即废”的零信任模式
    • 学习资源:HashiCorp Vault官方文档 SSH Secrets Engine章节
    • 实践建议:先在测试环境搭建Vault,配置SSH Secrets Engine,体验动态证书签发流程
  2. 基于FIDO2/U2F硬件密钥的SSH认证

    • OpenSSH 8.2+支持FIDO2安全密钥(如YubiKey),私钥存储在硬件中无法导出,比软件密钥更安全
    • 学习资源:OpenSSH 8.2 Release Notes,Yubico官方SSH配置指南
    • 实践建议:购买一个YubiKey 5系列,配置 ssh-keygen -t ed25519-sk 生成硬件绑定密钥
  3. 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 关键参数速查

参数
默认值
推荐值
说明
Port
22
52222
SSH监听端口
PermitRootLogin
yes
no
是否允许root登录
PasswordAuthentication
yes
no
是否允许密码认证
PubkeyAuthentication
yes
yes
是否允许公钥认证
MaxAuthTries
6
3
单次连接最大认证尝试次数
LoginGraceTime
120
30
认证超时时间(秒)
MaxSessions
10
5
单连接最大会话数
MaxStartups
10:30:100
10:30:60
未认证连接限制
ClientAliveInterval
0
300
心跳探测间隔(秒)
ClientAliveCountMax
3
3
心跳失败断开阈值
UseDNS
yes
no
是否做DNS反向解析
GSSAPIAuthentication
yes
no
是否启用GSSAPI认证
X11Forwarding
yes
no
是否允许X11转发
AllowAgentForwarding
yes
no
是否允许Agent转发
LogLevel
INFO
VERBOSE
日志级别
Banner
none
/etc/ssh/banner.txt
登录前显示的警告信息
StrictModes
yes
yes
是否检查文件权限

客户端 ~/.ssh/config 常用参数

参数
说明
示例
HostName
实际主机地址
192.168.1.100
Port
SSH端口
52222
User
登录用户名
opsadmin
IdentityFile
私钥文件路径
~/.ssh/id_ed25519
IdentitiesOnly
只用指定密钥
yes
ProxyJump
跳板机
jump
ServerAliveInterval
心跳间隔(秒)
60
ControlMaster
连接复用
auto
ControlPath
复用socket路径
~/.ssh/sockets/%r@%h-%p
ControlPersist
复用保持时间(秒)
600
Compression
压缩传输
yes
ForwardAgent
Agent转发
no

C. 术语表

术语
英文
解释
非对称加密
Asymmetric Encryption
使用公钥加密、私钥解密的加密方式,SSH密钥认证的基础
公钥
Public Key
可以公开分发的密钥,放在服务端的authorized_keys中
私钥
Private Key
必须严格保密的密钥,存放在客户端,权限必须是600
密钥指纹
Key Fingerprint
密钥的哈希摘要,用于快速识别和验证密钥身份
CA
Certificate Authority
证书颁发机构,SSH证书认证中负责签发和吊销用户证书
跳板机
Jump Host / Bastion Host
作为SSH中转的服务器,内网服务器只允许从跳板机访问
端口转发
Port Forwarding
通过SSH隧道将本地端口映射到远程端口,或反向映射
Agent转发
Agent Forwarding
将本地ssh-agent转发到远程服务器,实现多跳免密
连接复用
Connection Multiplexing
多个SSH会话共用一个TCP连接,减少握手开销
fail2ban
fail2ban
入侵防御工具,监控日志并自动封禁恶意IP
GSSAPI
Generic Security Services API
通用安全服务接口,用于Kerberos认证集成
SELinux
Security-Enhanced Linux
安全增强Linux,强制访问控制机制,影响SSH端口和文件访问

WeChat group

为了方便大家更好的交流运维等相关技术问题,创建了微信交流群,需要加群的小伙伴们可以扫一扫下面的二维码加我为好友拉您进群(备注:加群)。

代码仓库 网址
Github https://github.com/raymond999999
Gitee https://gitee.com/raymond9

Blog

博客 网址
CSDN
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

访问博客网站,查看更多优质原创内容。