fix: 添加自动创建/更新 webroot 路由功能,修复证书申请时的路由匹配问题

- 新增 ensure_webroot_route 方法,自动创建或更新 APISIX webroot 路由
- 支持为指定域名创建带 host 字段的路由,确保正确匹配
- 在申请证书前自动确保 webroot 路由存在并配置正确
- 设置路由优先级为 99999,确保优先匹配 /.well-known/acme-challenge/* 路径
- 使用 serverless-pre-function 插件从 /var/www/certbot 目录读取验证文件
- 修复了证书申请时因路由匹配问题导致的 404 错误
This commit is contained in:
jingrow 2026-01-03 16:53:16 +08:00
parent 3478db383e
commit db30f3d7f1

View File

@ -295,6 +295,78 @@ class APISIXSSLManager:
logger.error(f"上传证书到 APISIX 失败: {e}")
return False
def ensure_webroot_route(self, domain: str = None) -> bool:
"""确保 webroot 路由存在并正确配置
Args:
domain: 域名如果提供则创建针对该域名的路由否则创建通用路由
"""
if domain:
route_id = f"certbot-webroot-{domain.replace('.', '-')}"
else:
route_id = "certbot-webroot"
route_url = f"{self.apisix_admin_url}/apisix/admin/routes/{route_id}"
try:
# 检查路由是否存在
response = self.session.get(route_url, timeout=10)
route_config = {
"uri": "/.well-known/acme-challenge/*",
"name": route_id,
"priority": 99999,
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function(conf, ctx) local uri = ngx.var.uri; if not uri then uri = ctx.var.uri; end local token = string.match(uri, \"^/%.well%-known/acme%-challenge/(.+)$\"); if not token then ngx.status = 404; ngx.say(\"Token not found. URI: \" .. (uri or \"nil\")); return; end; 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 字段以确保正确匹配
if domain:
route_config["host"] = domain
if response.status_code == 200:
# 路由存在,检查是否需要更新
existing_route = response.json().get('value', {})
existing_priority = existing_route.get('priority', 0)
existing_host = existing_route.get('host')
# 检查是否需要更新(优先级或 host 不匹配)
needs_update = (existing_priority < 99999) or (domain and existing_host != domain)
if needs_update:
logger.info(f"更新 webroot 路由: {route_id}")
response = self.session.put(route_url, json=route_config, timeout=10)
if response.status_code in [200, 201]:
logger.info("Webroot 路由更新成功")
return True
else:
logger.warning(f"Webroot 路由更新失败: {response.status_code} - {response.text}")
return False
else:
logger.debug(f"Webroot 路由已存在且配置正确: {route_id}")
return True
else:
# 路由不存在,创建它
logger.info(f"创建 webroot 路由: {route_id}")
response = self.session.put(route_url, json=route_config, timeout=10)
if response.status_code in [200, 201]:
logger.info("Webroot 路由创建成功")
return True
else:
logger.error(f"Webroot 路由创建失败: {response.status_code} - {response.text}")
return False
except Exception as e:
logger.error(f"确保 webroot 路由时出错: {e}")
return False
def request_certificate(self, domain: str, additional_domains: List[str] = None, max_retries: int = 3) -> bool:
"""申请 Let's Encrypt 证书
@ -303,6 +375,9 @@ class APISIXSSLManager:
additional_domains: 额外域名列表
max_retries: 最大重试次数默认3次
"""
# 确保 webroot 路由存在(为当前域名创建)
self.ensure_webroot_route(domain)
domains = [domain]
if additional_domains:
domains.extend(additional_domains)