强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

SSH 服务器完全指南 / 第4章 密钥认证与证书

第4章 密钥认证与证书

4.1 密钥认证原理

SSH 密钥认证基于非对称加密的挑战-响应机制:

客户端                                    服务器
  │                                          │
  │  1. 发送公钥 ID ────────────────────────>│
  │                                          │  检查 authorized_keys
  │<── 2. 返回用公钥加密的随机挑战 ──────────│
  │                                          │
  │  3. 用私钥解密挑战                        │
  │     生成响应(含会话 ID + 挑战结果)     │
  │     发送响应 ───────────────────────────>│
  │                                          │  用公钥验证响应
  │<── 4. 认证成功 ─────────────────────────│

优势对比:

方面密码认证密钥认证
暴力破解风险高(弱密码)极低(256/4096 bit)
中间人攻击可能防范
自动化使用需要明文存储密码安全使用 ssh-agent
密钥轮换改密码替换公钥
多因素支持可以可以

4.2 authorized_keys 文件详解

文件位置

# 用户级授权文件(默认)
~/.ssh/authorized_keys

# 系统级授权文件(需要在 sshd_config 中配置)
/etc/ssh/authorized_keys/%u

# 自定义路径(在 sshd_config 中指定)
AuthorizedKeysFile /etc/ssh/authorized_keys/%u .ssh/authorized_keys

文件格式

每行一个公钥,格式为:

[选项] 算法类型 密钥数据 注释

完整示例:

# ~/.ssh/authorized_keys

# 普通公钥(无选项)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx admin@workstation

# 带选项的公钥
from="192.168.1.0/24",command="/usr/local/bin/restricted.sh",no-port-forwarding,no-X11-forwarding ssh-rsa AAAAB3... deploy@ci

# 另一个普通公钥
ssh-rsa AAAAB3NzaC1yc2EAAAA... user@laptop

常用公钥选项

选项说明
from="pattern"限制来源 IP/主机
command="cmd"只允许执行指定命令
no-port-forwarding禁止端口转发
no-X11-forwarding禁止 X11 转发
no-agent-forwarding禁止 Agent 转发
no-pty不分配伪终端
no-user-rc不执行 ~/.ssh/rc
permitopen="host:port"只允许转发到指定地址
environment="KEY=value"设置环境变量
expiry-time="20251231"公钥过期时间(OpenSSH 8.7+)
cert-authority标记为证书授权密钥
principals="user1,user2"允许的证书主体

配置选项示例

限制来源 IP

# 只允许从公司网络连接
from="10.0.0.0/8" ssh-ed25519 AAAAC3... admin@company

# 允许多个网段
from="192.168.1.*,10.0.0.*" ssh-ed25519 AAAAC3... admin@office

限制只能执行特定命令

# 只允许执行 backup 脚本
command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3... backup@ci

部署专用密钥(只读 Git)

# Git 服务器专用:只允许 git 操作
command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3... git@deploy

多个选项组合

# CI/CD 部署密钥:只允许从特定 IP,只执行部署脚本
from="192.168.1.100",command="/opt/deploy/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3... deploy@jenkins

4.3 公钥部署方法

方法一:ssh-copy-id(推荐)

# 自动部署公钥到远程服务器
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host

# 使用特定端口
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@remote-host

# 使用别名
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host

注意: ssh-copy-id 需要密码登录(或已有的密钥认证)才能工作。

方法二:手动复制

# 本地复制公钥内容
cat ~/.ssh/id_ed25519.pub | ssh user@remote-host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# 或者分步操作
# 1. 复制公钥
cat ~/.ssh/id_ed25519.pub
# 2. 粘贴到远程服务器
ssh user@remote-host
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "粘贴的公钥" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

方法三:Ansible 批量部署

# deploy-keys.yml
---
- name: Deploy SSH public keys
  hosts: all
  tasks:
    - name: Ensure .ssh directory exists
      file:
        path: ~/.ssh
        state: directory
        mode: '0700'

    - name: Deploy authorized_keys
      authorized_key:
        user: "{{ item.user }}"
        key: "{{ lookup('file', item.key_file) }}"
        state: present
      loop:
        - { user: 'admin', key_file: '~/.ssh/id_ed25519.pub' }
        - { user: 'deploy', key_file: '~/.ssh/id_deploy.pub' }

方法四:批量脚本部署

#!/bin/bash
# deploy-keys.sh

SERVERS="web01 web02 web03 db01 db02"
KEY_FILE="$HOME/.ssh/id_ed25519.pub"
USER="admin"

