分层证书体系完整方案


分层证书体系完整方案

本方案使用 openssl 生成根CA和中间CA,然后使用 CFSSL(基于中间CA)作为API服务,签发所有终端证书(服务端和客户端)。
终端证书有效期设为 1年,通过设备端自动续期实现无需人工干预。

适用场景:物联网百万级设备 mTLS 接入,Nginx 做 SSL 终结,EMQX 作为 MQTT Broker。

一、环境准备

本次演示的各个环境

操作系统:Ubuntu 20.04 64位

OpenSSL:1.1.1f(Ubuntu 20.04 默认版本)

Nginx:1.28.0(Ubuntu)

CFSSL:1.6.5

EMQX:5.8 开源社区版

1.1 安装必要工具

# 安装 openssl(通常已预装)
sudo apt update && sudo apt install openssl -y   # Debian/Ubuntu
# sudo yum install openssl -y                    # CentOS/RHEL

# 安装 CFSSL(v1.6.5)
curl -sLo cfssl "https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssl_1.6.5_linux_amd64"
curl -sLo cfssljson "https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssljson_1.6.5_linux_amd64"
chmod +x cfssl cfssljson
sudo mv cfssl cfssljson /usr/local/bin/
cfssl version   # 验证

1.2 创建工作目录

mkdir -p ~/pki/{root-ca,intermediate-ca,server,client,cfssl}
cd ~/pki
                     ┌─────────────────────────────────────┐
                     │         根证书 (Root CA)             │
                     │         root-ca.crt(导入设备)        │
                     │     有效期: 20年 (2046年)           │
                     │     角色: 信任锚,离线保存            │
                     │     私钥: root-ca.key (绝密, 保险柜)  │
                     └──────────────┬──────────────────────┘
                                    │
                                    │ 签发
                                    │
                     ┌──────────────▼──────────────────────┐
                     │       中间证书 (Intermediate CA)     │
                     │       /opt/cfssl/ca.pem             │
                     │     有效期: 10年                     │
                     │     角色: 签发所有终端证书            |
                     |     CFSSL服务(签发所有终端证书)       │
                     │     Nginx(验证客户端证书链)          │                      │
                     │     私钥: ca-key.pem (仅CA服务器)    │
                     └──────────────┬──────────────────────┘
                                    │
                                    │ 签发
               ┌────────────────────┼────────────────────┐
               │                    │                    │
               ▼                    ▼                    ▼
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│   服务端证书          │ │   服务端证书          │ │   ......             │
│   (Nginx用)          │ │   (Nginx用)          │ │                      │
│   server.pem         │ │   server.pem         │ │                      │
│   有效期: 1年        │ │   有效期: 1年        │ │                      │
│   角色: 验证服务器身份 │ │   角色: 验证服务器身份 │ │                      │
│   ────────────────   │ │   ────────────────   │ │                      │
│   每1年自动续期       │ │   每1年自动续期       │ │                      │
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘

               ┌──────────────────────────────────────────────────────┐
               │                                                      │
               │           客户端证书 (设备证书) 批量签发             │
               │                                                      │
               ▼                                                      ▼
┌──────────────────────┐                                   ┌──────────────────────┐
│   设备证书            │                                   │   设备证书            │
│   device_001.crt     │          ......                   │   device_NNN.crt     │
│   CN=device_001      │                                   │   CN=device_NNN      │
│   有效期: 1年        │                                   │   有效期: 1年        │
│   角色: 设备身份认证   │                                   │   角色: 设备身份认证   │
│   ────────────────   │                                   │   ────────────────   │
│   设备本地生成私钥    │                                   │   设备本地生成私钥    │
│   通过CFSSL API申请  │                                   │   通过CFSSL API申请  │
│   每1年自动续期       │                                   │   每1年自动续期       │
└──────────────────────┘                                   └──────────────────────┘

二、生成根CA(有效期20年)

2.1 创建根CA配置文件 root-ca/root-ca.conf

[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name
x509_extensions    = v3_ca
prompt             = no

[ req_distinguished_name ]
C  = CN
ST = Guangdong
L  = Shenzhen
O  = MyCompany
OU = IoT Root CA
CN = My IoT Root CA

[ v3_ca ]
basicConstraints       = critical, CA:TRUE
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer

2.2 生成根CA私钥和自签名证书(有效期7300天 = 20年)

cd ~/pki/root-ca
openssl genrsa -out root-ca.key 2048
openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 7300 -config root-ca.conf -out root-ca.crt

生成文件:

  • root-ca.key:根CA私钥(绝密,建议离线保存)

  • root-ca.crt:根CA证书(公开,用于验证中间CA和最终证书)

三、生成中间CA(有效期10年)

3.1 创建中间CA CSR配置文件 intermediate-ca/intermediate-ca.conf

[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name
req_extensions     = v3_req
prompt             = no

[ req_distinguished_name ]
C  = CN
ST = Guangdong
L  = Shenzhen
O  = MyCompany
OU = IoT Intermediate CA
CN = My IoT Intermediate CA

[ v3_req ]
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage         = critical, digitalSignature, keyCertSign

3.2 生成中间CA私钥和CSR

cd ~/pki/intermediate-ca
openssl genrsa -out intermediate-ca.key 2048
openssl req -new -key intermediate-ca.key -out intermediate-ca.csr -config intermediate-ca.conf

3.3 使用根CA签发中间CA证书(有效期3650天 = 10年)

创建中间CA证书扩展配置文件 intermediate-ca/v3_intermediate_ca.conf

basicConstraints       = critical, CA:TRUE, pathlen:0
keyUsage               = critical, digitalSignature, keyCertSign
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer

签发命令:

cd ~/pki/intermediate-ca
openssl x509 -req -in intermediate-ca.csr \
    -CA ../root-ca/root-ca.crt -CAkey ../root-ca/root-ca.key \
    -CAcreateserial -out intermediate-ca.crt -days 3650 -sha256 \
    -extfile v3_intermediate_ca.conf

3.4 验证中间CA证书

openssl verify -CAfile ../root-ca/root-ca.crt intermediate-ca.crt
# 应输出:intermediate-ca.crt: OK

四、准备CFSSL服务(使用中间CA签发终端证书)

4.1 创建CFSSL配置文件 cfssl/cfssl-config.json

终端证书有效期统一为 1年(8760小时),区分 server 和 client 用途。

{
    "signing": {
        "default": {
            "expiry": "8760h"
        },
        "profiles": {
            "server": {
                "usages": ["signing", "key encipherment", "server auth"],
                "expiry": "8760h"
            },
            "client": {
                "usages": ["signing", "key encipherment", "client auth"],
                "expiry": "8760h"
            }
        }
    }
}

4.2 准备中间CA证书供CFSSL使用

cd ~/pki/cfssl
cp ../intermediate-ca/intermediate-ca.crt ./ca.pem
cp ../intermediate-ca/intermediate-ca.key ./ca-key.pem

4.3 启动CFSSL API服务(前台测试)

cfssl serve -address 0.0.0.0 -port 8888 -ca ca.pem -ca-key ca-key.pem -config cfssl-config.json

生产环境:建议使用 systemd 守护,配置文件路径改为 /opt/cfssl/,并创建专用用户 cfssl

4.3 启动CFSSL API服务(生产环境)

systemd 服务示例(/etc/systemd/system/cfssl.service):

[Unit]
Description=CFSSL Certificate Authority API Server
Documentation=https://github.com/cloudflare/cfssl
After=network.target

[Service]
Type=simple
User=cfssl
Group=cfssl
WorkingDirectory=/opt/cfssl

# 核心启动命令:监听 127.0.0.1:8888
ExecStart=/usr/local/bin/cfssl serve \
          -address 127.0.0.1 \
          -port 8888 \
          -ca /opt/cfssl/ca.pem \
          -ca-key /opt/cfssl/ca-key.pem \
          -config /opt/cfssl/cfssl-config.json

# 自动重启策略:异常退出后等待10秒重启
Restart=on-failure
RestartSec=10s

# 安全加固:禁止进程提权
NoNewPrivileges=yes

# 日志相关
StandardOutput=journal
StandardError=journal
SyslogIdentifier=cfssl

[Install]
WantedBy=multi-user.target

创建专用系统用户:

# 创建无登录权限、无家目录的系统用户 cfssl
sudo useradd -r -s /bin/false -M cfssl

参数说明:-r 创建系统账户,-s /bin/false 禁止登录,-M 不创建家目录。

创建数据目录并迁移文件:

# 创建目录
sudo mkdir -p /opt/cfssl

# 将之前生成好的中间CA证书、私钥和CFSSL配置文件复制过去
# (假设你之前在 ~/pki/cfssl/ 目录下操作)
sudo cp ~/pki/cfssl/ca.pem /opt/cfssl/
sudo cp ~/pki/cfssl/ca-key.pem /opt/cfssl/
sudo cp ~/pki/cfssl/cfssl-config.json /opt/cfssl/

设置严格的文件权限(关键安全步骤)

# 将目录及其下所有文件的所有权赋给 cfssl 用户
sudo chown -R cfssl:cfssl /opt/cfssl

# 私钥文件仅允许所有者读写(权限 600),绝对不能让其他用户读取
sudo chmod 600 /opt/cfssl/ca-key.pem

# 证书和配置文件可读即可(权限 644)
sudo chmod 644 /opt/cfssl/ca.pem
sudo chmod 644 /opt/cfssl/cfssl-config.json

创建 Systemd 服务单元文件

创建 /etc/systemd/system/cfssl.service 文件:

sudo vim /etc/systemd/system/cfssl.service

把上面的 systemd 服务示例写入到文件

重载 Systemd 并启动服务

# 重新加载 systemd 配置
sudo systemctl daemon-reload

# 设置开机自启
sudo systemctl enable cfssl

# 立即启动服务
sudo systemctl start cfssl

# 查看运行状态
sudo systemctl status cfssl

验证服务是否正常运行

方式一:检查端口监听

sudo netstat -tlnp | grep 8888
# 应显示 tcp 127.0.0.1:8888,且进程名为 cfssl
# 或者
lsof -i:8888

方式二:调用 API 测试接口

curl -X POST http://127.0.0.1:8888/api/v1/cfssl/info -H "Content-Type: application/json" -d '{}'
# 正常应返回 JSON 格式的 CA 信息

方式三:查看实时日志(排查问题用)

sudo journalctl -u cfssl -f

调整设备端 API 调用地址

由于现在 CFSSL 仅监听 127.0.0.1,外部设备无法直接访问。你需要在 CFSSL 服务器上部署一个 Nginx 反向代理,或者设备通过内网负载均衡器访问。

添加一个 http 块的反向代理,用于接收设备发来的证书申请请求,并转发给本机的 127.0.0.1:8888,最好使用https

server {
    listen 80;
    server_name ca.yourdomain.com;   # 给 CA 服务单独分配一个域名

    # 建议加个简单的 API Token 防暴力调用(可选)
    location /api/ {
        proxy_pass http://127.0.0.1:8888/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

五、签发服务端证书(供Nginx使用,有效期1年)

5.1 准备CSR配置 server/server-csr.json

注意:CN 必须为客户端实际访问的域名(或IP)

{
    "CN": "emqx.test.com",
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "CN",
            "ST": "Guangdong",
            "L": "Shenzhen",
            "O": "Wenjy"
        }
    ]
}

5.2 使用CFSSL签发服务端证书

cd ~/pki/server

cfssl gencert \
    -ca=../cfssl/ca.pem \
    -ca-key=../cfssl/ca-key.pem \
    -config=../cfssl/cfssl-config.json \
    -profile=server \
    server-csr.json | cfssljson -bare server

生成文件:server.pem(证书)、server-key.pem(私钥)

5.3 构建完整的服务端证书链(供Nginx使用)

Nginx 需要完整的证书链(服务器证书 + 中间CA),以便客户端(设备)能验证完整路径。

cat server.pem ../intermediate-ca/intermediate-ca.crt > server-fullchain.pem

六、签发客户端证书(设备用,有效期1年)

6.1 准备CSR配置 client/client-csr.json

每台设备的 CN 必须是唯一的(如设备序列号)。

{
    "CN": "device_sn_001",
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "CN",
            "ST": "Guangdong",
            "L": "Shenzhen",
            "O": "Wenjy"
        }
    ]
}

