Skip to content
基于 SSH CA 的集中密钥管理 - 完整实现过程
  • 网络扑拓
bash
    (颁发证书)                (持有证书)                (信任CA)
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
 192.168.168.71 192.168.165.72 192.168.165.73
     CA host     │────▶│   client host   │────▶│   server host
└─────────────────┘     └─────────────────┘     └─────────────────┘
  • CA服务器上操作(第一阶段)

    • 环境准备
    bash
    # 登录到 CA 服务器
    ssh root@192.168.168.71
    
    # 1. 创建 SSH CA 目录结构
    mkdir -p /etc/ssh-ca/{users,hosts}/{certs,private,public,crl}
    mkdir -p /etc/ssh-ca/templates
    mkdir -p /etc/ssh-ca/bin
    
    # 2. 设置严格权限
    chmod 700 /etc/ssh-ca/users/private
    chmod 700 /etc/ssh-ca/hosts/private
    chmod 755 /etc/ssh-ca/{users,hosts}/{public,certs}
    chown root:root /etc/ssh-ca -R
    
    # 3. 记录服务器信息(便于后续使用)
    cat > /etc/ssh-ca/server-info.txt << EOF
    CA 服务器: 192.168.168.71
    客户端服务器: 192.168.165.72
    目标服务器: 192.168.165.73
    EOF
    • 创建 CA 根密钥对
    bash
    # 进入 CA 目录
    cd /etc/ssh-ca
    
    # 1. 创建用户 CA 密钥对(用于验证用户身份)
    ssh-keygen -t rsa -b 4096 \
    -f users/private/user-ca \
    -C "SSH User Certificate Authority" \
    -N ""  # 不设置密码(生产环境建议设置)
    
    # 2. 创建主机 CA 密钥对(可选,用于验证服务器身份)
    ssh-keygen -t rsa -b 4096 \
    -f hosts/private/host-ca \
    -C "SSH Host Certificate Authority" \
    -N ""
    
    # 3. 将公钥复制到公共目录
    cp users/private/user-ca.pub users/public/
    cp hosts/private/host-ca.pub hosts/public/
    
    # 4. 查看生成的密钥
    echo "=== 用户 CA 公钥 ==="
    cat users/public/user-ca.pub
    echo -e "\n=== 主机 CA 公钥 ==="
    cat hosts/public/host-ca.pub
    
    # 5. 记录公钥指纹(重要!)
    ssh-keygen -lf users/private/user-ca
    ssh-keygen -lf hosts/private/host-ca
    • 创建用户证书签发脚本
    bash
    #!/bin/bash
    # SSH 用户证书签发脚本
    # 用法: issue-user-cert.sh <用户名> [有效期天数] [权限选项]
    
    set -e
    
    # 配置文件
    CA_KEY="/etc/ssh-ca/users/private/user-ca"
    CERT_DIR="/etc/ssh-ca/users/certs"
    LOG_DIR="/var/log/ssh-ca"
    LOG_FILE="$LOG_DIR/user-certs.log"
    
    # 创建日志目录
    mkdir -p "$LOG_DIR"
    touch "$LOG_FILE"
    
    # 参数检查
    if [ $# -lt 1 ]; then
        echo "错误: 缺少参数"
        echo "用法: $0 <用户名> [有效期天数] [权限选项]"
        echo "示例: $0 alice 30 'no-port-forwarding,no-x11-forwarding'"
        exit 1
    fi
    
    USERNAME="$1"
    VALIDITY_DAYS="${2:-30}"  # 默认30天
    OPTIONS="${3:-}"
    PRINCIPALS="$USERNAME,admin"  # 允许登录的用户名(可登录为多个用户)
    
    # 用户公钥文件(假设从客户端上传)
    USER_PUB_KEY="/tmp/${USERNAME}-ssh-pubkey-$(date +%s).pub"
    
    echo "请将用户的公钥内容粘贴到下面 (以 ssh-rsa/ssh-ed25519 开头,以用户邮箱或注释结尾)"
    echo "粘贴完成后按 Ctrl+D"
    cat > "$USER_PUB_KEY"
    
    # 验证公钥格式
    if ! ssh-keygen -l -f "$USER_PUB_KEY" &>/dev/null; then
        echo "错误: 无效的公钥格式"
        rm -f "$USER_PUB_KEY"
        exit 1
    fi
    
    # 生成唯一证书ID
    CERT_ID="${USERNAME}-$(date +%Y%m%d-%H%M%S)"
    CERT_FILE="${CERT_DIR}/${CERT_ID}.pub"
    
    # 计算有效期
    VALIDITY="+${VALIDITY_DAYS}d"
    
    # 构建签发命令
    SIGN_CMD="ssh-keygen -s \"$CA_KEY\" -I \"$CERT_ID\" -n \"$PRINCIPALS\" -V \"$VALIDITY\""
    
    # 添加额外选项
    if [ -n "$OPTIONS" ]; then
        # 解析选项并添加到命令
        IFS=',' read -ra OPTS <<< "$OPTIONS"
        for opt in "${OPTS[@]}"; do
            SIGN_CMD="$SIGN_CMD -O $opt"
        done
    fi
    
    # 添加源地址限制(示例:仅允许从特定网络访问)
    # SIGN_CMD="$SIGN_CMD -O source-address=\"192.168.165.0/24\""
    
    # 完成命令
    SIGN_CMD="$SIGN_CMD \"$USER_PUB_KEY\""
    
    echo "正在签发证书..."
    eval "$SIGN_CMD"
    
    # 移动证书到存储目录
    PUB_KEY_NAME=$(echo ${USER_PUB_KEY}|awk -F'.' '{print $1}')
    mv "${PUB_KEY_NAME}-cert.pub" "$CERT_FILE"
    
    # 清理临时文件
    rm -f "$USER_PUB_KEY"
    
    # 记录日志
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 用户: $USERNAME - 证书: $CERT_ID - 有效期: $VALIDITY_DAYS天" >> "$LOG_FILE"
    
    echo ""
    echo "========== 证书签发成功 =========="
    echo "证书文件: $CERT_FILE"
    echo "证书ID: $CERT_ID"
    echo "有效期: $VALIDITY_DAYS 天"
    echo "允许的用户: $PRINCIPALS"
    echo "=================================="
    echo ""
    echo "请将以下证书内容发送给用户:"
    cat "$CERT_FILE"
    • 创建主机证书签发脚本(可选)
    bash
    #!/bin/bash
    # SSH 主机证书签发脚本
    
    set -e
    
    CA_KEY="/etc/ssh-ca/hosts/private/host-ca"
    CERT_DIR="/etc/ssh-ca/hosts/certs"
    HOSTNAME="$1"
    
    if [ -z "$HOSTNAME" ]; then
        echo "用法: $0 <主机名或IP>"
        exit 1
    fi
    
    # 从目标服务器获取主机公钥(需要提前配置SSH密钥登录)
    echo "正在从 $HOSTNAME 获取主机公钥..."
    scp root@$HOSTNAME:/etc/ssh/ssh_host_rsa_key.pub /tmp/host-key-${HOSTNAME}.pub 2>/dev/null || {
        echo "无法获取主机公钥,请手动提供"
        exit 1
    }
    
    HOST_PUB_KEY="/tmp/host-key-${HOSTNAME}.pub"
    
    # 签发主机证书(有效期为2年)
    ssh-keygen -s "$CA_KEY" \
        -I "host-${HOSTNAME}-$(date +%Y%m%d)" \
        -h \  # 重要:标记为主机证书
        -n "$HOSTNAME, $(dig +short $HOSTNAME | tr '\n' ',')" \
        -V "-1w:+104w" \  # 一周前生效,104周后过期
        "$HOST_PUB_KEY"
    
    # 移动证书
    mv "${HOST_PUB_KEY}-cert.pub" "${CERT_DIR}/host-${HOSTNAME}.pub"
    
    # 将证书发送回目标服务器
    scp "${CERT_DIR}/host-${HOSTNAME}.pub" root@$HOSTNAME:/etc/ssh/ssh_host_rsa_key-cert.pub
    
    echo "主机证书已签发并部署到 $HOSTNAME"
    • 创建证书吊销脚本
    bash
    #!/bin/bash
    # SSH 证书吊销脚本
    
    set -e
    
    CRL_FILE="/etc/ssh-ca/users/crl"
    CA_KEY="/etc/ssh-ca/users/private/user-ca"
    
    if [ $# -lt 1 ]; then
        echo "用法: $0 <证书文件或公钥文件> [原因]"
        exit 1
    fi
    
    CERT_FILE="$1"
    REASON="${2:-revoked-by-admin}"
    
    # 如果提供的是证书文件,提取其中的公钥
    if [[ "$CERT_FILE" == *-cert.pub ]]; then
        # 从证书中提取公钥
        TMP_PUBKEY="/tmp/pubkey-$(basename $CERT_FILE)"
        ssh-keygen -L -f "$CERT_FILE" | grep -A1 "Public key" | tail -1 | sed 's/^ *//' > "$TMP_PUBKEY"
        KEY_TO_REVOKE="$TMP_PUBKEY"
    else
        KEY_TO_REVOKE="$CERT_FILE"
    fi
    
    # 添加到吊销列表
    if [ ! -f "$CRL_FILE" ]; then
        ssh-keygen -k -f "$CRL_FILE"
    fi
    
    ssh-keygen -u -f "$CRL_FILE" -s "$CA_KEY" -z "1" "$KEY_TO_REVOKE"
    
    # 清理临时文件
    [ -f "$TMP_PUBKEY" ] && rm -f "$TMP_PUBKEY"
    
    echo "证书已吊销"
    echo "请将吊销列表 $CRL_FILE 分发到所有服务器"
    • 准备分发脚本
    bash
    #!/bin/bash
    # 分发 CA 公钥到目标服务器
    
    set -e
    
    USER_CA_PUB="/etc/ssh-ca/users/public/user-ca.pub"
    HOST_CA_PUB="/etc/ssh-ca/hosts/public/host-ca.pub"
    
    # 目标服务器列表
    SERVERS=("192.168.165.73")  # 只需要目标服务器信任CA
    
    for SERVER in "${SERVERS[@]}"; do
        echo "分发到 $SERVER..."
        
        # 分发用户 CA 公钥
        scp "$USER_CA_PUB" root@$SERVER:/etc/ssh/user-ca.pub
        
        # 在目标服务器上配置信任CA
        ssh root@$SERVER << 'REMOTE_EOF'
            # 确保 SSH 目录存在
            mkdir -p /etc/ssh
            
            # 备份原始配置
            cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)
            
            # 检查是否已配置 TrustedUserCAKeys
            if ! grep -q "TrustedUserCAKeys" /etc/ssh/sshd_config; then
                echo "TrustedUserCAKeys /etc/ssh/user-ca.pub" >> /etc/ssh/sshd_config
            else
                sed -i 's|^TrustedUserCAKeys.*|TrustedUserCAKeys /etc/ssh/user-ca.pub|' /etc/ssh/sshd_config
            fi
            
            # 确保权限正确
            chmod 644 /etc/ssh/user-ca.pub
            chown root:root /etc/ssh/user-ca.pub
            
            # 重启 SSH 服务
            systemctl restart sshd
            echo "已为 $SERVER 配置信任 CA"
    REMOTE_EOF
        
        echo "完成 $SERVER"
    done
    
    echo "所有服务器配置完成"
  • 在目标服务器:192.168.165.73上操作(第二阶段)

    • 配置信任 CA
    bash
    # 登录到目标服务器
    ssh root@192.168.165.73
    
    # 1. 从 CA 服务器获取公钥(或者等待 CA 服务器分发)
    scp root@192.168.168.71:/etc/ssh-ca/users/public/user-ca.pub /etc/ssh/
    
    # 2. 备份 SSH 配置文件
    cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)
    
    # 3. 编辑 SSH 服务器配置
    cat >> /etc/ssh/sshd_config << 'EOF'
    
    # === SSH CA 配置 ===
    # 信任的用户 CA
    TrustedUserCAKeys /etc/ssh/user-ca.pub
    
    # 可选:配置吊销列表(如果需要)
    # RevokedKeys /etc/ssh/revoked_keys
    
    # 可选:禁用密码认证(推荐)
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    
    # 可选:限制只有特定组的用户可以使用证书登录
    # Match User *,!root
    #    AuthenticationMethods publickey
    EOF
    
    # 4. 验证配置文件语法
    sshd -t
    
    # 5. 重启 SSH 服务
    systemctl restart sshd
    
    # 6. 测试配置是否生效
    sshd -T | grep -i trusteduserca
    
    # 7. 创建测试用户(可选)
    useradd -m testuser
    echo "testuser 用户创建完成"
    • 配置主机证书(可选)
    bash
    # 如果需要使用主机证书,执行以下步骤:
    
    # 1. 生成主机密钥(如果不存在)
    ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
    
    # 2. 将主机公钥发送到 CA 服务器进行签名
    scp /etc/ssh/ssh_host_rsa_key.pub root@192.168.168.71:/tmp/$(hostname)-host-key.pub
    
    # 3. 在 CA 服务器上签发主机证书后,获取证书
    # 4. 配置 SSH 使用主机证书
    echo "HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub" >> /etc/ssh/sshd_config
  • 在客户端服务器:192.168.165.72上操作(第三阶段)

    • 生成客户端密钥对
    bash
    # 登录到客户端服务器
    ssh root@192.168.165.72
    
    # 1. 创建测试用户(模拟实际用户)
    useradd -m alice
    su - alice
    
    # 2. 生成 SSH 密钥对
    ssh-keygen -t rsa -b 4096 \
    -f ~/.ssh/id_rsa \
    -C "alice@client-server" \
    -N ""  # 生产环境建议设置密码
    
    # 3. 查看公钥
    echo "=== 请复制以下公钥内容 ==="
    cat ~/.ssh/id_rsa.pub
    echo "=== 公钥结束 ==="
    
    # 4. 创建 SSH 配置(优化连接)
    cat > ~/.ssh/config << 'EOF'
    Host 192.168.165.73
        HostName 192.168.165.73
        User alice
        IdentityFile ~/.ssh/id_rsa
        CertificateFile ~/.ssh/id_rsa-cert.pub
        # 启用证书验证
        IdentitiesOnly yes
    
    Host 192.168.168.71
        HostName 192.168.168.71
        User root
        IdentityFile ~/.ssh/id_rsa
    EOF
    
    chmod 600 ~/.ssh/config
    
    # 5. 退出 alice 用户
    exit
    • 从 CA 服务器获取证书 (第一手动申请)
    bash
    # 1. 以管理员身份在 CA 服务器上执行签发
    # 在 CA 服务器上执行:
    # /etc/ssh-ca/bin/issue-user-cert.sh alice 30
    
    # 2. 将生成的证书内容复制到客户端
    # 在客户端服务器上,以 alice 用户执行:
    su - alice
    cat > ~/.ssh/id_rsa-cert.pub << 'CERT_CONTENT'
    # 在这里粘贴 CA 服务器生成的证书内容
    # 示例:
    # ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2E...(完整证书内容)
    CERT_CONTENT
    
    chmod 600 ~/.ssh/id_rsa-cert.pub
    • 自动申请脚本(后续申请)
    bash
    # 在客户端创建证书申请脚本
    cat > /usr/local/bin/request-ssh-cert.sh << 'EOF'
    #!/bin/bash
    # 自动向 CA 服务器申请 SSH 证书
    
    CA_SERVER="192.168.168.71"
    USERNAME=$(whoami)
    
    # 上传公钥到 CA 服务器
    scp ~/.ssh/id_rsa.pub $CA_SERVER:/tmp/$USERNAME-$(hostname)-pubkey.pub
    
    # 在 CA 服务器上签发证书
    ssh root@$CA_SERVER "/etc/ssh-ca/bin/issue-user-cert.sh $USERNAME 30 'no-port-forwarding,no-x11-forwarding' > /tmp/$USERNAME-cert.txt 2>&1"
    
    # 获取证书
    scp $CA_SERVER:/tmp/$USERNAME-cert.txt /tmp/
    
    # 从输出中提取证书(这里需要根据实际输出调整)
    # 实际部署时应使用更可靠的方法提取证书
    echo "请手动从 /tmp/$USERNAME-cert.txt 中提取证书内容"
    EOF
    
    chmod +x /usr/local/bin/request-ssh-cert.sh
  • 测试整个流程(第四阶段)

    • 测试证书登录
    bash
    # 在客户端服务器 (192.168.165.72) 上测试
    
    # 1. 切换到 alice 用户
    su - alice
    
    # 2. 测试连接到目标服务器
    ssh -v 192.168.165.73
    
    # 3. 查看证书详情
    ssh-keygen -L -f ~/.ssh/id_rsa-cert.pub
    
    # 4. 测试连接(带详细输出)
    ssh -vvv -o PreferredAuthentications=publickey 192.168.165.73
    
    # 5. 如果连接失败,检查目标服务器日志
    # 在目标服务器上查看日志
    ssh root@192.168.165.73 "tail -f /var/log/auth.log | grep ssh"
    • 验证证书
    bash
    # 1. 检查证书有效期
    ssh-keygen -L -f ~/.ssh/id_rsa-cert.pub | grep -i valid
    
    # 2. 尝试使用过期的证书(测试吊销机制)
    # 3. 测试证书限制(如禁止端口转发)
    ssh -N -L 8080:localhost:80 192.168.165.73
    # 如果配置了 no-port-forwarding,这个命令应该失败
  • 后期维护(第五阶段)

    • 定期任务(在 CA 服务器上)
    bash
    # 1. 证书过期检查脚本
    cat > /etc/cron.daily/check-cert-expiry.sh << 'EOF'
    #!/bin/bash
    # 检查即将过期的证书
    
    CERT_DIR="/etc/ssh-ca/users/certs"
    DAYS_WARNING=7
    
    for cert in "$CERT_DIR"/*.pub; do
        [ -f "$cert" ] || continue
        
        expiry=$(ssh-keygen -L -f "$cert" 2>/dev/null | grep "Valid:" | awk '{print $4}')
        if [ -n "$expiry" ]; then
            expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
            today_epoch=$(date +%s)
            days_left=$(( (expiry_epoch - today_epoch) / 86400 ))
            
            if [ $days_left -lt $DAYS_WARNING ] && [ $days_left -ge 0 ]; then
                echo "警告: 证书 $(basename $cert) 将在 $days_left 天后过期"
            fi
        fi
    done
    EOF
    
    chmod +x /etc/cron.daily/check-cert-expiry.sh
    
    # 2. 备份 CA 密钥(重要!)
    cat > /usr/local/bin/backup-ca-keys.sh << 'EOF'
    #!/bin/bash
    # 备份 CA 密钥
    
    BACKUP_DIR="/backup/ssh-ca"
    DATE=$(date +%Y%m%d)
    
    mkdir -p "$BACKUP_DIR/$DATE"
    cp -r /etc/ssh-ca/{users,hosts} "$BACKUP_DIR/$DATE/"
    cp /etc/ssh-ca/bin/*.sh "$BACKUP_DIR/$DATE/"
    tar -czf "$BACKUP_DIR/ssh-ca-backup-$DATE.tar.gz" -C /etc/ssh-ca .
    
    # 记录备份
    echo "$(date): 备份完成到 $BACKUP_DIR/ssh-ca-backup-$DATE.tar.gz" >> /var/log/ca-backup.log
    EOF
    
    chmod +x /usr/local/bin/backup-ca-keys.sh
    • 吊销证书示例
    bash
    # 如果需要吊销某个用户的证书:
    
    # 1. 在 CA 服务器上找到要吊销的证书
    ls -la /etc/ssh-ca/users/certs/
    
    # 2. 执行吊销
    /etc/ssh-ca/bin/revoke-cert.sh /etc/ssh-ca/users/certs/alice-20231201-143022.pub
    
    # 3. 分发吊销列表到所有目标服务器
    scp /etc/ssh-ca/users/crl root@192.168.165.73:/etc/ssh/revoked_keys
    
    # 4. 在目标服务器上重新加载 SSH 配置
    ssh root@192.168.165.73 "systemctl reload sshd"

欢迎来到 Laazua 的站点