""" Проверка геолокации сервера. Согласно 152-ФЗ (ст. 18), сбор персональных данных граждан РФ должен производиться с использованием баз данных, находящихся на территории РФ. """ from __future__ import annotations import socket from urllib.parse import urlparse from playwright.async_api import Page from scanner.checks.base import BaseCheck from scanner.models import CheckResult, RiskLevel class ServerLocationCheck(BaseCheck): """Проверяет, находится ли сервер сайта в России.""" name = "server_location" title = "Геолокация сервера" description = "Проверяет, находится ли сервер сайта на территории РФ" risk_on_fail = RiskLevel.CRITICAL default_weight = 25 # Популярные русские DNS-серверы/хостеры RUSSIAN_HOSTERS = [ "yandex", "mail.ru", "vk", "selectel", "timeweb", "beget", "reg.ru", "nic.ru", "rtcdn", "astra", "sbercloud", "cloud4russia", "гостех", "ростелеком", "мтс", "билайн", "мегафон", "вымпелком", "netology", "yandex.net", "mail.ru", "vk.com", "selectel.cloud", ] async def check(self, page: Page, url: str) -> CheckResult: try: # Извлекаем домен из URL parsed = urlparse(url) hostname = parsed.hostname or "" if not hostname: return self._skip_result(message="Не удалось извлечь домен из URL.") # Получаем IP через DNS try: ip = socket.gethostbyname(hostname) except socket.gaierror: return self._skip_result(message=f"Не удалось разрешить DNS для {hostname}.") # Определяем страну по IP country = await self._get_country(ip) if not country: return self._skip_result(message=f"Не удалось определить страну для IP {ip}.") # Проверяем, Россия ли это if country.lower() == "ru" or "Россия" in country or "russia" in country.lower(): return self._pass_result( message=f"Сервер находится в России (IP: {ip}, Страна: {country})", details={ "ip": ip, "country": country, }, ) # Сервер за рубежом — критическое нарушение return self._fail_result( message=( f"Сервер находится ЗА ПРЕДЕЛАМИ РОССИИ ({country}, IP: {ip}). " f"Это нарушение ст. 18 152-ФЗ: базы данных граждан РФ должны быть в РФ." ), details={ "ip": ip, "country": country, }, ) except Exception as e: return self._error_result(message=f"Ошибка при проверке геолокации: {str(e)}") async def _get_country(self, ip: str) -> str | None: """Получает страну по IP через публичный API (асинхронно).""" try: import aiohttp apis = [ f"https://ipapi.co/{ip}/json/", f"https://ip-api.com/json/{ip}?fields=country", ] async with aiohttp.ClientSession() as session: for api_url in apis: try: async with session.get( api_url, headers={"User-Agent": "Mozilla/5.0"}, timeout=aiohttp.ClientTimeout(total=5), ) as response: if response.status != 200: continue data = await response.json() # ipapi.co формат if "country_name" in data: return data["country_name"] # ip-api.com формат if "country" in data: return data["country"] except Exception: continue return None except Exception: return None