SSH 加固、免密登录与端口转发
服务器的第一道门就是 SSH。密码登录 + 22 端口 + root 允许登录,等于把门牌号和钥匙一起挂在门口。本章给出密钥登录的完整落地步骤、~/.ssh/config 的实用写法、三种端口转发的区别与用途,以及最基础的登录审计手段。
密码登录 vs 密钥登录
| 维度 | 密码登录 | 密钥登录 |
|---|---|---|
| 暴力破解难度 | 低,公网每天成千上万次尝试 | 极高,几乎不可能穷举 ed25519 私钥 |
| 凭据泄露风险 | 密码会被复用、被撞库 | 私钥只在本机,公钥泄露也无害 |
| 自动化友好度 | 需 sshpass 等不安全方案 | CI/CD、Ansible 原生支持 |
| 运维成本 | 改密码即生效 | 换机器要重新分发公钥 |
| 结论 | 仅用于应急救援 | 生产环境唯一选择 |
生成密钥并上传公钥
# 生成 ed25519 密钥(比 RSA 更短更安全,-C 是注释便于识别)
ssh-keygen -t ed25519 -C "deploy@laptop"
# 口令可留空(便于自动化),也可设置后交给 ssh-agent 缓存
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
# 上传公钥,会自动创建 ~/.ssh 与 authorized_keys 并设好权限
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@47.98.x.x
# 非默认端口:ssh-copy-id -p 2222 -i ~/.ssh/id_ed25519.pub deploy@47.98.x.x
# 先验证免密登录成功,再继续加固
ssh -i ~/.ssh/id_ed25519 deploy@47.98.x.x 'hostname && whoami'
务必先验证密钥能登录,再禁用密码登录,否则只能去控制台走 VNC 救援。
加固 sshd_config
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo vim /etc/ssh/sshd_config
# /etc/ssh/sshd_config 关键片段
Port 2222 # 改端口挡扫描(降噪,不是安全屏障)
PermitRootLogin no # 禁止 root 直接登录,用普通用户 + sudo
PasswordAuthentication no # 关闭密码登录,只认密钥
PubkeyAuthentication yes # 打开公钥认证
KbdInteractiveAuthentication no # 关闭键盘交互,防绕过密码策略
PermitEmptyPasswords no
MaxAuthTries 3 # 认证失败 3 次即断开
LoginGraceTime 30 # 30 秒内必须完成认证
ClientAliveInterval 300 # 5 分钟无响应发保活包
AllowUsers deploy ops # 白名单,只有这两个用户能登录
UseDNS no # 关闭反向解析,加快登录速度
正确顺序是:建普通用户 → 传公钥 → 验证免密登录 → 改 sshd_config → sshd -t 校验 → reload。
应用配置并校验
sudo sshd -t && echo config-ok # 语法校验
sudo systemctl reload sshd # 平滑重载,不断开现有连接
# 部分 Debian 版本服务名是 ssh:sudo systemctl reload ssh
# 另开一个终端验证新配置可登录,两件事都通过后再关掉旧会话
ssh -p 2222 deploy@47.98.x.x 'echo ok'
# 确认端口真的换了,且三处已同步(安全组、防火墙、sshd)
sudo ss -lntp | grep sshd
sudo ufw allow 2222/tcp && sudo ufw delete allow 22/tcp
~/.ssh/config:把长命令变成别名
# ~/.ssh/config(本机文件,权限 600,否则 ssh 会拒绝使用)
Host prod
HostName 47.98.x.x
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
Host db
HostName 10.0.0.8 # 内网地址,只能经跳板机到达
User deploy
ProxyJump prod
配置完成后 ssh prod、scp file prod:/srv/www/、ssh db 都能直接用;ProxyJump 替代了早年的 -o ProxyCommand="ssh -W %h:%p" 写法。
三种端口转发
| 类型 | 命令形式 | 方向 | 典型用途 |
|---|---|---|---|
| 本地转发 | ssh -L 13306:127.0.0.1:3306 prod | 本地端口 → 远端服务 | 本地客户端连生产库、访问内网面板 |
| 远程转发 | ssh -R 9000:127.0.0.1:3000 prod | 远端端口 → 本地服务 | 把本地开发服务临时给别人调试 |
| 动态转发 | ssh -D 1080 prod | 本地 SOCKS5 代理 | 浏览器走服务器出口访问内网资源 |
# 本地转发:生产 MySQL 全程不暴露,本地用 13306 连接
ssh -N -L 13306:127.0.0.1:3306 deploy@47.98.x.x
mysql -h 127.0.0.1 -P 13306 -u appuser -p
# 远程转发:默认只绑远端 127.0.0.1,需 GatewayPorts yes 才能对外
ssh -N -R 9000:127.0.0.1:3000 deploy@47.98.x.x
# -f 转后台、-N 不执行远程命令、-C 压缩
ssh -fNC -L 13306:127.0.0.1:3306 prod
fail2ban 与登录审计
# fail2ban:连续认证失败即封禁来源 IP,务必配 ignoreip 白名单
sudo apt -y install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# 在 jail.local 的 [sshd] 段改:enabled=true, maxretry=3, bantime=1h, port=2222
sudo systemctl enable --now fail2ban && sudo fail2ban-client status sshd
# 登录审计
last -a | head -20 # 最近成功登录
sudo lastb | head -20 # 最近失败登录
sudo tail -f /var/log/auth.log # RHEL 系为 /var/log/secure
sudo grep 'Failed password' /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
| 先关密码登录,公钥还没传 | 彻底失联,只能控制台救援 | 顺序:建用户 → 传公钥 → 验证 → 再关密码 |
| 改端口后忘了改安全组 | 新端口连不上,旧端口已关 | 三处同步:sshd_config、防火墙、安全组 |
| 私钥权限过松 | 报 UNPROTECTED PRIVATE KEY FILE | chmod 600 私钥、chmod 700 ~/.ssh |
| 私钥提交到 Git | 密钥永久泄露 | 私钥永不入库,.gitignore 加 .ssh/、*.pem |
| fail2ban 白名单没设 | 自己的出口 IP 被封 | ignoreip 写死固定的运维出口 IP |
改完直接 restart sshd | 现有会话被断,无法验证 | 用 reload,并另开会话验证 |
小结:SSH 加固的顺序是建普通用户、上传 ed25519 公钥、验证免密、再关 PasswordAuthentication、PermitRootLogin no 并用 AllowUsers 收窄范围;~/.ssh/config 用别名与 ProxyJump 统一入口;端口转发让数据库永远不用对外暴露;最后用 fail2ban 与 lastb 把爆破挡在门外并定期审计。