for server in $SERVERS; do
    echo "Deploying key to $server..."
    ssh-copy-id -i "$KEY_FILE" "$USER@$server" 2>/dev/null
    if [ $? -eq 0 ]; then
        echo "  ✅ Success"
    else
        echo "  ❌ Failed"
    fi
done

4.4 密钥管理最佳实践

一对密钥 vs 多对密钥

策略优点缺点适用场景
一对密钥通用管理简单泄露影响大个人开发机
按环境分离影响可控管理稍复杂开发/生产分离
按用途分离最安全管理复杂企业环境

推荐的密钥分离方案

~/.ssh/
├── id_personal          # 个人开发机
├── id_personal.pub
├── id_work              # 工作环境
├── id_work.pub
├── id_prod              # 生产环境(最严格保护)
├── id_prod.pub
├── id_deploy            # CI/CD 部署
├── id_deploy.pub
├── id_github            # GitHub/GitLab
├── id_github.pub
└── config               # 客户端配置

密钥轮换

定期更换 SSH 密钥是安全最佳实践:

#!/bin/bash
# rotate-key.sh - 密钥轮换脚本

SERVER="$1"
NEW_KEY="$HOME/.ssh/id_ed25519_new.pub"
OLD_KEY="$HOME/.ssh/id_ed25519.pub"

if [ -z "$SERVER" ]; then
    echo "Usage: $0 user@server"
    exit 1
fi

# 1. 部署新密钥
echo "Deploying new key to $SERVER..."
ssh-copy-id -i "$NEW_KEY" "$SERVER"

# 2. 验证新密钥可用
echo "Verifying new key..."
ssh -i "$HOME/.ssh/id_ed25519_new" "$SERVER" "echo 'New key works!'"

if [ $? -eq 0 ]; then
    # 3. 删除旧密钥
    echo "Removing old key..."
    ssh -i "$HOME/.ssh/id_ed25519_new" "$SERVER" \
        "sed -i '$(grep -c . $OLD_KEY)d' ~/.ssh/authorized_keys"
    
    # 4. 本地替换
    mv "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_ed25519.old"
    mv "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ed25519.pub.old"
    mv "$HOME/.ssh/id_ed25519_new" "$HOME/.ssh/id_ed25519"
    mv "$HOME/.ssh/id_ed25519_new.pub" "$HOME/.ssh/id_ed25519.pub"
    
    echo "✅ Key rotation complete"
else
    echo "❌ New key verification failed. Old key still in place."
    exit 1
fi

清理过期/不再使用的密钥

# 在服务器上查看 authorized_keys
cat ~/.ssh/authorized_keys

# 列出每个密钥的指纹和注释
while IFS= read -r line; do
    echo "$line" | ssh-keygen -lf -
done < ~/.ssh/authorized_keys

# 删除特定用户/机器的公钥
# 假设注释为 user@old-laptop
sed -i '/user@old-laptop/d' ~/.ssh/authorized_keys

4.5 SSH 证书认证

为什么需要证书?

传统密钥管理的痛点:

痛点说明
密钥分发每台服务器都要部署公钥(N×M 问题)
主机信任用户需要手动验证每台服务器的指纹
密钥撤销没有集中的撤销机制
短期访问难以实现临时/一次性访问

SSH 证书通过引入证书授权机构 (CA) 解决了这些问题:

证书认证工作原理

┌─────────────┐      ┌───────────────┐      ┌─────────────┐
│    用户      │      │     CA        │      │   服务器     │
│             │      │  (签署证书)    │      │             │
│ 1. 生成密钥  │      │               │      │             │
│    ──────────────>  │               │      │             │
│             │      │ 2. 签发证书    │      │             │
│  <────────────────│               │      │             │
│             │      │               │      │             │
│ 3. 提交证书  │      │               │      │             │
│    ─────────────────────────────────────>  │             │
│             │      │               │      │ 4. 验证证书  │
│             │      │               │      │    (用 CA 公钥)│
│             │      │               │      │             │
│ <════════════════════════════════════════════ 允许登录   │
└─────────────┘      └───────────────┘      └─────────────┘

创建 SSH CA

用户证书 CA

# 1. 生成 CA 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/ca_user_key -C "User CA"
# 设置强密码!

# 保护 CA 私钥
chmod 600 ~/.ssh/ca_user_key

主机证书 CA

# 生成主机 CA 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/ca_host_key -C "Host CA"
chmod 600 ~/.ssh/ca_host_key

签发用户证书

# 用户提交公钥,CA 签发证书
# 1. 用户生成密钥(如果没有)
ssh-keygen -t ed25519 -f ~/.ssh/id_work

