From 06b23dcd96982ac15383453b00e58fda634b09c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 6 Oct 2025 09:32:14 +0200 Subject: [PATCH] praca za proxy --- app/api.py | 78 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/app/api.py b/app/api.py index a904bb8..c8f6b9d 100644 --- a/app/api.py +++ b/app/api.py @@ -10,6 +10,13 @@ import re router = APIRouter() security = HTTPBasic() +VENDOR_SINGLE_IP_HEADERS = [ + "cf-connecting-ip", # Cloudflare + "true-client-ip", # Akamai/F5 + "x-cluster-client-ip", # niektóre load balancery + "x-real-ip", # klasyk (nginx/traefik) +] + def _check_admin(creds: HTTPBasicCredentials): user = settings.admin_user pwd = settings.admin_pass @@ -57,32 +64,48 @@ def _extract_from_forwarded(header_value: str) -> list[str]: return ips def get_client_ip(request: Request) -> str: - # 1) X-Forwarded-For + """ + Zwraca IP klienta biorąc pod uwagę: + - CF-Connecting-IP / True-Client-IP / X-Cluster-Client-Ip / X-Real-IP + - X-Forwarded-For (RFC7239 semantyka: client, proxy1, proxy2) + - Forwarded: for=... + Logika XFF: + - weź listę IP + - zdejmuj od PRAWEJ strony te, które są zaufanymi proxy + - zwróć ostatni pozostały (jeśli nic nie zostało, zwróć lewy skrajny) + """ + # 0) Vendorowe nagłówki z pojedynczym IP (preferowane, jeśli są i nie są zaufane) + for h in VENDOR_SINGLE_IP_HEADERS: + v = request.headers.get(h) + if v: + ip = _normalize_ip_str(v) + if ip: + if not settings.trusted_proxies or not _is_ip_trusted(ip): + return ip + # jeśli vendor wskazuje zaufane proxy, idź dalej + + # 1) X-Forwarded-For (client, proxy1, proxy2...) xff = request.headers.get("x-forwarded-for") if xff: - # XFF: client, proxy1, proxy2 raw_ips = [p.strip() for p in xff.split(",") if p.strip()] - # Normalizuj i usuń porty norm_ips = [] for raw in raw_ips: v = _normalize_ip_str(raw) if v: norm_ips.append(v) - # jeśli mamy zaufane proxy -> zwracamy pierwsze IP, które NIE jest zaufane - if settings.trusted_proxies: - for ip in norm_ips: - try: - # ignoruj jeżeli to zaufane proxy - if not _is_ip_trusted(ip): - return ip - except Exception: - continue - # jeśli wszystkie były zaufane, zwróć pierwsze (najbardziej "client" lub lewo) - if norm_ips: + + if norm_ips: + if settings.trusted_prox ies: + # zdejmuj od PRAWEJ strony zaufane hop’y + tmp = norm_ips[:] + while tmp and _is_ip_trusted(tmp[-1]): + tmp.pop() + if tmp: + return tmp[-1] # ostatni niezaufany = klient + # w skrajnym przypadku wszystkie są zaufane – zwróć najbardziej „kliencki” (lewy) return norm_ips[0] - else: - # bez zaufanych proxy: przyjmujemy lewą (first) jako klienta - if norm_ips: + else: + # bez listy zaufanych proxy bierzemy lewy skrajny return norm_ips[0] # 2) Forwarded (RFC7239) @@ -92,24 +115,15 @@ def get_client_ip(request: Request) -> str: norm_ips = [_normalize_ip_str(ip) for ip in fwd_ips if _normalize_ip_str(ip)] if norm_ips: if settings.trusted_proxies: - for ip in norm_ips: - if not _is_ip_trusted(ip): - return ip + tmp = norm_ips[:] + while tmp and _is_ip_trusted(tmp[-1]): + tmp.pop() + if tmp: + return tmp[-1] return norm_ips[0] return norm_ips[0] - # 3) X-Real-IP - xri = request.headers.get("x-real-ip") - if xri: - v = _normalize_ip_str(xri) - if v: - if settings.trusted_proxies and _is_ip_trusted(v): - # jeśli header wskazuje zaufane proxy - nie używamy go jako klienta - pass - else: - return v - - # Fallback: request.client.host (np. bez reverse-proxy lub jeśli nic innego) + # 3) Fallback: request.client.host try: host = request.client.host if host: