fix(security): funnel公网流量强制校验API_KEY双保险(Tailscale-Funnel-Request头,不可伪造)

This commit is contained in:
gouki
2026-08-15 22:24:47 +00:00
parent 8fea6c2288
commit 2fbb658126
+25 -7
View File
@@ -55,13 +55,17 @@ def _is_trusted(ip: str) -> bool:
return any(addr in net for net in TRUSTED_NETWORKS) return any(addr in net for net in TRUSTED_NETWORKS)
async def require_api_key( def _is_funnel_request(request: Request) -> bool:
request: Request, """判断是否 tailscale funnel 公网流量
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
) -> None: tailscale serve 转发时会把客户端伪造的 Tailscale-* 头清空后按真实
"""校验 API KeyTailscale 内网/本机放行,外部来源必须携带有效 Key""" 情况设置:tailnet 内无此头,funnel 公网流量为 "?1"(不可伪造)。
if _is_trusted(_client_ip(request)): """
return return request.headers.get("tailscale-funnel-request") == "?1"
async def _require_key(x_api_key: Optional[str]) -> None:
"""按外部来源校验 API Key(可信内网已在调用方排除)"""
if not settings.API_KEY: if not settings.API_KEY:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
@@ -74,6 +78,20 @@ async def require_api_key(
) )
async def require_api_key(
request: Request,
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
) -> None:
"""校验 API KeyTailscale 内网/本机放行,外部来源必须携带有效 Key"""
# funnel 公网流量:即使来源 IP 恰在 tailnet 网段也强制校验(双保险)
if _is_funnel_request(request):
await _require_key(x_api_key)
return
if _is_trusted(_client_ip(request)):
return
await _require_key(x_api_key)
async def require_agent_key( async def require_agent_key(
x_agent_key: Optional[str] = Header(default=None, alias="X-Agent-Key"), x_agent_key: Optional[str] = Header(default=None, alias="X-Agent-Key"),
) -> None: ) -> None: