Files
2026-07-18 20:09:26 +08:00

22 KiB
Raw Permalink Blame History

CertCenter - 轻量化证书管理中心

一站式 SSL 证书管理平台,集成 ACME 证书申请、自动续签、多服务器分发,通过 Web UI 完成所有操作。


目录


架构概览

┌─────────────────────────────────────────────────────────┐
│                    CertCenter 服务                        │
│                                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │  WebUI   │  │ ACME 引擎 │  │  数据库   │              │
│  │  (Vue)   │  │ (certbot) │  │ (SQLite) │              │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘              │
│       │              │              │                    │
│       └──────────────┴──────────────┘                    │
│                      │                                   │
│              FastAPI 后端服务                              │
└──────────────────────┬──────────────────────────────────┘
                       │
          ┌────────────┼────────────┐
          │            │            │
     ┌────▼────┐  ┌────▼────┐  ┌────▼────┐
     │ VPS-A   │  │ VPS-B   │  │ VPS-C   │
     │ Linux   │  │ Linux   │  │ Windows │
     │ cron    │  │ cron    │  │ schtasks│
     └─────────┘  └─────────┘  └─────────┘

核心流程:

  1. 在 WebUI 配置 ACME 凭据(阿里云 DNS / Cloudflare
  2. 添加服务器和域名信息
  3. 点击"申请证书"CertCenter 自动完成 DNS 验证并签发证书
  4. 业务服务器通过 cron 定时拉取最新证书
  5. 证书到期前自动续签

环境要求

服务端(CertCenter

依赖 版本 说明
Python 3.11 ~ 3.12 ⚠️ 不支持 3.14pydantic-core 无预编译 wheel
certbot 2.10+ ACME 证书申请
certbot-dns-alicloud 最新 阿里云 DNS 插件(如使用阿里云)

客户端(业务服务器)

依赖 说明
bash / PowerShell 执行部署脚本
curl / Invoke-WebRequest 下载证书
cron / schtasks 定时任务

安装部署

0. 确认 Python 版本

python --version
# 需要 3.11.x 或 3.12.x,不支持 3.14

如果版本不对,先装 Python 3.12

1. 克隆项目

git clone <repo-url> certcenter
cd certcenter

2. 创建虚拟环境并安装依赖

# 创建虚拟环境(指定 Python 3.12
py -3.12 -m venv .venv

# 激活虚拟环境
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # Linux/Mac

# 安装依赖
pip install -r requirements.txt

如果使用阿里云 DNS,还需要安装 certbot 插件:

pip install certbot-dns-alicloud

3. 安装前端依赖并构建

cd frontend
npm install
npm run build
cd ..

构建完成后,frontend/dist/ 目录包含前端静态文件,FastAPI 会自动加载。

4. 配置环境变量

cp .env .env

编辑 .env 文件:

# 数据库路径(SQLite
DATABASE_URL=sqlite+aiosqlite:///./certcenter.db

# 证书存储目录
CERT_DIR=/srv/certs

# CertCenter 的访问地址(客户端通过此地址拉取证书)
BASE_URL=https://cert.example.com

5. 启动服务

python -m backend.main

服务默认监听 http://0.0.0.0:8000

首次启动时,系统会自动为 CertCenter 的域名生成一份自签证书(存放在 CERT_DIR 下),确保 HTTPS 可用。后续正式签发证书后会自动替换。

6. 引导流程(首次使用)

首次使用时的推荐操作顺序:

1. 启动 CertCenter(自签证书自动生成)
   python -m backend.main

2. 打开 WebUI,配置 ACME 凭据(阿里云 AK/SK

3. 添加域名:cert.zhzp.topCertCenter 自身的域名)

4. 点击"申请证书",为 CertCenter 签发正式证书

5. 重启 CertCenter,正式证书自动加载
   # 此时所有客户端连接都是受信任的 HTTPS

为什么需要这个流程?

CertCenter 使用 HTTPS → 需要证书 → 证书由 CertCenter 自己签发
                                            ↑
                                    这就是"引导"问题

解决方式:先用自签证书启动 → 签发正式证书 → 替换。客户端脚本已内置 curl -k 跳过 TLS 验证,不受影响。

7. 生产环境部署(可选)

使用 systemd 托管服务:

cat > /etc/systemd/system/certcenter.service << 'EOF'
[Unit]
Description=CertCenter Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/certcenter
ExecStart=/opt/certcenter/venv/bin/python -m backend.main
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now certcenter

如果需要 HTTPS 访问 CertCenter 本身,可以使用 nginx 反向代理:

server {
    listen 443 ssl;
    server_name cert.example.com;

    ssl_certificate     /etc/nginx/ssl/cert.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/cert.example.com/private.key;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

测试验证

部署完成后,建议按以下步骤验证系统是否正常工作。

1. 环境检查

运行部署验证脚本,检查所有依赖和配置:

python -m tests.test_setup

该脚本会检查:

检查项 说明
Python 依赖 fastapi、sqlalchemy、acme、cryptography 等
Certbot certbot 命令是否可用
DNS 插件 certbot-dns-alicloud 或 certbot-dns-cloudflare
.env 配置 环境变量文件是否存在
数据库 数据库连接是否正常
前端构建 frontend/dist/ 是否存在
证书目录 存储目录是否可写
BASE_URL 是否已修改默认值
Let's Encrypt 连通性 Staging 和 Production 环境是否可达
FastAPI 服务 服务能否正常启动

所有检查通过后,进入下一步。

2. 端到端测试

使用 Let's Encrypt Staging 环境(测试环境)验证完整的证书签发流程。

前提条件:

  • 一个使用阿里云 DNS 解析的域名
  • 阿里云 RAM 子账号的 AccessKey(需有 AliyunDNSFullAccess 权限)
  • CertCenter 服务已启动

执行测试:

python -m tests.test_e2e \
  --domain test.example.com \
  --email admin@example.com \
  --ak YOUR_ACCESS_KEY \
  --sk YOUR_ACCESS_SECRET

测试流程:

[0] 检查 API 服务是否正常
[1] 配置 ACMEStaging 环境 + 阿里云 DNS 凭据)
[2] 创建测试服务器
[3] 创建测试域名配置
[4] 申请证书(自动完成 DNS-01 验证)
[5] 检查证书信息(签发者、有效期、SAN)
[6] 测试版本接口
[7] 测试脚本生成
[8] 测试证书下载

预期输出:

==================================================
  端到端测试: test.example.com
==================================================

  ✅ [0] API 服务正常
  ✅ [1] ACME 配置完成
  ✅ [2] 服务器 ID: 1
  ✅ [3] 域名 ID: 1
  ✅ [4] 证书申请成功: Certificate issued, expires: 2026-10-16 00:00:00
  ✅ [5] 签发者: CN=(STAGING) Artificial Apricot R3, O=Let's Encrypt
  ✅ [5] 有效期: 2026-07-18 ~ 2026-10-16
  ✅ [5] 域名: ['test.example.com']
  ✅ [5] 剩余天数: 90
  ✅ [6] 版本号: 1
  ✅ [7] 脚本生成成功,长度: 1234 字符
  ✅ [8] 证书下载成功

==================================================
  ✅ 端到端测试完成!
==================================================

  证书位置: /tmp/cert-e2e-test/test.example.com/
  Staging 证书不受浏览器信任,仅用于验证流程。
  确认流程正常后,在 WebUI 中切换到生产环境重新签发。

Staging vs Production

环境 地址 说明
Staging https://acme-staging-v02.api.letsencrypt.org/directory 测试环境,证书不受信任,无速率限制
Production https://acme-v02.api.letsencrypt.org/directory 生产环境,正式证书,有速率限制

建议: 先用 Staging 环境完成全流程测试,确认无误后在 WebUI 中切换到 Production 环境,删除测试域名,重新添加正式域名并签发。


WebUI 操作指南

启动服务后,浏览器访问 http://服务器IP:8000(或配置的域名)。

1. 仪表盘

首页展示:

  • 统计卡片:服务器总数、域名总数、即将过期证书数
  • 最近部署日志:最近 10 条客户端部署记录
  • 快捷操作:新增服务器、新增域名

2. 服务器管理

新增服务器:

  1. 点击侧边栏"服务器管理"
  2. 点击"+ 新增服务器"
  3. 填写表单:
    • 名称:服务器标识,如 vps-blogapi-server
    • 平台:选择 LinuxWindows
    • Token:点击"生成"按钮自动生成,或手动填写
    • IP:可选,仅用于备注
  4. 点击"创建"

编辑/删除服务器:

  • 在列表中点击"编辑"修改信息
  • 点击"删除"会同时删除该服务器下的所有域名配置

3. 域名管理

新增域名:

  1. 点击侧边栏"域名管理"
  2. 点击"+ 新增域名"
  3. 填写表单:
    • 所属服务器:选择域名所在的业务服务器
    • 域名:如 example.comapi.example.com
    • 证书存放路径:证书在业务服务器上的存放目录
      • Linux 示例:/etc/nginx/ssl/example.com
      • Windows 示例:C:\certs\example.com
    • 校验命令:替换证书后验证服务是否正常的命令
      • nginx 示例:nginx -t
      • 其他服务根据实际情况填写
    • 重载命令:证书更新后重载服务的命令
      • Linux 示例:systemctl reload nginx
      • Windows 示例:nginx -s reload
  4. 点击"创建"

申请证书:

  1. 在域名列表中找到目标域名
  2. 点击"申请证书"按钮
  3. 系统自动完成:
    • 通过 DNS-01 验证(自动添加 _acme-challenge TXT 记录)
    • 向 Let's Encrypt 申请证书
    • 保存证书到配置的目录
    • 更新证书到期时间
  4. 申请成功后,"证书到期"列会显示到期日期

复制部署命令:

点击"复制部署命令",获取在业务服务器上执行的一键部署命令。

查看脚本:

点击"查看脚本",预览将下发到业务服务器的部署脚本内容。

4. ACME 配置

基本配置:

  1. 点击侧边栏"ACME 配置"
  2. 配置项:
    • ACME 服务器
      • Let's Encrypt (生产):正式环境使用
      • Let's Encrypt (测试):测试环境,不受速率限制
    • 邮箱Let's Encrypt 注册邮箱,用于接收证书到期提醒
    • DNS 提商:选择使用的 DNS 服务商
    • 续签提前天数:证书到期前几天自动续签(默认 30 天)

DNS 凭据配置:

根据选择的 DNS 提商填写对应的凭据:

DNS 提商 需要的凭据
阿里云 DNS Access Key + Access Secret
Cloudflare API Token

获取阿里云 DNS 凭据:

  1. 登录 阿里云控制台
  2. 创建子账号,授予 AliyunDNSFullAccess 权限
  3. 创建 AccessKey,记录 Access Key ID 和 Access Key Secret

自动续签:

点击"🔄 自动续签所有"按钮,系统会:

  1. 检查所有域名的证书到期时间
  2. 对即将过期的证书自动执行续签
  3. 显示每个域名的续签结果

5. 部署日志

查看所有客户端(业务服务器)的证书拉取部署记录:

  • 时间:部署执行时间
  • 服务器:执行部署的服务器
  • 域名:部署的域名
  • 状态:成功 / 失败 / 跳过
  • 消息:详细信息

支持按状态筛选。

6. ACME 日志

查看所有证书申请/续签操作记录:

  • 时间:操作时间
  • 域名:操作的域名
  • 操作:申请 / 续签 / 吊销 / 注册
  • 状态:成功 / 失败 / 进行中
  • 消息:结果信息
  • 详情:点击"查看"显示完整日志

客户端部署指南

Linux 服务器

首次部署(只需执行一次):

# 1. 下载部署脚本(从 WebUI "复制部署命令" 获取)
curl -fsSL "https://cert.example.com/api/script/example.com?server_name=vps-blog" \
  -o /usr/local/bin/deploy-cert.sh

# 2. 添加执行权限
chmod +x /usr/local/bin/deploy-cert.sh

# 3. 添加 cron 定时任务(每 30 分钟检查一次)
echo '*/30 * * * * /usr/local/bin/deploy-cert.sh >> /var/log/deploy-cert.log 2>&1' | crontab -

手动执行测试:

/usr/local/bin/deploy-cert.sh

查看部署日志:

tail -f /var/log/deploy-cert.log

脚本执行流程:

1. 请求 CertCenter 获取远端版本号
2. 对比本地版本号
   ├── 一致 → 退出(无需更新)
   └── 不一致 ↓
3. 下载新证书(fullchain.pem + private.key
4. 备份旧证书
5. 替换新证书
6. 执行校验命令(nginx -t)
   ├── 失败 → 回滚旧证书 → 退出
   └── 成功 ↓
7. 更新本地版本号
8. 重载服务(systemctl reload nginx

Windows 服务器

首次部署:

# 1. 创建脚本目录
New-Item -ItemType Directory -Force -Path C:\scripts

# 2. 下载部署脚本
Invoke-WebRequest `
  -Uri "https://cert.example.com/api/script/www.example.com?server_name=win-web" `
  -OutFile C:\scripts\deploy-cert.ps1

# 3. 添加计划任务(每 30 分钟)
schtasks /create /sc minute /mo 30 /tn "CertDeploy-www.example.com" /tr "powershell -ExecutionPolicy Bypass -File C:\scripts\deploy-cert.ps1"

手动执行测试:

powershell -ExecutionPolicy Bypass -File C:\scripts\deploy-cert.ps1

API 参考

客户端 API

供业务服务器上的部署脚本调用,使用 Bearer Token 认证。

获取证书版本号

GET /api/version/{domain}
  • 认证:无需认证
  • 返回:版本号字符串(纯文本)

示例:

curl https://cert.example.com/api/version/example.com
# 返回: 3

下载证书

GET /api/cert/{domain}/fullchain
GET /api/cert/{domain}/private
  • 认证:Authorization: Bearer <token>
  • 返回:PEM 格式证书文件

示例:

curl -H "Authorization: Bearer tok_xxx" \
  https://cert.example.com/api/cert/example.com/fullchain \
  -o fullchain.pem

获取部署脚本

GET /api/script/{domain}?server_name={server_name}
  • 认证:无需认证
  • 返回:部署脚本内容(bash 或 PowerShell

示例:

curl https://cert.example.com/api/script/example.com?server_name=vps-blog \
  -o /usr/local/bin/deploy-cert.sh

上报部署结果

POST /api/report/{domain}?status={status}&message={message}
  • 认证:Authorization: Bearer <token>
  • 参数:
    • statussuccess / failed / skipped
    • message:可选,详细信息

示例:

curl -X POST \
  -H "Authorization: Bearer tok_xxx" \
  "https://cert.example.com/api/report/example.com?status=success&message=updated+to+v3"

管理 API

供 WebUI 前端调用,所有接口以 /admin/api 为前缀。

统计数据

GET /admin/api/stats

返回:

{
  "server_count": 3,
  "domain_count": 5,
  "expiring_count": 1,
  "recent_logs": [...]
}

服务器管理

方法 路径 说明
GET /admin/api/servers 服务器列表
GET /admin/api/servers/{id} 服务器详情
POST /admin/api/servers 新增服务器
PUT /admin/api/servers/{id} 编辑服务器
DELETE /admin/api/servers/{id} 删除服务器

新增/编辑请求体:

{
  "name": "vps-blog",
  "platform": "linux",
  "token": "tok_xxxxxxxxxxxx",
  "ip": "1.2.3.4"
}

域名管理

方法 路径 说明
GET /admin/api/domains 域名列表
GET /admin/api/domains/{id} 域名详情
POST /admin/api/domains 新增域名
PUT /admin/api/domains/{id} 编辑域名
DELETE /admin/api/domains/{id} 删除域名

新增/编辑请求体:

{
  "server_id": 1,
  "domain": "example.com",
  "cert_dir": "/etc/nginx/ssl/example.com",
  "check_cmd": "nginx -t",
  "reload_cmd": "systemctl reload nginx"
}

ACME 配置

方法 路径 说明
GET /admin/api/acme/config 获取 ACME 配置
PUT /admin/api/acme/config 更新 ACME 配置

更新请求体:

{
  "acme_server": "https://acme-v02.api.letsencrypt.org/directory",
  "email": "admin@example.com",
  "dns_provider": "aliyun",
  "dns_credentials": "{\"access_key\":\"xxx\",\"access_secret\":\"xxx\"}",
  "renew_days": 30
}

ACME 操作

方法 路径 说明
POST /admin/api/acme/issue/{domain_id} 申请证书
POST /admin/api/acme/renew/{domain_id} 续签证书
POST /admin/api/acme/auto-renew 自动续签所有

日志查询

方法 路径 说明
GET /admin/api/logs?status={status} 部署日志
GET /admin/api/acme/logs?status={status} ACME 日志

证书信息

GET /admin/api/cert-info/{domain_id}

返回:

{
  "domain": "example.com",
  "cert_info": {
    "subject": "CN=example.com",
    "issuer": "CN=R3, O=Let's Encrypt",
    "not_before": "2026-01-01T00:00:00",
    "not_after": "2026-04-01T00:00:00",
    "serial_number": "1234567890",
    "san": ["example.com"]
  },
  "need_renew": false,
  "days_left": 75
}

配说说明

环境变量(.env

变量 默认值 说明
DATABASE_URL sqlite+aiosqlite:///./certcenter.db 数据库连接字符串
CERT_DIR /srv/certs 证书存储根目录
BASE_URL https://cert.example.com CertCenter 访问地址

证书存储目录结构

/srv/certs/
├── example.com/
│   ├── fullchain.pem      # 完整证书链
│   ├── private.key        # 私钥
│   └── .version           # 当前版本号
├── api.example.com/
│   ├── fullchain.pem
│   ├── private.key
│   └── .version
└── .certbot-work/         # certbot 工作目录
    ├── .certbot-config/
    └── .certbot-logs/

版本号说明

版本号为递增整数,初始值为 0。每次证书申请/续签成功后自动 +1。

客户端通过对比版本号判断是否需要下载新证书,避免重复下载。


常见问题

Q: 申请证书时报错 "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.xxx"

原因: DNS TXT 记录未正确添加或未及时传播。

解决方案:

  1. 确认阿里云 DNS 凭据正确(Access Key / Secret
  2. 确认域名使用的是阿里云 DNS 服务
  3. 等待 DNS 记录传播(通常 1-2 分钟)
  4. 使用测试环境(Let's Encrypt Staging)调试

Q: 客户端脚本执行后没有更新证书

可能原因:

  1. 版本号一致,无需更新(正常行为)
  2. Token 不正确,API 返回 401
  3. 网络不通,无法访问 CertCenter

排查步骤:

# 1. 手动测试版本接口
curl https://cert.example.com/api/version/example.com

# 2. 手动测试证书下载
curl -H "Authorization: Bearer tok_xxx" \
  https://cert.example.com/api/cert/example.com/fullchain

# 3. 查看脚本详细执行过程
bash -x /usr/local/bin/deploy-cert.sh

Q: nginx -t 校验失败,证书已回滚

原因: 新证书与 nginx 配置不匹配。

排查步骤:

# 1. 检查 nginx 配置
nginx -t

# 2. 检查证书内容
openssl x509 -in /etc/nginx/ssl/example.com/fullchain.pem -text -noout

# 3. 检查私钥是否匹配
openssl x509 -noout -modulus -in /etc/nginx/ssl/example.com/fullchain.pem | md5sum
openssl rsa -noout -modulus -in /etc/nginx/ssl/example.com/private.key | md5sum

Q: 如何更换 DNS 提商

  1. 在 WebUI 的"ACME 配置"页面修改"DNS 提商"下拉框
  2. 填写新提商的凭据
  3. 点击"保存凭据"
  4. 重新申请证书

Q: 如何备份和迁移

备份:

# 备份数据库
cp certcenter.db certcenter.db.bak

# 备份证书目录
tar czf certs-backup.tar.gz /srv/certs/

# 备份配置
cp .env .env.bak

迁移:

  1. 在新服务器部署项目
  2. 恢复数据库和证书目录
  3. 修改 .env 中的 BASE_URL 为新地址
  4. 更新业务服务器脚本中的地址(或重新执行部署命令)

Q: 如何添加通配符证书

通配符证书需要 DNS 验证,CertCenter 已支持。在域名管理中添加:

域名: *.example.com

申请时系统会自动通过 DNS-01 验证完成签发。

Q: Let's Encrypt 速率限制

Let's Encrypt 有以下限制:

限制类型 限制值
每个域名每周证书数 50 张
每个域名每小时重复证书数 5 张
注册账户数 每个 IP 每小时 10 个

建议: 先使用测试环境(Staging)调试,确认无误后再切换到生产环境。