diff --git a/app/core/security.py b/app/core/security.py index 2630190..8d294cc 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -55,13 +55,17 @@ def _is_trusted(ip: str) -> bool: return any(addr in net for net in TRUSTED_NETWORKS) -async def require_api_key( - request: Request, - x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"), -) -> None: - """校验 API Key:Tailscale 内网/本机放行,外部来源必须携带有效 Key""" - if _is_trusted(_client_ip(request)): - return +def _is_funnel_request(request: Request) -> bool: + """判断是否 tailscale funnel 公网流量 + + tailscale serve 转发时会把客户端伪造的 Tailscale-* 头清空后按真实 + 情况设置:tailnet 内无此头,funnel 公网流量为 "?1"(不可伪造)。 + """ + 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: raise HTTPException( 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 Key:Tailscale 内网/本机放行,外部来源必须携带有效 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( x_agent_key: Optional[str] = Header(default=None, alias="X-Agent-Key"), ) -> None: