SSH安全加固與免密登錄
一、概述
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 環境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| 操作系統 | 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/RHEL grep"Failed password"/var/log/secure | tail -20 # Ubuntu/Debian grep"Failed password"/var/log/auth.log | tail -20
重要提醒:修改SSH配置前,務必保持當前SSH會話不斷開,同時開一個新終端測試。配置改錯了當前會話還能用來恢復,斷開就只能去機房或用VNC了。我們團隊的規矩是改SSH配置必須兩人操作,一人改一人保持連接。
2.1.2 安裝依賴
# CentOS/RHEL 安裝 sudo yum install -y epel-release sudo yum install -y fail2ban fail2ban-systemd openssh-server openssh-clients # Ubuntu/Debian 安裝 sudo apt update sudo 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 # 找到#Port22,改為: 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/tcp sudo firewall-cmd --reload sudo firewall-cmd --list-ports # 或者iptables(舊系統) sudo iptables -A INPUT -p tcp --dport 52222 -j ACCEPT sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT sudo service iptables save # Ubuntu UFW sudo ufw allow 52222/tcp sudo ufw status
這個地方有個坑:先放行新端口再改配置重啟sshd,順序反了會把自己鎖在外面。
2.2.2 禁用Root直接登錄
root賬戶是暴力破解的首要目標,因為攻擊者知道每臺Linux都有root用戶,只需要猜密碼。禁用root登錄后,攻擊者還得猜用戶名,難度指數級上升。
# /etc/ssh/sshd_config 中修改 PermitRootLogin no
配套操作:確保有一個sudo權限的普通用戶可用。
# 創建運維用戶 sudo useradd -m -s /bin/bash opsadmin sudo passwd opsadmin # 加入wheel組(CentOS)或sudo組(Ubuntu) # CentOS sudo usermod -aG wheel opsadmin # Ubuntu sudo usermod -aG sudo opsadmin # 驗證sudo權限 su - opsadmin sudo whoami # 輸出應為 root
2.2.3 禁用密碼認證,僅允許密鑰登錄
密碼認證的問題:再復雜的密碼也可能被社工、釣魚、撞庫搞到。密鑰認證從原理上杜絕了暴力破解——私鑰文件不在網絡上傳輸,服務端只做簽名驗證。
# /etc/ssh/sshd_config 中修改 PasswordAuthentication no ChallengeResponseAuthentication no UsePAM yes PubkeyAuthentication 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 opsadmin sudo usermod -aG sshusers deployer
2.2.5 設置登錄超時和重試限制
# /etc/ssh/sshd_config 中修改 LoginGraceTime 30 MaxAuthTries 3 MaxSessions 5 MaxStartups 1060 ClientAliveInterval 300 ClientAliveCountMax 3
參數說明:
LoginGraceTime 30:用戶30秒內必須完成認證,否則斷開。默認120秒太長了。
MaxAuthTries 3:單次連接最多嘗試3次認證。超過后斷開連接,配合fail2ban效果更好。
MaxSessions 5:單個連接最多5個會話復用。
MaxStartups 1060:當未認證連接數達到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-4096 ssh-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 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub # 家目錄權限不能大于755 chmod 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 jump Host 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 jump Host db-prod-01 HostName 10.0.2.21 Host db-prod-02 HostName 10.0.2.22 # 測試環境,直連 Hosttest-* Port 52222 User opsadmin IdentityFile ~/.ssh/id_ed25519 Hosttest-web-01 HostName 192.168.100.11 Hosttest-db-01 HostName 192.168.100.21
# 配置好后直接用別名連接 ssh web-prod-01 ssh 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或iptables banaction = 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 = true port = 52222 filter = sshd logpath = /var/log/secure # Ubuntu用這個路徑: # logpath = /var/log/auth.log maxretry = 3 bantime = 7200 findtime = 300
# 啟動fail2ban sudo systemctl start fail2ban sudo systemctlenablefail2ban # 查看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-clientsetsshd unbanip 192.168.1.50 # 手動封禁某個IP sudo fail2ban-clientsetsshd 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加固配置 # ============================================ # 網絡和協議 # ============================================ # 監聽端口,改掉默認22 Port 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_key HostKey /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 yes AuthorizedKeysFile .ssh/authorized_keys # 禁用密碼認證 PasswordAuthentication no PermitEmptyPasswords no # 禁用質詢響應認證 ChallengeResponseAuthentication no # 禁用基于主機的認證 HostbasedAuthentication no IgnoreRhosts yes # 禁用GSSAPI(不用Kerberos就關掉,開著會導致連接慢) GSSAPIAuthentication no GSSAPICleanupCredentials no # 禁用X11轉發(服務器不需要圖形界面) X11Forwarding no # 禁用TCP轉發(如果不需要SSH隧道) # AllowTcpForwarding no # 如果需要SSH隧道做端口轉發,保持默認yes AllowTcpForwarding yes # 禁用Agent轉發(除非明確需要) AllowAgentForwarding no # 關閉DNS反向解析(開著會導致連接慢2-5秒) UseDNS no # 使用PAM UsePAM yes # ============================================ # 訪問控制 # ============================================ # 只允許sshusers組的用戶登錄 AllowGroups sshusers # 或者指定用戶白名單(和AllowGroups二選一) # AllowUsers opsadmin deployer monitor # ============================================ # 會話控制 # ============================================ # 認證超時30秒 LoginGraceTime 30 # 最大認證嘗試次數 MaxAuthTries 3 # 最大會話數 MaxSessions 5 # 未認證連接限制 MaxStartups 1060 # 客戶端存活檢測 ClientAliveInterval 300 ClientAliveCountMax 3 # ============================================ # 日志 # ============================================ SyslogFacility AUTH LogLevel VERBOSE # ============================================ # 登錄Banner # ============================================ Banner /etc/ssh/banner.txt PrintMotd no PrintLastLog 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.txt cat > /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.txt
set-euo pipefail
# ========== 配置區 ==========
SSH_PORT=52222
SSH_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='?33[0;31m'
GREEN='?33[0;32m'
YELLOW='?33[1;33m'
NC='?33[0m'
log() {
locallevel=$1
shift
localmsg="[$(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"
exit1
}
# 參數檢查
if[[$#-ne 1 ]];then
usage
fi
HOST_FILE=$1
if[[ ! -f"$HOST_FILE"]];then
log"ERROR""主機列表文件不存在:$HOST_FILE"
exit1
fi
if[[ ! -f"$PUB_KEY_FILE"]];then
log"ERROR""公鑰文件不存在:$PUB_KEY_FILE"
log"INFO""請先生成密鑰: ssh-keygen -t ed25519"
exit1
fi
# 檢查sshpass是否安裝
USE_PASSWORD=false
ifcommand-v sshpass &>/dev/null;then
read-sp"輸入SSH密碼(如果目標機器已配置密鑰登錄,直接回車跳過): "SSH_PASS
echo
if[[ -n"$SSH_PASS"]];then
USE_PASSWORD=true
fi
fi
# 統計
TOTAL=0
SUCCESS=0
FAILED=0
log"INFO""開始分發SSH公鑰"
log"INFO""公鑰文件:$PUB_KEY_FILE"
log"INFO""目標用戶:$SSH_USER"
log"INFO""SSH端口:$SSH_PORT"
whileIFS=read-r host;do
# 跳過空行和注釋
[[ -z"$host"||"$host"=~ ^# ]] && continue
TOTAL=$((TOTAL + 1))
log"INFO""[$TOTAL] 正在處理:$host"
if$USE_PASSWORD;then
# 使用密碼分發
ifsshpass -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";then
log"INFO""${GREEN}成功${NC}:$host"
SUCCESS=$((SUCCESS + 1))
else
log"ERROR""${RED}失敗${NC}:$host"
FAILED=$((FAILED + 1))
fi
else
# 使用已有密鑰分發新密鑰
ifssh-copy-id
-i"$PUB_KEY_FILE"
-p"$SSH_PORT"
-o StrictHostKeyChecking=no
-o ConnectTimeout=$TIMEOUT
"${SSH_USER}@${host}"2>>"$LOG_FILE";then
log"INFO""${GREEN}成功${NC}:$host"
SUCCESS=$((SUCCESS + 1))
else
log"ERROR""${RED}失敗${NC}:$host"
FAILED=$((FAILED + 1))
fi
fi
done"$HOST_FILE"
log"INFO""========== 分發完成 =========="
log"INFO""總計:?$TOTAL? 成功:?$SUCCESS? 失敗:?$FAILED"
log"INFO""詳細日志:?$LOG_FILE"
if?[[?$FAILED?-gt 0 ]];?then
? ??log"WARN""有?$FAILED?臺服務器分發失敗,請檢查日志"
? ??exit?1
fi
# 使用方法 chmod +x distribute_ssh_keys.sh # 準備主機列表 cat > hosts.txt <'EOF' 192.168.1.101 192.168.1.102 192.168.1.103 10.0.1.11 10.0.1.12 EOF # 執行分發 ./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 jump Host 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-01 Host 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 33073306 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.conf Host * 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_prod Host prod-web-* User deployer IdentityFile ~/.ssh/id_ed25519_prod ProxyJump prod-jump Host prod-web-01 HostName 10.0.1.11 Host prod-web-02 HostName 10.0.1.12 Host prod-web-03 HostName 10.0.1.13 Host prod-db-* User dbadmin IdentityFile ~/.ssh/id_ed25519_db ProxyJump prod-jump Host prod-db-master HostName 10.0.2.21 Host prod-db-slave-01 HostName 10.0.2.22 Host 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 -Oexitprod-web-01
案例三:SSH密鑰自動輪換腳本
場景描述:安全合規要求SSH密鑰每90天輪換一次。手動操作容易遺漏,寫個腳本自動化處理。
#!/bin/bash
# 文件名:rotate_ssh_keys.sh
# 功能:自動輪換SSH密鑰并分發到目標服務器
# 建議配合crontab每季度執行一次
set-euo pipefail
KEY_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""-q
log"新密鑰已生成:$KEY_DIR/id_${KEY_TYPE}"
# 3. 分發新公鑰到所有服務器(用舊密鑰認證)
if[[ -f"$HOST_FILE"]];then
whileIFS=:read-r host port user;do
port=${port:-52222}
user=${user:-opsadmin}
log"分發到:${user}@${host}:${port}"
ifssh-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";then
log"成功:${host}"
else
log"失敗:${host}- 需要手動處理"
fi
done"$HOST_FILE"
fi
# 4. 驗證新密鑰可用
log"驗證新密鑰..."
if?[[ -f?"$HOST_FILE"?]];?then
? ??while?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;then
log"驗證通過:${host}"
else
log"驗證失敗:${host}- 緊急!請檢查"
fi
done"$HOST_FILE"
fi
log"密鑰輪換完成"
# managed_hosts.txt 格式:主機:端口:用戶 cat > ~/.ssh/managed_hosts.txt <'EOF' 192.168.1.101opsadmin 192.168.1.102opsadmin 10.0.1.11deployer 10.0.1.12deployer EOF
四、最佳實踐和注意事項
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/sockets chmod 700 ~/.ssh/sockets
關閉DNS反向解析和GSSAPI:sshd默認會對客戶端IP做DNS反向解析,如果DNS服務器響應慢或不可達,每次連接會卡5-30秒。GSSAPI認證同理,不用Kerberos就關掉。
# 服務端 /etc/ssh/sshd_config UseDNS no GSSAPIAuthentication 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訪問SSH sudo firewall-cmd --permanent --zone=public --remove-service=ssh sudo firewall-cmd --permanent --new-zone=ssh-restricted 2>/dev/null ||true sudo firewall-cmd --permanent --zone=ssh-restricted --add-source=172.16.0.0/16 sudo firewall-cmd --permanent --zone=ssh-restricted --add-source=203.0.113.10/32 sudo firewall-cmd --permanent --zone=ssh-restricted --add-port=52222/tcp sudo firewall-cmd --reload # iptables方式 sudo iptables -A INPUT -p tcp --dport 52222 -s 172.16.0.0/16 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 52222 -s 203.0.113.10/32 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 52222 -j DROP sudo 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
# 查看非工作時間(2200)的登錄
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_config 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 MACs 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替代ProxyJump Host 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,排查問題臨時改為DEBUG3 LogLevel VERBOSE # LogLevel DEBUG3 # 改完reload sudo 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目錄必須是700 ls -la ~/.ssh/authorized_keys # authorized_keys必須是600 # 修復權限 chmod 755 ~ chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys # 3. 檢查authorized_keys內容 cat ~/.ssh/authorized_keys # 確認公鑰完整,沒有換行符截斷 # 每個公鑰必須是一行,不能有折行 # 4. 檢查文件屬主 ls -la ~/.ssh/authorized_keys # owner必須是當前用戶,不能是root chown $(whoami):$(whoami) ~/.ssh/authorized_keys # 5. 檢查SELinux上下文(CentOS/RHEL) ls -Z ~/.ssh/authorized_keys # 應該是 unconfined_ussh_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 # 解封特定IP sudo fail2ban-clientsetsshd 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.yml
groups:
-name:ssh_security
interval:30s
rules:
# SSH認證失敗率告警
-alert:SSHAuthFailureHigh
expr:rate(ssh_auth_failures_total[5m])>10
for:5m
labels:
severity:warning
annotations:
summary:"SSH認證失敗率過高 ({{ $labels.instance }})"
description:"5分鐘內SSH認證失敗率超過10次/秒,可能遭受暴力破解"
# SSH活躍連接數告警
-alert:SSHConnectionsHigh
expr:ssh_active_connections>100
for:2m
labels:
severity:warning
annotations:
summary:"SSH連接數過高 ({{ $labels.instance }})"
description:"SSH活躍連接數{{ $value }},超過閾值100"
# fail2ban封禁數告警
-alert:Fail2banBannedIPsHigh
expr:fail2ban_banned_ips{jail="sshd"}>20
for:5m
labels:
severity:critical
annotations:
summary:"fail2ban封禁IP數過多 ({{ $labels.instance }})"
description:"SSH jail當前封禁{{ $value }}個IP,可能正在遭受大規模攻擊"
# sshd進程存活檢測
-alert:SSHDProcessDown
expr:node_systemd_unit_state{name="sshd.service",state="active"}!=1
for:1m
labels:
severity:critical
annotations:
summary:"sshd服務異常 ({{ $labels.instance }})"
description:"sshd服務未在運行狀態,遠程管理通道中斷"
配合node_exporter的textfile collector采集SSH指標:
#!/bin/bash
# 文件名:ssh_metrics.sh
# 功能:采集SSH相關指標,輸出為Prometheus格式
# 配合crontab每分鐘執行:* * * * * /opt/scripts/ssh_metrics.sh
METRICS_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 ||echo0)
# 當前登錄用戶數
LOGGED_USERS=$(who | wc -l)
# 最近5分鐘認證失敗次數
FAIL_COUNT=$(sudo journalctl -u sshd --since"5 minutes ago"2>/dev/null | grep -c"Failed"||echo0)
# fail2ban封禁IP數
BANNED_IPS=$(sudo fail2ban-client status sshd 2>/dev/null | grep"Currently banned"| awk'{print $NF}'||echo0)
cat >"$METRICS_FILE.tmp"<< EOF
# HELP ssh_active_connections Current number of SSH connections
# TYPE ssh_active_connections gauge
ssh_active_connections?$ACTIVE_CONN
# HELP ssh_logged_users Current number of logged in users
# TYPE ssh_logged_users gauge
ssh_logged_users?$LOGGED_USERS
# HELP ssh_auth_failures_5m SSH authentication failures in last 5 minutes
# TYPE ssh_auth_failures_5m gauge
ssh_auth_failures_5m?$FAIL_COUNT
# HELP fail2ban_banned_ips Number of IPs banned by fail2ban
# TYPE fail2ban_banned_ips gauge
fail2ban_banned_ips{jail="sshd"}?$BANNED_IPS
EOF
mv?"$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=12 mkdir -p"$BACKUP_DIR" # 備份服務端配置 sudo cp -a /etc/ssh/sshd_config"$BACKUP_DIR/" sudo cp -a /etc/ssh/ssh_config"$BACKUP_DIR/"2>/dev/null sudo 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/null sudo 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 -typed -mtime +$((KEEP_WEEKS * 7)) -execrm -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_config sudo cp"$RESTORE_DIR"/ssh_host_* /etc/ssh/ # 恢復權限 sudo chmod 600 /etc/ssh/ssh_host_*_key sudo chmod 644 /etc/ssh/ssh_host_*_key.pub sudo chmod 644 /etc/ssh/sshd_config
驗證配置:
sudo sshd -t
重啟服務:
sudo systemctl start sshd sudo systemctl status sshd ss -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 33073306 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-clientsetsshd unbanip 1.2.3.4 # 解封IP sudo fail2ban-clientsetsshd 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 | 10100 | 1060 | 未認證連接限制 |
| 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端口和文件訪問 |
-
服務器
+關注
關注
14文章
10250瀏覽量
91476 -
SSH
+關注
關注
0文章
200瀏覽量
17717
原文標題:SSH安全加固與免密登錄:企業級SSH管理方案
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Linux上建立SSH安全連接的10種方法
Ubuntu修改SSH默認端口指南
NAS教程:鐵威馬如何登錄 SSH終端?
信息安全管理必備!Linux系統使用SSH登錄root賬號的方法
SSH安全加固與免密登錄實戰指南
評論