ssh-keygen / ssh —— 密钥、免密与 SSH 加固

运维工具手册 07-自动化与版本 6.9 KB 原文

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. 常见坑

  1. 私钥泄露:立刻从所有 authorized_keys 移除并重新生成,历史提交也要清理。
  2. 私钥权限 644:sshd 直接拒绝,chmod 600。
  3. 家目录 group/other 可写:同样被拒,chmod g-w,o-w ~。
  4. -p 是端口,但 ssh-copy-id -p 也是端口,scp -P 是大写,容易混淆。
  5. authorized_keys 一行一个公钥:多行粘贴时注意不要断行。
  6. 密钥注释不影响认证,只是标识。
  7. StrictHostKeyChecking=no 有中间人风险:脚本里用 accept-new 更合适。
  8. AllowTcpForwarding no 会破坏隧道:需要转发时单独用 Match 放行。
  9. PermitRootLogin no 后仍需 root 操作:用普通用户 + sudo,别开倒车。
  10. 改端口后防火墙/SELinux 未同步:RHEL 系要 semanage port -a -t ssh_port_t -p tcp 2222 并放行 firewalld。
  11. ssh-agent -A 转发风险:跳板机被攻破等于交出你所有密钥的使用权。
  12. ~/.ssh/config 中 Host * 放最后:前面的具体规则优先,顺序错了会被覆盖。
  13. FIPS 环境限制算法:ed25519 可能被禁用,需用 rsa/ecdsa,按合规要求调整。
  14. 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