# 2. CA 签发证书
ssh-keygen -s ~/.ssh/ca_user_key \
    -I "admin-cert-2025" \
    -n "admin,root" \
    -V "+52w" \
    -z 1 \
    ~/.ssh/id_work.pub

参数说明:

参数说明
-sCA 私钥路径
-I证书标识(用于审计)
-n允许的用户名(principal)
-V有效期(+52w = 52周)
-z序列号(用于追踪)
# 生成的证书文件
# ~/.ssh/id_work-cert.pub

# 查看证书内容
ssh-keygen -Lf ~/.ssh/id_work-cert.pub

输出示例:

/home/user/.ssh/id_work-cert.pub:
        Type: [email protected] user certificate
        Public key: ED25519-CERT SHA256:xxxxx
        Signing CA: ED25519 SHA256:yyyyy (using ssh-ed25519)
        Key ID: "admin-cert-2025"
        Serial: 1
        Valid: from 2025-01-01T00:00:00 to 2025-12-31T23:59:59
        Principals: 
                admin
                root
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

签发主机证书

# 在服务器上执行
# 1. 生成主机密钥(如果还没有)
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""

# 2. 将主机公钥发给 CA 签署
sudo scp /etc/ssh/ssh_host_ed25519_key.pub ca-admin@ca-server:/tmp/

# 3. CA 签发主机证书
ssh-keygen -s ~/.ssh/ca_host_key \
    -I "web01-host-cert" \
    -h \
    -n "web01.example.com,192.168.1.100,10.0.0.100" \
    -V "+52w" \
    /tmp/ssh_host_ed25519_key.pub

# 4. 将证书发回服务器
sudo scp ca-admin@ca-server:/tmp/ssh_host_ed25519_key-cert.pub /etc/ssh/

# 5. 配置 sshd 使用证书
# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

配置服务器信任 CA

# /etc/ssh/sshd_config

# 信任用户 CA(用于认证用户)
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
# 将用户 CA 公钥部署到服务器
sudo cp ~/.ssh/ca_user_key.pub /etc/ssh/ca_user_key.pub
sudo chmod 644 /etc/ssh/ca_user_key.pub
sudo chown root:root /etc/ssh/ca_user_key.pub

配置客户端信任主机 CA

# ~/.ssh/known_hosts

# 添加主机 CA 公钥(信任由该 CA 签署的所有主机)
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxx

SSH 证书的优势总结

特性传统密钥SSH 证书
密钥分发N×M 部署只需信任 CA
主机验证手动确认指纹自动信任 CA 签署的主机
过期机制支持时间限制
集中管理困难CA 集中签发/撤销
审计追踪有限证书 ID 可追踪
短期访问实现复杂签发短周期证书

4.6 使用场景

场景一:企业统一身份管理

# 部署方案:
# 1. 创建公司 CA
# 2. HR 系统离职时自动停止签发证书
# 3. 证书有效期设为 8 小时(一天的工作时间)
# 4. 员工每天通过 SSO 申请新证书

# 自动化签发脚本
#!/bin/bash
# get-cert.sh - 通过 SSO 获取日证书

TOKEN=$(sso-authenticate)
ssh-keygen -t ed25519 -f ~/.ssh/id_daily -N "" -C "$(whoami)-daily"
curl -H "Authorization: Bearer $TOKEN" \
    -F "pubkey=@$HOME/.ssh/id_daily.pub" \
    -o ~/.ssh/id_daily-cert.pub \
    https://ca.internal/sign-user

echo "Certificate issued. Valid for 8 hours."

场景二:临时运维访问

# 打包工单系统签发 2 小时临时证书
ssh-keygen -s /etc/ssh/ca_ops_key \
    -I "ticket-12345-$(whoami)" \
    -n "$TARGET_USER" \
    -V "+2h" \
    -O "force-command=/usr/local/bin/ops-script.sh" \
    -O "no-port-forwarding" \
    ~/.ssh/id_temp.pub

场景三:大规模集群管理

# 100 台服务器,传统方式需要部署 100 次公钥
# 证书方式:只需在每台服务器上部署 CA 公钥(一次)

# 批量部署 CA 公钥
ansible all -m copy -a \
    "src=ca_user_key.pub dest=/etc/ssh/ca_user_key.pub mode=0644"

ansible all -m lineinfile -a \
    "path=/etc/ssh/sshd_config line='TrustedUserCAKeys /etc/ssh/ca_user_key.pub'"

ansible all -m systemd -a "name=sshd state=reloaded"

扩展阅读


下一章: 第5章 密码认证与多因素认证 → 学习密码认证、PAM 集成和多因素认证配置。