6.2 签发客户端证书

cd ~/pki/client

cfssl gencert \
    -ca=../cfssl/ca.pem \
    -ca-key=../cfssl/ca-key.pem \
    -config=../cfssl/cfssl-config.json \
    -profile=client \
    client-csr.json | cfssljson -bare client

生成:client.pem(证书)、client-key.pem(私钥)

批量签发:循环修改 CN 字段,重复执行上述命令即可。

七、准备信任链文件(供Nginx验证客户端)

Nginx 验证客户端证书时,需要完整的信任链(根CA + 中间CA)。

cd ~/pki

cat root-ca/root-ca.crt intermediate-ca/intermediate-ca.crt > ca-chain.crt

八、配置Nginx实现mTLS

8.1 Nginx stream 配置 /etc/nginx/conf.d/mqtts.conf

stream 需要看你的具体配置,如果原来有配置了stream 引入文件,那就行去掉外围的stream就行

stream {
    upstream emqx_backend {
        server 127.0.0.1:1883;
        # 若有多个EMQX节点,可添加
        # server 192.168.1.101:1883;
    }

    server {
        listen 8883 ssl;
        server_name emqx.test.com;

        # 服务端证书链(服务器证书 + 中间CA)
        ssl_certificate     /usr/local/nginx/conf/ssl/mqtt/server-fullchain.pem;
        ssl_certificate_key /usr/local/nginx/conf/ssl/mqtt/server-key.pem;

        # 客户端证书验证链(根CA + 中间CA)
        ssl_client_certificate /usr/local/nginx/conf/ssl/mqtt/ca-chain.crt;
        ssl_verify_client on;
        ssl_verify_depth 2;

        # SSL 优化
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        #ssl_session_cache shared:SSL:10m;
        #ssl_session_timeout 10m;

        # 传递真实客户端IP(必须)
        proxy_protocol on;
        proxy_pass emqx_backend;
        proxy_connect_timeout 5s;
    }
}

