From 2421089e68ddd8a770cafb8e0b159653956528e0 Mon Sep 17 00:00:00 2001 From: jingrow Date: Thu, 1 Jan 2026 18:43:06 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=A3=80=E6=9F=A5=EF=BC=8C=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 + ssl_manager/CONFIG.md | 121 -------- ssl_manager/DASHBOARD_TROUBLESHOOT.md | 231 -------------- ssl_manager/FULL_TEST_FLOW.md | 133 --------- ssl_manager/HTTP_VERIFY_ISSUE_ANALYSIS.md | 228 -------------- ssl_manager/README.md | 349 ---------------------- ssl_manager/TEST_README.md | 173 ----------- ssl_manager/route_watcher.py | 4 + 8 files changed, 11 insertions(+), 1235 deletions(-) delete mode 100644 ssl_manager/CONFIG.md delete mode 100644 ssl_manager/DASHBOARD_TROUBLESHOOT.md delete mode 100644 ssl_manager/FULL_TEST_FLOW.md delete mode 100644 ssl_manager/HTTP_VERIFY_ISSUE_ANALYSIS.md delete mode 100644 ssl_manager/README.md delete mode 100644 ssl_manager/TEST_README.md diff --git a/.gitignore b/.gitignore index 3df7d04..0aff9c7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ /etcd_data/* !/etcd_data/.keep + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python diff --git a/ssl_manager/CONFIG.md b/ssl_manager/CONFIG.md deleted file mode 100644 index 999c58b..0000000 --- a/ssl_manager/CONFIG.md +++ /dev/null @@ -1,121 +0,0 @@ -# 配置说明 - -## 配置方式 - -配置已直接定义在 Python 文件中,**不需要** `config.json` 文件。 - -### 方式一:直接修改 Python 文件(推荐) - -编辑 Python 文件中的 `DEFAULT_CONFIG` 字典: - -#### ssl_manager.py - -```python -DEFAULT_CONFIG = { - 'apisix_admin_url': 'http://localhost:9180', - 'apisix_admin_key': '8206e6e42b6b53243c52a767cc633137', - 'certbot_path': '/usr/bin/certbot', - 'cert_dir': '/etc/letsencrypt/live', - 'letsencrypt_email': 'admin@jingrowtools.cn', # 修改为你的邮箱 - 'letsencrypt_staging': True, # 生产环境改为 False - 'webroot_path': '/var/www/certbot' -} -``` - -#### test_ssl_auto.py - -```python -DEFAULT_TEST_CONFIG = { - 'apisix_admin_url': 'http://localhost:9180', - 'apisix_admin_key': '8206e6e42b6b53243c52a767cc633137', - 'webroot_path': '/var/www/certbot', - 'letsencrypt_staging': True -} -``` - -### 方式二:使用环境变量(推荐用于生产环境) - -通过环境变量覆盖默认配置: - -```bash -export APISIX_ADMIN_URL="http://localhost:9180" -export APISIX_ADMIN_KEY="your-admin-key" -export LETSENCRYPT_EMAIL="your-email@example.com" -export LETSENCRYPT_STAGING="false" -export WEBROOT_PATH="/var/www/certbot" -``` - -### 方式三:使用配置文件(可选,向后兼容) - -如果仍想使用 `config.json`,可以创建它,配置会覆盖默认值: - -```json -{ - "apisix_admin_url": "http://localhost:9180", - "apisix_admin_key": "your-admin-key", - "certbot_path": "/usr/bin/certbot", - "cert_dir": "/etc/letsencrypt/live", - "letsencrypt_email": "your-email@example.com", - "letsencrypt_staging": false, - "webroot_path": "/var/www/certbot" -} -``` - -然后使用 `--config` 参数: - -```bash -python3 ssl_manager.py request --domain example.com --config config.json -``` - -## 配置优先级 - -1. **环境变量**(最高优先级) -2. **配置文件**(如果使用 `--config` 参数) -3. **Python 文件中的 DEFAULT_CONFIG**(默认值) - -## 配置项说明 - -| 配置项 | 说明 | 默认值 | -|--------|------|--------| -| `apisix_admin_url` | APISIX Admin API 地址 | `http://localhost:9180` | -| `apisix_admin_key` | APISIX Admin API 密钥 | `8206e6e42b6b53243c52a767cc633137` | -| `certbot_path` | Certbot 可执行文件路径 | `/usr/bin/certbot` | -| `cert_dir` | 证书存储目录 | `/etc/letsencrypt/live` | -| `letsencrypt_email` | Let's Encrypt 邮箱 | `admin@jingrowtools.cn` | -| `letsencrypt_staging` | 是否使用 staging 模式 | `True`(测试环境) | -| `webroot_path` | Webroot 验证文件路径 | `/var/www/certbot` | - -## 快速配置 - -### 修改邮箱和切换到生产环境 - -编辑 `ssl_manager.py`: - -```python -DEFAULT_CONFIG = { - # ... 其他配置 ... - 'letsencrypt_email': 'your-email@example.com', # 修改这里 - 'letsencrypt_staging': False, # 改为 False 使用生产环境 - # ... 其他配置 ... -} -``` - -### 修改 APISIX Admin Key - -编辑 `ssl_manager.py`: - -```python -DEFAULT_CONFIG = { - 'apisix_admin_key': 'your-new-admin-key', # 修改这里 - # ... 其他配置 ... -} -``` - -## 注意事项 - -1. **Staging 模式**:默认使用 staging 模式(测试环境),不会消耗生产环境配额 -2. **生产环境**:切换到生产环境前,确保: - - 域名 DNS 已正确解析 - - 80 端口可访问 - - 验证路径可访问 -3. **安全性**:不要将包含敏感信息的配置文件提交到版本控制系统 diff --git a/ssl_manager/DASHBOARD_TROUBLESHOOT.md b/ssl_manager/DASHBOARD_TROUBLESHOOT.md deleted file mode 100644 index a42ab7b..0000000 --- a/ssl_manager/DASHBOARD_TROUBLESHOOT.md +++ /dev/null @@ -1,231 +0,0 @@ -# Dashboard 看不到路由和证书的排查指南 - -## 问题现象 - -测试脚本成功创建了路由和证书,但在 Dashboard 面板中看不到。 - -## 验证数据确实存在 - -### 通过 Admin API 验证 - -```bash -# 检查路由 -curl -s http://localhost:9180/apisix/admin/routes/test.jingrowtools.cn \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' - -# 检查 SSL -curl -s http://localhost:9180/apisix/admin/ssls/00000000000000000023 \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' -``` - -### 通过 etcd 验证 - -```bash -# 检查路由 -docker exec apisix-etcd-1 etcdctl get /apisix/routes/test.jingrowtools.cn - -# 检查 SSL -docker exec apisix-etcd-1 etcdctl get /apisix/ssls/00000000000000000023 -``` - -## 可能的原因和解决方案 - -### 1. Dashboard 缓存问题 ⚠️ **最常见** - -**原因:** Dashboard 可能缓存了数据,没有实时刷新。 - -**解决方案:** -- **刷新浏览器页面**(F5 或 Ctrl+R) -- **清除浏览器缓存**(Ctrl+Shift+Delete) -- **硬刷新**(Ctrl+Shift+R 或 Ctrl+F5) -- **等待几秒钟**后再次查看(Dashboard 可能有轮询间隔) - -### 2. Dashboard 过滤条件 - -**原因:** Dashboard 可能设置了过滤条件,隐藏了某些路由。 - -**解决方案:** -- 检查 Dashboard 的搜索/过滤框,确保没有输入过滤条件 -- 检查路由列表的显示选项(如:显示所有、只显示启用的等) -- 尝试搜索 `test.jingrowtools.cn` 看是否能找到 - -### 3. Dashboard 连接问题 - -**原因:** Dashboard 可能连接到了不同的 etcd 实例。 - -**检查方法:** -```bash -# 检查 Dashboard 日志 -docker logs apisix-dashboard --tail 50 | grep -i etcd - -# 检查 Dashboard 配置 -cat /home/jingrow/apisix/dashboard_conf/conf.yaml -``` - -**解决方案:** -- 确保 Dashboard 配置的 etcd 地址正确 -- 重启 Dashboard:`docker restart apisix-dashboard` - -### 4. 路由/证书状态问题 - -**原因:** Dashboard 可能只显示特定状态的路由/证书。 - -**检查方法:** -```bash -# 检查路由状态 -curl -s http://localhost:9180/apisix/admin/routes/test.jingrowtools.cn \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; print(json.load(sys.stdin)['value']['status'])" - -# 检查 SSL 状态 -curl -s http://localhost:9180/apisix/admin/ssls/00000000000000000023 \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; print(json.load(sys.stdin)['value']['status'])" -``` - -**解决方案:** -- 确保 `status: 1`(启用状态) -- 如果状态为 0,需要启用 - -### 5. Dashboard 版本兼容性问题 - -**原因:** Dashboard 版本可能与 APISIX 版本不兼容。 - -**检查方法:** -```bash -# 检查 Dashboard 版本 -docker exec apisix-dashboard cat /usr/local/apisix-dashboard/version 2>/dev/null || \ - docker exec apisix-dashboard ls /usr/local/apisix-dashboard/ - -# 检查 APISIX 版本 -docker exec apisix-apisix-1 apisix version -``` - -### 6. 权限问题 - -**原因:** Dashboard 用户可能没有查看权限。 - -**解决方案:** -- 检查 Dashboard 登录用户权限 -- 尝试使用管理员账户登录 - -## 快速排查步骤 - -### 步骤 1: 验证数据存在 - -```bash -# 检查路由是否存在 -curl -s http://localhost:9180/apisix/admin/routes \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; routes=json.load(sys.stdin)['list']; \ - test=[r for r in routes if 'test.jingrowtools.cn' in str(r.get('value', {}).get('name', ''))]; \ - print(f'找到测试路由: {len(test)} 个')" - -# 检查 SSL 是否存在 -curl -s http://localhost:9180/apisix/admin/ssls \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; ssls=json.load(sys.stdin)['list']; \ - test=[s for s in ssls if 'test.jingrowtools.cn' in str(s.get('value', {}).get('snis', []))]; \ - print(f'找到测试 SSL: {len(test)} 个')" -``` - -### 步骤 2: 刷新 Dashboard - -1. 在浏览器中按 `Ctrl+Shift+R` 硬刷新 -2. 等待 5-10 秒 -3. 再次查看路由和 SSL 列表 - -### 步骤 3: 检查 Dashboard 连接 - -```bash -# 重启 Dashboard -docker restart apisix-dashboard - -# 等待启动 -sleep 5 - -# 检查日志 -docker logs apisix-dashboard --tail 20 -``` - -### 步骤 4: 直接通过 Dashboard API 查询 - -```bash -# 访问 Dashboard API(需要登录 token) -# 在浏览器中打开开发者工具(F12),查看 Network 请求 -# 找到 Dashboard API 的请求,查看返回的数据 -``` - -## 常见解决方案 - -### 方案 1: 强制刷新(最简单) - -1. 打开 Dashboard -2. 按 `Ctrl+Shift+R` 硬刷新 -3. 等待几秒钟 -4. 再次查看 - -### 方案 2: 重启 Dashboard - -```bash -docker restart apisix-dashboard -``` - -等待 10 秒后刷新浏览器。 - -### 方案 3: 检查路由状态 - -如果路由状态为 0(禁用),需要启用: - -```bash -# 启用路由 -curl -X PUT http://localhost:9180/apisix/admin/routes/test.jingrowtools.cn \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' \ - -H 'Content-Type: application/json' \ - -d '{ - "uri": "/*", - "name": "test.jingrowtools.cn", - "host": "test.jingrowtools.cn", - "status": 1, - ... - }' -``` - -## 验证脚本 - -创建一个验证脚本检查 Dashboard 是否能访问数据: - -```bash -#!/bin/bash -# 验证 Dashboard 数据访问 - -echo "检查路由..." -curl -s http://localhost:9180/apisix/admin/routes \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; data=json.load(sys.stdin); \ - print(f'总路由数: {len(data[\"list\"])}'); \ - test=[r for r in data['list'] if 'test' in str(r.get('value', {}).get('name', ''))]; \ - print(f'测试路由数: {len(test)}')" - -echo "" -echo "检查 SSL..." -curl -s http://localhost:9180/apisix/admin/ssls \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' | \ - python3 -c "import sys, json; data=json.load(sys.stdin); \ - print(f'总 SSL 数: {len(data[\"list\"])}'); \ - test=[s for s in data['list'] if 'test' in str(s.get('value', {}).get('snis', []))]; \ - print(f'测试 SSL 数: {len(test)}')" -``` - -## 总结 - -**最可能的原因:** -1. ✅ Dashboard 缓存 - 需要刷新浏览器 -2. ✅ Dashboard 过滤条件 - 检查搜索框 -3. ✅ 数据状态 - 确保 status=1 - -**快速解决:** -1. 硬刷新浏览器(Ctrl+Shift+R) -2. 等待 5-10 秒 -3. 检查搜索/过滤条件 -4. 如果还不行,重启 Dashboard diff --git a/ssl_manager/FULL_TEST_FLOW.md b/ssl_manager/FULL_TEST_FLOW.md deleted file mode 100644 index 4df7d05..0000000 --- a/ssl_manager/FULL_TEST_FLOW.md +++ /dev/null @@ -1,133 +0,0 @@ -# 完整测试流程说明 - -## 是的,测试脚本执行的是完整的创建路由并自动申请 SSL 证书的流程 - -### 测试流程步骤 - -当使用 `python3 test_ssl_auto.py --domain test.jingrowtools.cn` 时,脚本会依次执行: - -#### 1. 检查系统依赖 ✅ -- 检查 Certbot 是否安装 -- 检查 Python 模块是否可用 -- 检查 OpenSSL 是否安装 - -#### 2. 检查 APISIX 服务 ✅ -- 验证 APISIX Admin API 是否可访问 -- 检查连接和认证 - -#### 3. 检查 Webroot 目录 ✅ -- 检查 `/var/www/certbot` 目录是否存在 -- 创建 `.well-known/acme-challenge` 子目录 - -#### 4. 检查/创建 Webroot 路由 ✅ **自动创建验证路由** -- 检查是否存在该域名的 webroot 路由 -- **如果不存在,自动创建**: - - 路由 ID: `certbot-webroot-{domain}` - - Host: 指定的域名 - - URI: `/.well-known/acme-challenge/*` - - Priority: 10000(最高优先级) - - 功能: 自动读取验证文件并返回 - -#### 5. 测试验证路径 ⚠️ **测试验证路由是否工作** -- 创建测试文件 -- 通过 APISIX 访问验证路径 -- 验证路由是否正确返回文件内容 - -#### 6. 创建测试路由 ✅ **创建用户指定的域名路由** -- **自动创建域名路由**: - - 路由 ID: 域名本身 - - Host: 指定的域名 - - URI: `/*` - - Upstream: 配置的后端服务 - - 这是实际业务路由 - -#### 7. 申请 SSL 证书 ✅ **自动申请证书** -- 使用 Certbot 申请 Let's Encrypt 证书 -- 自动处理验证流程 -- 证书保存到 `/etc/letsencrypt/live/{domain}/` - -#### 8. 同步证书到 APISIX ✅ **自动上传证书** -- 读取证书文件 -- 通过 APISIX Admin API 创建/更新 SSL 配置 -- 关联到域名 - -#### 9. 验证证书信息 ✅ -- 使用 OpenSSL 验证证书 -- 显示证书详细信息 - -## 完整流程示例 - -```bash -# 执行测试 -python3 test_ssl_auto.py --domain test.jingrowtools.cn --no-cleanup - -# 流程输出: -[步骤 1/9] 检查系统依赖 ✅ -[步骤 2/9] 检查 APISIX 服务 ✅ -[步骤 3/9] 检查 Webroot 目录 ✅ -[步骤 4/9] 检查/创建 Webroot 路由 ✅ - → 自动创建: certbot-webroot-test-jingrowtools-cn -[步骤 5/9] 测试验证路径 ✅ -[步骤 6/9] 创建测试路由 ✅ - → 自动创建: test.jingrowtools.cn (业务路由) -[步骤 7/9] 申请 SSL 证书 ✅ - → Certbot 自动申请证书 -[步骤 8/9] 同步证书到 APISIX ✅ - → 证书自动上传到 APISIX -[步骤 9/9] 验证证书信息 ✅ -``` - -## 自动化的部分 - -### ✅ 自动创建验证路由 -- 无需手动配置 webroot 路由 -- 脚本自动检测并创建 -- 确保验证路径可访问 - -### ✅ 自动创建业务路由 -- 根据指定域名自动创建路由 -- 配置默认的 upstream -- 路由立即可用 - -### ✅ 自动申请证书 -- 调用 Certbot 自动申请 -- 处理所有验证流程 -- 无需手动干预 - -### ✅ 自动同步证书 -- 读取证书文件 -- 自动上传到 APISIX -- 自动关联域名 - -## 权限问题处理 - -如果遇到权限问题(如测试输出中的错误),脚本已优化: - -1. **自动尝试使用 sudo**:如果直接写入失败,会尝试使用 sudo -2. **容错处理**:即使验证路径测试失败,也会继续执行证书申请(Certbot 会自动处理文件创建) -3. **提示信息**:提供清晰的权限修复建议 - -### 修复权限(推荐) - -```bash -# 一次性修复权限 -sudo mkdir -p /var/www/certbot/.well-known/acme-challenge -sudo chown -R $USER:$USER /var/www/certbot -sudo chmod -R 755 /var/www/certbot -``` - -## 总结 - -**是的,测试脚本执行的是完整的自动化流程:** - -1. ✅ **自动创建验证路由**(webroot 路由) -2. ✅ **自动创建业务路由**(用户指定的域名) -3. ✅ **自动申请 SSL 证书**(Let's Encrypt) -4. ✅ **自动同步证书到 APISIX**(上传并配置) - -整个过程完全自动化,只需要: -- 提供域名 -- 确保 DNS 已解析 -- 运行测试脚本 - -测试完成后,域名路由和 SSL 证书都已配置完成,可以直接使用 HTTPS 访问。 diff --git a/ssl_manager/HTTP_VERIFY_ISSUE_ANALYSIS.md b/ssl_manager/HTTP_VERIFY_ISSUE_ANALYSIS.md deleted file mode 100644 index 4a66d55..0000000 --- a/ssl_manager/HTTP_VERIFY_ISSUE_ANALYSIS.md +++ /dev/null @@ -1,228 +0,0 @@ -# HTTP-01 验证失败根源分析 - -## 问题现象 - -Let's Encrypt 在申请证书时返回: -``` -Domain: jingrowtools.cn -Type: unauthorized -Detail: 125.89.136.224: Invalid response from http://jingrowtools.cn/.well-known/acme-challenge/xxx: 404 -``` - -## 根本原因分析 - -### 1. **路由匹配优先级问题** ⚠️ **核心问题** - -**问题描述:** -- APISIX 路由匹配规则:`host` 匹配 > `uri` 匹配 > `priority` 优先级 -- 当前配置: - - `jingrowtools.cn` 路由:`host: "jingrowtools.cn"`, `uri: "/*"`, `priority: 0` - - `certbot-webroot` 路由:`uri: "/.well-known/acme-challenge/*"`, `priority: 9999`, **没有 host 限制** - -**匹配流程:** -1. 请求 `http://jingrowtools.cn/.well-known/acme-challenge/xxx` -2. APISIX 先匹配 `host: "jingrowtools.cn"` → 找到 `jingrowtools.cn` 路由 -3. 在该路由内匹配 `uri: "/*"` → **匹配成功** ✅ -4. 请求被转发到 upstream `192.168.10.2:8201` -5. **webroot 路由永远不会被匹配到** ❌ - -**根本原因:** -- `host` 匹配优先级高于 `uri` 匹配 -- 即使 webroot 路由的 `priority` 更高,但 `host` 匹配会先命中 `jingrowtools.cn` 路由 -- webroot 路由没有设置 `host`,导致无法优先匹配 - -### 2. **容器文件系统隔离问题** - -**问题描述:** -- APISIX 运行在 Docker 容器中 -- Certbot 在宿主机上运行 -- 验证文件写入到 `/var/www/certbot`(宿主机) -- 但 APISIX 容器内无法访问宿主机文件系统 - -**解决方案:** -- 需要在 `docker-compose.yml` 中挂载卷: - ```yaml - volumes: - - /var/www/certbot:/var/www/certbot:ro - ``` - -### 3. **Serverless 函数执行问题** - -**问题描述:** -- 使用 `serverless-pre-function` 插件读取文件 -- Lua 函数需要访问容器内的文件系统 -- 如果文件路径不正确或权限不足,会返回 404 - -**当前函数:** -```lua -local path = "/var/www/certbot/.well-known/acme-challenge/" .. string.match(ctx.var.uri, "/.well-known/acme-challenge/(.+)") -local file = io.open(path, "r") -``` - -**潜在问题:** -- 路径拼接可能不正确 -- 文件权限问题 -- 容器内文件不存在 - -## 解决方案 - -### 方案一:修复路由匹配(推荐)✅ - -**修改 webroot 路由,添加 host 匹配并提高优先级:** - -```bash -curl -X PUT 'http://localhost:9180/apisix/admin/routes/certbot-webroot' \ - -H 'X-API-KEY: 8206e6e42b6b53243c52a767cc633137' \ - -H 'Content-Type: application/json' \ - -d '{ - "uri": "/.well-known/acme-challenge/*", - "name": "certbot-webroot", - "priority": 10000, - "host": "jingrowtools.cn", - "plugins": { - "serverless-pre-function": { - "phase": "rewrite", - "functions": [ - "return function(conf, ctx) local token = string.match(ctx.var.uri, \"/.well-known/acme-challenge/(.+)\"); local path = \"/var/www/certbot/.well-known/acme-challenge/\" .. token; local file = io.open(path, \"r\"); if file then local content = file:read(\"*all\"); file:close(); ngx.header.content_type = \"text/plain\"; ngx.say(content); else ngx.status = 404; ngx.say(\"File not found: \" .. path); end end" - ] - } - }, - "status": 1 - }' -``` - -**关键修改:** -1. ✅ 添加 `"host": "jingrowtools.cn"` - 确保只匹配该域名 -2. ✅ 提高 `priority` 到 10000 - 确保优先匹配 -3. ✅ 修复 Lua 函数路径处理 - -### 方案二:修改主路由,排除验证路径 - -**在 `jingrowtools.cn` 路由中添加条件,排除验证路径:** - -```json -{ - "uri": "/*", - "name": "jingrowtools.cn", - "host": "jingrowtools.cn", - "vars": [ - ["uri", "!~", "^/.well-known/acme-challenge/"] - ], - ... -} -``` - -这样验证路径就不会被主路由匹配。 - -### 方案三:使用 DNS-01 验证(最可靠)✅✅ - -**优点:** -- 不依赖 HTTP 服务 -- 不需要配置 webroot 路由 -- 适合容器化环境 -- 支持通配符证书 - -**缺点:** -- 需要 DNS API 访问权限 -- 配置稍复杂 - -**实现:** -需要修改 `ssl_manager.py`,支持 DNS-01 验证。 - -### 方案四:使用独立的验证服务 - -**创建一个简单的 HTTP 服务专门处理验证:** - -```python -# 简单的验证服务 -from http.server import HTTPServer, BaseHTTPRequestHandler -import os - -class ACMEHandler(BaseHTTPRequestHandler): - def do_GET(self): - if self.path.startswith('/.well-known/acme-challenge/'): - token = self.path.split('/')[-1] - file_path = f'/var/www/certbot/.well-known/acme-challenge/{token}' - if os.path.exists(file_path): - with open(file_path, 'r') as f: - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(f.read().encode()) - else: - self.send_response(404) - self.end_headers() - else: - self.send_response(404) - self.end_headers() - -if __name__ == '__main__': - server = HTTPServer(('0.0.0.0', 8080), ACMEHandler) - server.serve_forever() -``` - -然后在 APISIX 中创建一个路由指向这个服务。 - -## 推荐解决步骤 - -### 立即修复(方案一) - -1. **确保容器挂载了 webroot 目录:** - ```yaml - # docker-compose.yml - volumes: - - /var/www/certbot:/var/www/certbot:ro - ``` - -2. **更新 webroot 路由配置(添加 host 和更高优先级):** - ```bash - # 使用上面的方案一配置 - ``` - -3. **测试验证路径:** - ```bash - echo "test-token" | sudo tee /var/www/certbot/.well-known/acme-challenge/test-token - curl http://jingrowtools.cn/.well-known/acme-challenge/test-token - # 应该返回: test-token - ``` - -4. **重新申请证书:** - ```bash - cd /home/jingrow/apisix/ssl_manager - python3 ssl_manager.py request --domain jingrowtools.cn - ``` - -## 验证流程说明 - -Let's Encrypt HTTP-01 验证流程: - -1. **Certbot 生成随机 token** - - 例如:`yUXNhDPsvsjEwYjxishk_XZ6kGZFRqn22FSUYcfuZQY` - -2. **写入验证文件** - - 路径:`/var/www/certbot/.well-known/acme-challenge/{token}` - - 内容:`{token}.{account_key_thumbprint}` - -3. **Let's Encrypt 服务器访问** - - URL: `http://jingrowtools.cn/.well-known/acme-challenge/{token}` - - 期望:返回文件内容 - -4. **APISIX 路由匹配** - - ❌ **当前问题**:匹配到 `jingrowtools.cn` 路由,转发到 upstream - - ✅ **应该**:匹配到 `certbot-webroot` 路由,返回文件内容 - -5. **验证结果** - - 成功:Let's Encrypt 收到正确内容 → 颁发证书 - - 失败:收到 404 或其他错误 → 拒绝申请 - -## 总结 - -**HTTP 验证失败的根本原因:** - -1. **主要问题**:路由匹配优先级 - `host` 匹配优先于 `uri` 匹配,导致验证请求被错误路由 -2. **次要问题**:容器文件系统隔离 - 需要正确挂载卷 -3. **潜在问题**:Serverless 函数路径处理 - 需要确保路径正确 - -**最佳解决方案:** -- 方案一(修复路由匹配)+ 容器卷挂载 = 最简单有效 -- 方案三(DNS-01 验证)= 最可靠,适合生产环境 diff --git a/ssl_manager/README.md b/ssl_manager/README.md deleted file mode 100644 index f54048d..0000000 --- a/ssl_manager/README.md +++ /dev/null @@ -1,349 +0,0 @@ -# APISIX SSL 证书自动管理 - -基于 Certbot + APISIX Admin API 的 SSL 证书自动申请和续期方案。 - -## 功能特性 - -- ✅ 自动申请 Let's Encrypt 免费 SSL 证书 -- ✅ 自动将证书同步到 APISIX -- ✅ 自动续期证书(90 天有效期) -- ✅ 监听路由创建,自动为新域名申请证书 -- ✅ 支持多域名证书 -- ✅ 支持测试环境(staging) - -## 目录结构 - -``` -ssl_manager/ -├── ssl_manager.py # SSL 证书管理主脚本 -├── route_watcher.py # 路由监听服务 -├── certbot_deploy_hook.sh # Certbot 部署钩子 -├── config.json # 配置文件 -├── requirements.txt # Python 依赖 -└── README.md # 说明文档 -``` - -## 安装步骤 - -### 1. 安装 Certbot - -```bash -# Ubuntu/Debian -sudo apt-get update -sudo apt-get install certbot - -# CentOS/RHEL -sudo yum install certbot -``` - -### 2. 安装 Python 依赖 - -```bash -cd /home/jingrow/apisix/ssl_manager - -# 方法一:使用 uv(推荐,更快) -uv pip install --system -r requirements.txt - -# 方法二:使用 pip3 -pip3 install -r requirements.txt -``` - -### 3. 配置 - -配置已直接定义在 Python 文件中,**不需要** `config.json`。 - -**方式一:直接修改 Python 文件(推荐)** - -编辑 `ssl_manager.py` 中的 `DEFAULT_CONFIG`: - -```python -DEFAULT_CONFIG = { - 'apisix_admin_url': 'http://localhost:9180', - 'apisix_admin_key': '你的APISIX管理密钥', # 修改这里 - 'letsencrypt_email': 'your-email@example.com', # 修改这里 - 'letsencrypt_staging': False, # 生产环境改为 False - # ... 其他配置 ... -} -``` - -**方式二:使用环境变量** - -```bash -export APISIX_ADMIN_KEY="your-admin-key" -export LETSENCRYPT_EMAIL="your-email@example.com" -export LETSENCRYPT_STAGING="false" -``` - -详细配置说明请查看 `CONFIG.md` - -### 4. 创建 Webroot 目录 - -Certbot 需要 webroot 目录用于验证: - -```bash -sudo mkdir -p /var/www/certbot -sudo chown -R www-data:www-data /var/www/certbot -``` - -### 5. 配置 APISIX 支持 Webroot 验证 - -需要在 APISIX 中配置一个路由,将 `/.well-known/acme-challenge/` 请求转发到 webroot 目录。 - -## 使用方法 - -### 手动申请证书 - -```bash -# 申请单个域名证书 -python3 ssl_manager.py request --domain example.com - -# 申请多域名证书 -python3 ssl_manager.py request --domain example.com --additional-domains www.example.com api.example.com -``` - -### 手动续期证书 - -```bash -# 续期单个域名 -python3 ssl_manager.py renew --domain example.com - -# 续期所有证书 -python3 ssl_manager.py renew-all -``` - -### 同步现有证书到 APISIX - -```bash -python3 ssl_manager.py sync --domain example.com -``` - -### 检查证书过期时间 - -```bash -python3 ssl_manager.py check --domain example.com -``` - -### 启动路由监听服务 - -```bash -# 持续监听(每 60 秒检查一次) -python3 route_watcher.py - -# 只执行一次 -python3 route_watcher.py --once - -# 自定义检查间隔 -python3 route_watcher.py --interval 30 -``` - -## 自动续期配置 - -### 方法一:使用 Certbot 自动续期 + 部署钩子 - -1. 配置 Certbot 自动续期: - -```bash -# 编辑 Certbot 续期配置 -sudo nano /etc/letsencrypt/renewal/example.com.conf -``` - -在配置文件中添加部署钩子: - -```ini -[renewalparams] -deploy_hook = /home/jingrow/apisix/ssl_manager/certbot_deploy_hook.sh -``` - -2. 测试续期: - -```bash -sudo certbot renew --dry-run -``` - -3. 设置 systemd timer(推荐): - -创建 `/etc/systemd/system/certbot-renew.timer`: - -```ini -[Unit] -Description=Certbot Renewal Timer - -[Timer] -OnCalendar=daily -RandomizedDelaySec=3600 -Persistent=true - -[Install] -WantedBy=timers.target -``` - -创建 `/etc/systemd/system/certbot-renew.service`: - -```ini -[Unit] -Description=Certbot Renewal Service -After=network.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/certbot renew --quiet --deploy-hook /home/jingrow/apisix/ssl_manager/certbot_deploy_hook.sh -``` - -启用定时器: - -```bash -sudo systemctl enable certbot-renew.timer -sudo systemctl start certbot-renew.timer -``` - -### 方法二:使用自定义脚本 + systemd timer - -创建 `/etc/systemd/system/apisix-ssl-renew.service`: - -```ini -[Unit] -Description=APISIX SSL Certificate Renewal -After=network.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/python3 /home/jingrow/apisix/ssl_manager/ssl_manager.py renew-all -User=root -``` - -创建 `/etc/systemd/system/apisix-ssl-renew.timer`: - -```ini -[Unit] -Description=APISIX SSL Certificate Renewal Timer - -[Timer] -OnCalendar=weekly -RandomizedDelaySec=3600 -Persistent=true - -[Install] -WantedBy=timers.target -``` - -启用: - -```bash -sudo systemctl enable apisix-ssl-renew.timer -sudo systemctl start apisix-ssl-renew.timer -``` - -## 路由监听服务 - -路由监听服务会自动检测新创建的路由,并为其中的域名申请证书。 - -### 启动服务 - -```bash -# 使用 systemd(推荐) -sudo systemctl start apisix-route-watcher.service - -# 或使用 screen/tmux -screen -S route-watcher -python3 route_watcher.py -``` - -### 创建 systemd 服务 - -创建 `/etc/systemd/system/apisix-route-watcher.service`: - -```ini -[Unit] -Description=APISIX Route Watcher Service -After=network.target - -[Service] -Type=simple -ExecStart=/usr/bin/python3 /home/jingrow/apisix/ssl_manager/route_watcher.py -Restart=always -RestartSec=10 -User=root - -[Install] -WantedBy=multi-user.target -``` - -启用服务: - -```bash -sudo systemctl enable apisix-route-watcher.service -sudo systemctl start apisix-route-watcher.service -``` - -## 环境变量配置 - -也可以通过环境变量配置: - -```bash -export APISIX_ADMIN_URL="http://localhost:9180" -export APISIX_ADMIN_KEY="your-admin-key" -export LETSENCRYPT_EMAIL="your-email@example.com" -export LETSENCRYPT_STAGING="false" -export WEBROOT_PATH="/var/www/certbot" -``` - -## 注意事项 - -1. **首次使用建议使用 staging 模式测试**: - ```json - "letsencrypt_staging": true - ``` - -2. **确保 80 端口可访问**:Let's Encrypt 需要验证域名所有权,通常使用 HTTP-01 验证方式。 - -3. **DNS 验证**:如果无法使用 HTTP-01 验证,可以配置 DNS-01 验证(需要修改脚本)。 - -4. **证书存储**:证书存储在 `/etc/letsencrypt/live/` 目录,请确保有备份。 - -5. **日志位置**: - - SSL 管理器日志:`/var/log/apisix-ssl-manager.log` - - 路由监听日志:`/var/log/apisix-route-watcher.log` - - Certbot 部署钩子日志:`/var/log/apisix-certbot-deploy.log` - -## 故障排查 - -### 证书申请失败 - -1. 检查域名 DNS 解析是否正确 -2. 检查 80 端口是否可访问 -3. 检查 webroot 目录权限 -4. 查看 Certbot 日志:`/var/log/letsencrypt/letsencrypt.log` - -### 证书无法同步到 APISIX - -1. 检查 APISIX Admin API 是否可访问 -2. 检查 Admin Key 是否正确 -3. 查看 SSL 管理器日志 - -### 自动续期不工作 - -1. 检查 systemd timer 状态:`systemctl status certbot-renew.timer` -2. 手动测试续期:`certbot renew --dry-run` -3. 检查部署钩子脚本权限:`chmod +x certbot_deploy_hook.sh` - -## 测试 - -### 测试环境(staging) - -```json -"letsencrypt_staging": true -``` - -Staging 环境有更高的申请频率限制,适合测试。 - -### 生产环境 - -测试通过后,设置: - -```json -"letsencrypt_staging": false -``` - -## 许可证 - -Apache License 2.0 diff --git a/ssl_manager/TEST_README.md b/ssl_manager/TEST_README.md deleted file mode 100644 index 7b21206..0000000 --- a/ssl_manager/TEST_README.md +++ /dev/null @@ -1,173 +0,0 @@ -# SSL 证书自动申请测试脚本使用说明 - -## 功能 - -`test_ssl_auto.py` 是一个完整的测试脚本,用于测试从路由创建到 SSL 证书申请的整个流程。 - -## 测试步骤 - -脚本会依次执行以下步骤,每个步骤都会显示成功或失败: - -1. **检查系统依赖** - 检查 certbot、Python 模块等 -2. **检查 APISIX 服务** - 验证 APISIX Admin API 是否可访问 -3. **检查 Webroot 目录** - 检查验证文件目录是否存在 -4. **检查/创建 Webroot 路由** - 检查或创建验证路由 -5. **测试验证路径** - 测试验证路径是否可访问 -6. **创建测试路由** - 创建测试域名路由 -7. **申请 SSL 证书** - 使用 Let's Encrypt 申请证书 -8. **同步证书到 APISIX** - 将证书上传到 APISIX -9. **验证证书信息** - 验证证书是否有效 - -## 使用方法 - -### 基本用法 - -```bash -# 使用自动生成的测试域名(测试完成后自动清理) -python3 test_ssl_auto.py - -# 指定测试域名 -python3 test_ssl_auto.py --domain test.example.com - -# 指定配置文件 -python3 test_ssl_auto.py --config config.json --domain test.example.com -``` - -### 选项说明 - -- `--domain, -d`: 指定测试域名(不指定则自动生成) -- `--config, -c`: 指定配置文件路径(默认使用环境变量或默认配置) -- `--cleanup`: 测试完成后清理测试数据(路由和 SSL 配置) -- `--no-cleanup`: 测试完成后不清理测试数据 - -### 示例 - -```bash -# 示例1: 快速测试(自动生成域名,自动清理) -python3 test_ssl_auto.py - -# 示例2: 测试指定域名(保留测试数据) -python3 test_ssl_auto.py --domain test.jingrowtools.cn --no-cleanup - -# 示例3: 使用配置文件,测试完成后清理 -python3 test_ssl_auto.py --config config.json --domain test.jingrowtools.cn --cleanup -``` - -## 输出说明 - -### 成功输出示例 - -``` -✅ 检查系统依赖 - 成功 -✅ 检查 APISIX 服务 - 成功 -✅ 检查 Webroot 目录 - 成功 -... -``` - -### 失败输出示例 - -``` -❌ 检查系统依赖 - 失败 -❌ 检查 APISIX 服务 - 异常: Connection refused -... -``` - -## 注意事项 - -1. **域名解析**: 测试域名必须 DNS 解析到当前服务器,否则证书申请会失败 -2. **Staging 模式**: 默认使用 staging 模式(测试环境),不会消耗生产环境配额 -3. **权限要求**: 某些操作需要 root 权限(如创建证书文件) -4. **清理数据**: 使用自动生成的域名时,默认会清理测试数据;指定域名时,默认保留数据 - -## 测试流程 - -``` -开始测试 - ↓ -检查依赖 → 失败 → 结束 - ↓ 成功 -检查 APISIX → 失败 → 结束 - ↓ 成功 -检查 Webroot → 失败 → 结束 - ↓ 成功 -检查/创建路由 → 失败 → 结束 - ↓ 成功 -测试验证路径 → 失败 → 结束 - ↓ 成功 -创建测试路由 → 失败 → 结束 - ↓ 成功 -申请证书 → 失败 → 结束 - ↓ 成功 -同步证书 → 失败 → 结束 - ↓ 成功 -验证证书 → 完成 - ↓ -清理数据(可选) - ↓ -显示测试总结 -``` - -## 故障排查 - -### 1. Certbot 未安装 - -```bash -sudo apt-get install certbot -``` - -### 2. APISIX 服务不可访问 - -```bash -# 检查 APISIX 是否运行 -docker ps | grep apisix - -# 检查端口 -netstat -tlnp | grep 9180 -``` - -### 3. Webroot 目录不存在 - -```bash -sudo mkdir -p /var/www/certbot/.well-known/acme-challenge -sudo chown -R www-data:www-data /var/www/certbot -``` - -### 4. 验证路径不可访问 - -- 检查 webroot 路由是否正确配置 -- 检查路由 priority 是否足够高 -- 检查路由 host 是否匹配 - -## 完整测试示例 - -```bash -# 1. 准备测试域名(确保 DNS 已解析) -export TEST_DOMAIN="test-$(date +%s).jingrowtools.cn" - -# 2. 运行测试 -python3 test_ssl_auto.py --domain $TEST_DOMAIN --no-cleanup - -# 3. 检查结果 -curl -v https://$TEST_DOMAIN - -# 4. 清理(如果需要) -python3 ssl_manager.py sync --domain $TEST_DOMAIN # 先同步证书 -# 然后手动删除路由和 SSL 配置 -``` - -## 与生产环境集成 - -测试通过后,可以: - -1. 修改 `config.json` 设置 `letsencrypt_staging: false` -2. 使用 `ssl_manager.py` 申请生产证书 -3. 配置自动续期 - -```bash -# 申请生产证书 -python3 ssl_manager.py request --domain your-domain.com - -# 启用自动续期 -sudo systemctl enable apisix-ssl-renew.timer -sudo systemctl start apisix-ssl-renew.timer -``` diff --git a/ssl_manager/route_watcher.py b/ssl_manager/route_watcher.py index 8dd0c76..8675e94 100755 --- a/ssl_manager/route_watcher.py +++ b/ssl_manager/route_watcher.py @@ -188,6 +188,10 @@ class RouteWatcher: # 从路由中提取所有域名 for route in routes: + route_value = route.get('value', {}) + # 跳过禁用的路由(status=0 表示禁用) + if route_value.get('status') == 0: + continue domains = self.extract_domains_from_route(route) new_domains.update(domains)