Files
152-fz-scanner/scanner/checks/server_location.py
T

120 lines
4.8 KiB
Python

"""
Проверка геолокации сервера.
Согласно 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 urllib.request
import json
# Пробуем несколько бесплатных API
apis = [
f"https://ipapi.co/{ip}/json/",
f"https://ip-api.com/json/{ip}?fields=country",
f"https://api.ipgeolocation.io/ipgeo?ip={ip}",
]
for api_url in apis:
try:
req = urllib.request.Request(
api_url,
headers={"User-Agent": "Mozilla/5.0"}
)
with urllib.request.urlopen(req, timeout=5) as response:
data = json.loads(response.read().decode())
# ipapi.co формат
if "country_name" in data:
return data["country_name"]
# ip-api.com формат
if "country" in data:
return data["country"]
# ipgeolocation формат
if "country_name" in data:
return data["country_name"]
except Exception:
continue
return None
except Exception:
return None