120 lines
4.8 KiB
Python
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
|