8.2 复制证书并重载Nginx

复制证书到nginx目录

sudo mkdir -p /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/server/server-fullchain.pem /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/server/server-key.pem /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/ca-chain.crt /usr/local/nginx/conf/ssl/mqtt

# 关键:设置严格权限
sudo chmod 600 /usr/local/nginx/conf/ssl/mqtt/server-key.pem

sudo nginx -t && sudo nginx -s reload

九、配置EMQX(开启PROXY协议)

EMQX 5.8 社区版支持 proxy_protocol,用于获取客户端真实IP。

方式一:配置文件(/etc/emqx/emqx.conf 或 cluster.hocon)

listeners.tcp.default {
    bind = "0.0.0.0:1883"
    proxy_protocol = true
    proxy_protocol_timeout = 3s
}

方式二:Dashboard

访问 EMQX Dashboard → 管理 → 监听器 → 找到 tcp:default(1883)

编辑 → 开启 Proxy Protocol → 保存并重启监听器

十、设备端集成(轻量级密码库)

设备(MCU/嵌入式)需使用轻量级密码库(如 wolfSSL 或 Mbed TLS)完成以下操作:

  1. 在设备本地生成私钥和CSR(私钥永不离开设备)。

  2. 将CSR(Base64编码)通过HTTP POST发送给CFSSL API(http://CA_IP:8888/api/v1/cfssl/newcert)。

  3. 接收返回的证书并保存到本地存储。

  4. 使用该证书(及私钥)连接Nginx的8883端口,完成mTLS握手。

设备端伪代码(Python示例,仅用于理解逻辑):

import subprocess, requests, base64

# 1. 生成私钥和CSR(实际用wolfSSL API,此处用openssl模拟)
subprocess.run("openssl genrsa -out device.key 2048", shell=True)
subprocess.run("openssl req -new -key device.key -out device.csr -subj '/CN=device_sn_001'", shell=True)

# 2. 提交CSR
with open('device.csr', 'rb') as f:
    csr_b64 = base64.b64encode(f.read()).decode()
resp = requests.post('http://your-ca-server:8888/api/v1/cfssl/newcert',
                      json={'certificate_request': csr_b64})
cert_pem = resp.json()['result']['certificate']

# 3. 保存证书
with open('device.crt', 'w') as f:
    f.write(cert_pem)

真实设备请使用 wolfSSL 的 wolfSSL_CTX_use_certificate_chain_file 等函数管理证书。

十一、证书自动续期机制

11.1 设备端主动续期(推荐)

设备在每次启动或定期检查本地证书有效期,若剩余天数 < 30天,则触发续期流程:

  1. 生成新的私钥和CSR(可重新生成或复用旧私钥,视安全策略而定)。

  2. 调用CFSSL API申请新证书。

  3. 原子替换旧证书文件(保证连接不中断)。

  4. 使用新证书重新连接MQTT。

续期逻辑伪代码:

def check_and_renew():
    if days_until_expiry('device.crt') < 30:
        new_key, new_csr = generate_new_key_and_csr()
        new_cert = request_cert_from_ca(new_csr)
        replace_cert(new_cert, new_key)
        mqtt_reconnect_with_new_cert()

11.2 服务端证书续期

服务端证书(Nginx用)同样1年有效,可手动或使用脚本在过期前30天重新签发,然后执行 nginx -s reload

十二、测试与验证

12.1 使用 mosquitto 客户端测试 mTLS 连接

# 安装 mosquitto-clients
sudo apt install mosquitto-clients -y

# 发布消息(需指定CA根证书验证服务端,以及客户端证书/私钥)
mosquitto_pub -h emqx.test.com -p 8884 \
    --cafile ~/pki/root-ca/root-ca.crt \
    --cert ~/pki/client/client.pem \
    --key ~/pki/client/client-key.pem \
    -u test -P test \
    -t "test" -m "hello" -d

12.1 使用MQTTX客户端验证

  • 下载并安装 MQTTX

首先,你需要从官方渠道下载并安装 MQTTX 桌面客户端:

官方下载页面:https://mqttx.app/zh

EMQ 官方下载:https://www.emqx.com/zh/downloads/MQTTX

GitHub Releases:https://github.com/emqx/MQTTX/releases

选择适合你操作系统的版本(Windows、macOS、Linux)下载并安装即可

  • 获取客户端证书(通过 CFSSL API)
    你的设备(或测试电脑)需要向 CFSSL 服务申请一张唯一的客户端证书。

第一步:生成设备私钥和证书签名请求(CSR)

在设备(或测试电脑)上,使用 openssl 命令生成:

# 1. 生成设备私钥(请妥善保管,切勿泄露)
openssl genrsa -out device.key 2048

# 2. 生成证书签名请求 (CSR),CN 必须填设备的唯一标识
openssl req -new -key device.key -out device.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=device_sn_001"

# 验证 CSR 中的 Subject
openssl req -in device.csr -noout -subject
# 应输出:subject=C=CN, ST=Guangdong, L=Shenzhen, O=Wenjy, CN=device_sn_001

第二步:调用 CFSSL API 申请证书

将上一步生成的 device.csr 文件内容进行 Base64 编码,然后通过 curl 发送给 CFSSL 服务

# 如果没有安装 jq,先安装
sudo apt install jq -y   # Ubuntu/Debian

CSR_JSON=$(cat device.csr | sed 's/$/\\n/' | tr -d '\n')

curl -X POST http://你的CA服务器IP:8888/api/v1/cfssl/newcert \
    -H "Content-Type: application/json" \
    -d "{\"certificate_request\": \"$CSR_JSON\", \"profile\": \"client\"}" \
    | jq -r '.result.certificate' > device.crt

第三步:保存证书,上面通过jq来保存

API 调用成功后,会返回一个 JSON 响应,其中 result.certificate 字段就是你的客户端证书(PEM 格式)。

将返回的证书内容保存为 device.crt 文件。最终的证书文件就是:

客户端证书:device.crt

客户端私钥:device.key

至于根证书 root-ca.crt

  • 配置 SSL/TLS 证书(关键)

在 “SSL/TLS” 或 “安全” 选项卡中:

字段 值 说明
CA 文件 选择 root-ca.crt(根证书20年有效期) 用于验证服务端证书
客户端证书文件 选择 device.crt 你的设备证书
客户端密钥文件 选择 device.key 你的设备私钥
SSL 安全 开启(不可关闭) 确保验证服务端

注意:如果服务端证书是自签名的,必须指定 CA 文件,否则会报“证书验证失败”。

连接成功后,进行消息收发测试

12.2 服务端证书自动续期脚本 + Cron 定时任务

  • 续期脚本

创建脚本文件 /usr/local/bin/renew_nginx_cert.sh

#!/bin/bash

# =======================================================
# 服务端证书自动续期脚本 (Nginx + CFSSL)
# 功能:检查证书剩余天数,若小于30天则自动续期
# 适用:Ubuntu 20.04, Nginx, CFSSL API (127.0.0.1:8888)
# =======================================================

set -e

# ---------- 配置变量 ----------
DOMAIN="emqx.test.com"                # 服务端证书域名
NGINX_SSL_DIR="/etc/nginx/ssl"
SERVER_KEY="${NGINX_SSL_DIR}/server-key.pem"
SERVER_CERT="${NGINX_SSL_DIR}/server-fullchain.pem"
CA_CHAIN="${NGINX_SSL_DIR}/ca-chain.crt"

CFSSL_API="http://127.0.0.1:8888/api/v1/cfssl/sign"
PROFILE="server"                       # 对应 cfssl-config.json 中的 profile

LOG_FILE="/var/log/renew_nginx_cert.log"

# ---------- 函数:记录日志 ----------
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# ---------- 1. 检查证书剩余天数 ----------
if [ ! -f "$SERVER_CERT" ]; then
    log "错误: 证书文件 $SERVER_CERT 不存在,无法检查有效期。"
    exit 1
fi

# 提取证书过期日期
exp_date=$(openssl x509 -in "$SERVER_CERT" -noout -enddate | cut -d= -f2)
exp_epoch=$(date -d "$exp_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (exp_epoch - now_epoch) / 86400 ))

log "当前证书剩余天数: $days_left 天"

# 如果剩余天数大于30天,无需续期
if [ "$days_left" -gt 30 ]; then
    log "证书有效期充足,无需续期。"
    exit 0
fi

log "证书将在 ${days_left} 天后过期,开始续期流程..."

# ---------- 2. 生成新的 CSR(使用现有私钥) ----------
# 注意:我们沿用旧的 server-key.pem,只更新证书。
# 如果希望每次轮换私钥,可在此处生成新私钥并替换,但需额外处理。
WORK_DIR=$(mktemp -d)
cd "$WORK_DIR"

# 生成 CSR(使用原私钥)
openssl req -new -key "$SERVER_KEY" -out server.csr \
    -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=${DOMAIN}"

# 添加 SAN 扩展(如果需要)
# 由于 CFSSL 配置中会覆盖 SAN,这里可以忽略,但为了保险,我们生成带 SAN 的 CSR:
# 先生成 openssl 配置文件,但为简化,使用 -addext 参数(OpenSSL 1.1.1+ 支持)
openssl req -new -key "$SERVER_KEY" -out server.csr \
    -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=${DOMAIN}" \
    -addext "subjectAltName=DNS:${DOMAIN}"

log "CSR 生成完成"

# ---------- 3. 将 CSR 转换为 JSON 字符串(转义换行) ----------
CSR_JSON=$(cat server.csr | sed 's/$/\\n/' | tr -d '\n')

# ---------- 4. 调用 CFSSL API 签名 ----------
response=$(curl -s -X POST "$CFSSL_API" \
    -H "Content-Type: application/json" \
    -d "{\"certificate_request\": \"$CSR_JSON\", \"profile\": \"$PROFILE\"}")

# 检查是否成功
success=$(echo "$response" | jq -r '.success')
if [ "$success" != "true" ]; then
    error_msg=$(echo "$response" | jq -r '.errors[0].message')
    log "CFSSL 签名失败: $error_msg"
    rm -rf "$WORK_DIR"
    exit 1
fi

# ---------- 5. 提取新证书 ----------
new_cert=$(echo "$response" | jq -r '.result.certificate')
if [ -z "$new_cert" ] || [ "$new_cert" = "null" ]; then
    log "响应中未包含证书内容"
    rm -rf "$WORK_DIR"
    exit 1
fi

# 将新证书保存到临时文件
echo "$new_cert" > server_new.crt

# ---------- 6. 构建完整的证书链(服务器证书 + 中间CA) ----------
# 假设中间CA证书位于 /opt/cfssl/ca.pem(即中间证书)
INTERMEDIATE_CA="/opt/cfssl/ca.pem"
if [ ! -f "$INTERMEDIATE_CA" ]; then
    log "警告: 中间CA证书 $INTERMEDIATE_CA 不存在,将仅使用服务器证书"
    cat server_new.crt > server_fullchain_new.pem
else
    cat server_new.crt "$INTERMEDIATE_CA" > server_fullchain_new.pem
fi

# ---------- 7. 备份旧证书,替换新证书 ----------
TIMESTAMP=$(date +%Y%m%d%H%M%S)
cp "$SERVER_CERT" "${SERVER_CERT}.bak.${TIMESTAMP}"
cp "$SERVER_KEY" "${SERVER_KEY}.bak.${TIMESTAMP}"

cp server_fullchain_new.pem "$SERVER_CERT"
# 私钥不变(我们用的是原私钥),所以无需替换 server-key.pem

log "证书文件已更新"

# ---------- 8. 测试 Nginx 配置并重载 ----------
if /usr/local/nginx/sbin/nginx -t; then
    /usr/local/nginx/sbin/nginx -s reload
    log "Nginx 重载成功,新证书已生效"
else
    # 如果测试失败,回滚证书
    log "Nginx 配置测试失败,正在回滚证书..."
    cp "${SERVER_CERT}.bak.${TIMESTAMP}" "$SERVER_CERT"
    log "回滚完成,请手动检查 Nginx 配置"
    rm -rf "$WORK_DIR"
    exit 1
fi

# ---------- 9. 清理 ----------
rm -rf "$WORK_DIR"
log "服务端证书续期完成"
exit 0
  • 赋予脚本执行权限
sudo chmod +x /usr/local/bin/renew_nginx_cert.sh
  • 测试脚本(手动执行一次)
sudo /usr/local/bin/renew_nginx_cert.sh

观察日志输出,确保没有错误。如果手动执行成功,Nginx 证书被更新并重载。

  • 设置 Cron 定时任务
    编辑 root 用户的 crontab:
    sudo crontab -e

添加以下内容(每天凌晨 3:00 执行):

0 3 * * * /usr/local/bin/renew_nginx_cert.sh >> /var/log/renew_nginx_cert.log 2>&1
  • 日志轮转(避免日志文件过大)

创建 /etc/logrotate.d/renew_nginx_cert

/var/log/renew_nginx_cert.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
}

文章作者: 江湖义气
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江湖义气 !
  目录