ssh-keygen / ssh —— 密钥、免密与 SSH 加固
ssh-keygen / ssh —— 密钥、免密与 SSH 加固
分类:07 自动化与版本 适用:OpenSSH 7.x ~ 9.x 一句话:免密登录是自动化(Ansible、rsync、CI)的前提,SSH 配置则是服务器安全的第一道门。
1. 它解决什么问题
- 每次
ssh/scp都输密码,脚本没法跑。 - 批量管理上百台机器,需要统一的密钥分发。
- 服务器被暴力破解,要加固 SSH。
- 用跳板机访问内网,不暴露内网端口。
2. 生成密钥
ssh-keygen -t ed25519 -C "ops@example.com"
ssh-keygen -t ed25519 -C "ops@example.com" -f ~/.ssh/id_ed25519_ops
ssh-keygen -t rsa -b 4096 -C "legacy@example.com" # 老系统兼容
| 类型 | 建议 | 说明 |
|---|---|---|
ed25519 |
首选 | 短、快、安全,OpenSSH 6.5+ |
rsa -b 4096 |
兼容老设备 | 部分老旧系统/设备不支持 ed25519 |
ecdsa |
不推荐 | 有实现争议 |
dsa |
禁用 | 已不安全 |
生成后会得到:
~/.ssh/id_ed25519 私钥(600,绝不外传)
~/.ssh/id_ed25519.pub 公钥(可自由分发)
passphrase 建议设置,配合 ssh-agent 免重复输入。CI 场景可用空口令密钥,但必须限制权限与来源。
3. 分发公钥
ssh-copy-id -i ~/.ssh/id_ed25519.pub ops@10.0.0.11
ssh-copy-id -p 2222 -i ~/.ssh/id_ed25519.pub ops@host
# 手动方式(没有 ssh-copy-id 时)
cat ~/.ssh/id_ed25519.pub | ssh ops@host 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
权限必须正确,否则 sshd 拒绝使用:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub ~/.ssh/known_hosts
chmod 700 ~/.ssh # 家目录不能是 777
chmod g-w,o-w ~
4. ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l # 已加载的密钥
ssh-add -D # 全部移除
# 转发 agent 到跳板机后的机器(谨慎开启)
ssh -A jump
~/.ssh/config 里可指定密钥,避免依赖 agent:
Host *
AddKeysToAgent yes
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
5. ~/.ssh/config
Host web01
HostName 10.0.0.11
User ops
Port 22
IdentityFile ~/.ssh/id_ed25519_ops
Host 10.0.0.*
User ops
StrictHostKeyChecking accept-new
Host internal-*
ProxyJump jump
Host jump
HostName 1.2.3.4
User ops
Port 2222
IdentityFile ~/.ssh/id_ed25519_jump
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
TCPKeepAlive yes
之后直接:
ssh web01
ssh internal-db01
scp file web01:/tmp/
rsync -av dir/ web01:/data/
ProxyJump(跳板机)
ssh -J ops@jump:2222 ops@10.0.0.21
ssh -o ProxyJump=jump internal-db
比 -L 端口转发简单,且支持多级 -J a,b,c。
6. 密钥管理
# 改口令
ssh-keygen -p -f ~/.ssh/id_ed25519
# 从私钥导出公钥
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
# 查看指纹
ssh-keygen -lf ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/id_ed25519.pub -E md5
# 更换注释
ssh-keygen -c -C "new-comment" -f ~/.ssh/id_ed25519
# known_hosts 操作
ssh-keygen -F 10.0.0.11 # 查找主机
ssh-keygen -R 10.0.0.11 # 删除(重装系统后必做)
ssh-keyscan -p 22 10.0.0.11 >> ~/.ssh/known_hosts
证书模式(大规模场景)
用 CA 签发短期证书,避免逐台分发 authorized_keys:
# 1. 生成 CA
ssh-keygen -t ed25519 -f ca_key -C "ssh-ca"
# 2. 服务端信任 CA
echo "TrustedUserCAKeys /etc/ssh/ca_key.pub" >> /etc/ssh/sshd_config
# 重启 sshd
# 3. 给用户签发证书(有效期 8 小时)
ssh-keygen -s ca_key -I "alice@corp" -n alice,ops -V +8h ~/.ssh/id_ed25519.pub
# 生成 id_ed25519-cert.pub,随私钥一起使用
优点:可设有效期、可绑定用户名与来源、可集中吊销。适合几十台以上的环境。
7. 限制公钥能力(authorized_keys 选项)
# 只允许执行备份命令,禁止 shell 与端口转发
command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding ssh-ed25519 AAAA... backup@host
# 限制来源 IP
from="10.0.0.0/24",no-pty ssh-ed25519 AAAA... deploy@ci
# 只允许端口转发
no-pty,permitopen="10.0.0.21:3306" ssh-ed25519 AAAA... tunnel@host
这些选项是"最小权限"的落地手段,尤其适合给第三方/自动化账号。
8. SSH 服务端加固
/etc/ssh/sshd_config:
Port 22
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers ops deploy
# 或 AllowGroups sshusers
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no # 需要隧道时改为 yes 或用 Match
PermitTunnel no
按用户细分:
Match User backup
AllowTcpForwarding no
PermitTTY no
ForceCommand /usr/local/bin/backup-shell.sh
验证与生效:
sshd -t # 语法检查(必须先做)
systemctl reload sshd
# 重载前保持一个已登录会话,防止配置错误锁死自己
改 SSH 前务必保留一个活动会话,新开窗口验证能登录后再关闭旧会话。
9. 实战场景
9.1 批量免密(Ansible 前置)
for h in 10.0.0.11 10.0.0.12 10.0.0.13; do
ssh-copy-id -i ~/.ssh/id_ed25519.pub ops@$h
done
# 验证
for h in 10.0.0.11 10.0.0.12 10.0.0.13; do
ssh -o BatchMode=yes -o ConnectTimeout=3 ops@$h 'echo ok' || echo "FAIL $h"
done
BatchMode=yes 禁止交互提示,是脚本检测的关键。
9.2 迁移机器后 known_hosts 报冲突
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
ssh-keygen -R 10.0.0.11
ssh -o StrictHostKeyChecking=accept-new ops@10.0.0.11
先确认不是中间人攻击(对比主机指纹、确认是重装),再删记录。
9.3 只允许某密钥做端口转发
no-pty,command="/bin/false",permitopen="10.0.0.21:3306" ssh-ed25519 AAAA... bi@corp
配合 autossh 做稳定的数据库隧道。
9.4 排查"密钥登录仍要密码"
ssh -vvv ops@host 2>&1 | grep -E 'Offering|Authentications|denied|publickey'
常见原因:
| 现象 | 原因 |
|---|---|
Permission denied (publickey) |
公钥没在 authorized_keys,或权限不对 |
| 一直提示密码 | 服务端 PasswordAuthentication yes 且公钥失败 |
bad permissions |
.ssh 或私钥权限过宽 |
no mutual signature algorithm |
老服务器不支持 ed25519,改用 rsa |
Too many authentication failures |
agent 里密钥太多,加 IdentitiesOnly yes |
服务端日志:
journalctl -u sshd -f
tail -f /var/log/secure # RHEL
tail -f /var/log/auth.log # Debian
9.5 用 ssh -G 检查最终生效配置
ssh -G web01 | grep -E 'hostname|user|port|identityfile|proxyjump'
排查 config 写错、优先级混乱时非常有用。
10. 常见坑
- 私钥泄露:立刻从所有
authorized_keys移除并重新生成,历史提交也要清理。 - 私钥权限 644:sshd 直接拒绝,
chmod 600。 - 家目录 group/other 可写:同样被拒,
chmod g-w,o-w ~。 -p是端口,但ssh-copy-id -p也是端口,scp -P是大写,容易混淆。authorized_keys一行一个公钥:多行粘贴时注意不要断行。- 密钥注释不影响认证,只是标识。
StrictHostKeyChecking=no有中间人风险:脚本里用accept-new更合适。AllowTcpForwarding no会破坏隧道:需要转发时单独用Match放行。PermitRootLogin no后仍需 root 操作:用普通用户 + sudo,别开倒车。- 改端口后防火墙/SELinux 未同步:RHEL 系要
semanage port -a -t ssh_port_t -p tcp 2222并放行 firewalld。 ssh-agent -A转发风险:跳板机被攻破等于交出你所有密钥的使用权。~/.ssh/config中Host *放最后:前面的具体规则优先,顺序错了会被覆盖。- FIPS 环境限制算法:ed25519 可能被禁用,需用 rsa/ecdsa,按合规要求调整。
known_hosts定期清理:云环境 IP 复用会频繁冲突。
11. 速查表
ssh-keygen -t ed25519 -C "comment"
ssh-keygen -t rsa -b 4096 -C "legacy"
ssh-keygen -p -f <key> # 改口令
ssh-keygen -y -f <key> > <key>.pub # 导出公钥
ssh-keygen -lf <key>.pub # 指纹
ssh-keygen -R <host> # 删 known_hosts
ssh-keygen -F <host> # 查 known_hosts
ssh-copy-id -i <key>.pub user@host
ssh-copy-id -p 2222 -i <key>.pub user@host
eval "$(ssh-agent -s)" && ssh-add <key>
ssh-add -l / -D
ssh -J jump user@internal
ssh -G host # 查看生效配置
ssh -vvv host # 调试
ssh -o BatchMode=yes host true # 非交互检测
# 加固要点
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
AllowUsers ops
sshd -t && systemctl reload sshd