6.9 KiB
6.9 KiB
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: 0certbot-webroot路由:uri: "/.well-known/acme-challenge/*",priority: 9999, 没有 host 限制
匹配流程:
- 请求
http://jingrowtools.cn/.well-known/acme-challenge/xxx - APISIX 先匹配
host: "jingrowtools.cn"→ 找到jingrowtools.cn路由 - 在该路由内匹配
uri: "/*"→ 匹配成功 ✅ - 请求被转发到 upstream
192.168.10.2:8201 - webroot 路由永远不会被匹配到 ❌
根本原因:
host匹配优先级高于uri匹配- 即使 webroot 路由的
priority更高,但host匹配会先命中jingrowtools.cn路由 - webroot 路由没有设置
host,导致无法优先匹配
2. 容器文件系统隔离问题
问题描述:
- APISIX 运行在 Docker 容器中
- Certbot 在宿主机上运行
- 验证文件写入到
/var/www/certbot(宿主机) - 但 APISIX 容器内无法访问宿主机文件系统
解决方案:
- 需要在
docker-compose.yml中挂载卷:volumes: - /var/www/certbot:/var/www/certbot:ro
3. Serverless 函数执行问题
问题描述:
- 使用
serverless-pre-function插件读取文件 - Lua 函数需要访问容器内的文件系统
- 如果文件路径不正确或权限不足,会返回 404
当前函数:
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 匹配并提高优先级:
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
}'
关键修改:
- ✅ 添加
"host": "jingrowtools.cn"- 确保只匹配该域名 - ✅ 提高
priority到 10000 - 确保优先匹配 - ✅ 修复 Lua 函数路径处理
方案二:修改主路由,排除验证路径
在 jingrowtools.cn 路由中添加条件,排除验证路径:
{
"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 服务专门处理验证:
# 简单的验证服务
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 中创建一个路由指向这个服务。
推荐解决步骤
立即修复(方案一)
-
确保容器挂载了 webroot 目录:
# docker-compose.yml volumes: - /var/www/certbot:/var/www/certbot:ro -
更新 webroot 路由配置(添加 host 和更高优先级):
# 使用上面的方案一配置 -
测试验证路径:
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 -
重新申请证书:
cd /home/jingrow/apisix/ssl_manager python3 ssl_manager.py request --domain jingrowtools.cn
验证流程说明
Let's Encrypt HTTP-01 验证流程:
-
Certbot 生成随机 token
- 例如:
yUXNhDPsvsjEwYjxishk_XZ6kGZFRqn22FSUYcfuZQY
- 例如:
-
写入验证文件
- 路径:
/var/www/certbot/.well-known/acme-challenge/{token} - 内容:
{token}.{account_key_thumbprint}
- 路径:
-
Let's Encrypt 服务器访问
- URL:
http://jingrowtools.cn/.well-known/acme-challenge/{token} - 期望:返回文件内容
- URL:
-
APISIX 路由匹配
- ❌ 当前问题:匹配到
jingrowtools.cn路由,转发到 upstream - ✅ 应该:匹配到
certbot-webroot路由,返回文件内容
- ❌ 当前问题:匹配到
-
验证结果
- 成功:Let's Encrypt 收到正确内容 → 颁发证书
- 失败:收到 404 或其他错误 → 拒绝申请
总结
HTTP 验证失败的根本原因:
- 主要问题:路由匹配优先级 -
host匹配优先于uri匹配,导致验证请求被错误路由 - 次要问题:容器文件系统隔离 - 需要正确挂载卷
- 潜在问题:Serverless 函数路径处理 - 需要确保路径正确
最佳解决方案:
- 方案一(修复路由匹配)+ 容器卷挂载 = 最简单有效
- 方案三(DNS-01 验证)= 最可靠,适合生产环境