Files
acme-auto/README.md
T
2026-07-18 20:09:26 +08:00

884 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CertCenter - 轻量化证书管理中心
一站式 SSL 证书管理平台,集成 ACME 证书申请、自动续签、多服务器分发,通过 Web UI 完成所有操作。
---
## 目录
- [架构概览](#架构概览)
- [环境要求](#环境要求)
- [安装部署](#安装部署)
- [WebUI 操作指南](#webui-操作指南)
- [仪表盘](#1-仪表盘)
- [服务器管理](#2-服务器管理)
- [域名管理](#3-域名管理)
- [ACME 配置](#4-acme-配置)
- [部署日志](#5-部署日志)
- [ACME 日志](#6-acme-日志)
- [测试验证](#测试验证)
- [环境检查](#1-环境检查)
- [端到端测试](#2-端到端测试)
- [客户端部署指南](#客户端部署指南)
- [Linux 服务器](#linux-服务器)
- [Windows 服务器](#windows-服务器)
- [API 参考](#api-参考)
- [客户端 API](#客户端-api)
- [管理 API](#管理-api)
- [配置说明](#配置说明)
- [常见问题](#常见问题)
---
## 架构概览
```
┌─────────────────────────────────────────────────────────┐
│ 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 版本
```bash
python --version
# 需要 3.11.x 或 3.12.x,不支持 3.14
```
如果版本不对,先装 Python 3.12
- 下载地址:https://www.python.org/downloads/release/python-3128/
- 安装时勾选 "Add python.exe to PATH"
### 1. 克隆项目
```bash
git clone <repo-url> certcenter
cd certcenter
```
### 2. 创建虚拟环境并安装依赖
```bash
# 创建虚拟环境(指定 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 插件:
```bash
pip install certbot-dns-alicloud
```
### 3. 安装前端依赖并构建
```bash
cd frontend
npm install
npm run build
cd ..
```
构建完成后,`frontend/dist/` 目录包含前端静态文件,FastAPI 会自动加载。
### 4. 配置环境变量
```bash
cp .env .env
```
编辑 `.env` 文件:
```ini
# 数据库路径(SQLite
DATABASE_URL=sqlite+aiosqlite:///./certcenter.db
# 证书存储目录
CERT_DIR=/srv/certs
# CertCenter 的访问地址(客户端通过此地址拉取证书)
BASE_URL=https://cert.example.com
```
### 5. 启动服务
```bash
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 托管服务:
```bash
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 反向代理:
```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. 环境检查
运行部署验证脚本,检查所有依赖和配置:
```bash
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 服务已启动
**执行测试:**
```bash
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-blog``api-server`
- **平台**:选择 `Linux``Windows`
- **Token**:点击"生成"按钮自动生成,或手动填写
- **IP**:可选,仅用于备注
4. 点击"创建"
**编辑/删除服务器:**
- 在列表中点击"编辑"修改信息
- 点击"删除"会同时删除该服务器下的所有域名配置
### 3. 域名管理
**新增域名:**
1. 点击侧边栏"域名管理"
2. 点击"+ 新增域名"
3. 填写表单:
- **所属服务器**:选择域名所在的业务服务器
- **域名**:如 `example.com``api.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. 登录 [阿里云控制台](https://ram.console.aliyun.com/)
2. 创建子账号,授予 `AliyunDNSFullAccess` 权限
3. 创建 AccessKey,记录 Access Key ID 和 Access Key Secret
**自动续签:**
点击"🔄 自动续签所有"按钮,系统会:
1. 检查所有域名的证书到期时间
2. 对即将过期的证书自动执行续签
3. 显示每个域名的续签结果
### 5. 部署日志
查看所有客户端(业务服务器)的证书拉取部署记录:
- **时间**:部署执行时间
- **服务器**:执行部署的服务器
- **域名**:部署的域名
- **状态**:成功 / 失败 / 跳过
- **消息**:详细信息
支持按状态筛选。
### 6. ACME 日志
查看所有证书申请/续签操作记录:
- **时间**:操作时间
- **域名**:操作的域名
- **操作**:申请 / 续签 / 吊销 / 注册
- **状态**:成功 / 失败 / 进行中
- **消息**:结果信息
- **详情**:点击"查看"显示完整日志
---
## 客户端部署指南
### Linux 服务器
**首次部署(只需执行一次):**
```bash
# 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 -
```
**手动执行测试:**
```bash
/usr/local/bin/deploy-cert.sh
```
**查看部署日志:**
```bash
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 服务器
**首次部署:**
```powershell
# 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
powershell -ExecutionPolicy Bypass -File C:\scripts\deploy-cert.ps1
```
---
## API 参考
### 客户端 API
供业务服务器上的部署脚本调用,使用 Bearer Token 认证。
#### 获取证书版本号
```
GET /api/version/{domain}
```
- 认证:无需认证
- 返回:版本号字符串(纯文本)
**示例:**
```bash
curl https://cert.example.com/api/version/example.com
# 返回: 3
```
#### 下载证书
```
GET /api/cert/{domain}/fullchain
GET /api/cert/{domain}/private
```
- 认证:`Authorization: Bearer <token>`
- 返回:PEM 格式证书文件
**示例:**
```bash
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
**示例:**
```bash
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>`
- 参数:
- `status``success` / `failed` / `skipped`
- `message`:可选,详细信息
**示例:**
```bash
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
```
返回:
```json
{
"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}` | 删除服务器 |
**新增/编辑请求体:**
```json
{
"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}` | 删除域名 |
**新增/编辑请求体:**
```json
{
"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 配置 |
**更新请求体:**
```json
{
"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}
```
返回:
```json
{
"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
**排查步骤:**
```bash
# 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 配置不匹配。
**排查步骤:**
```bash
# 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: 如何备份和迁移
**备份:**
```bash
# 备份数据库
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)调试,确认无误后再切换到生产环境。