Версия 0.0.2

This commit is contained in:
commit 7b9c998224
530 changed files with 424916 additions and 0 deletions

0
SRV/services/__init__.py Normal file
View file

View file

@ -0,0 +1,59 @@
from scapy.layers.l2 import ARP, Ether
from scapy.sendrecv import srp
import netifaces
from netaddr import IPNetwork
class NetworkScanner:
"""Иницилазицая сетевого сканера"""
def __init__(self, interface=None):
"""Инициализация параметров интерфейсов """
self.interface = interface or netifaces.gateways()['default'][netifaces.AF_INET][1]
self.ip_info = netifaces.ifaddresses(self.interface).get(netifaces.AF_INET, [])
self.networks = [IPNetwork(f"{ip['addr']}/{ip['netmask']}") for ip in self.ip_info]
self.local_mac = netifaces.ifaddresses(self.interface)[netifaces.AF_LINK][0]['addr']
def _create_arp_packet(self, target_ip):
"""Создание пакета ARP для отправки"""
arp = ARP(pdst=target_ip)
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
return ether/arp
def scan_network(self, attempts=3, timeout=2):
"""Подготовка к сканированию сети"""
all_clients = []
for network in self.networks:
target_ip = f"{network.network}/{network.prefixlen}"
packet = self._create_arp_packet(target_ip)
clients = []
for _ in range(attempts):
result = srp(packet, timeout=timeout, iface=self.interface, verbose=0)[0]
for _, received in result:
client = {'ip': received.psrc, 'mac': received.hwsrc}
if client not in clients: # Избегаем дублирования клиентов
clients.append(client)
all_clients.extend(clients)
# Добавляет все локальные IP-адреса интерфейса как клиенты
for ip_info in self.ip_info:
local_client = {'ip': ip_info['addr'], 'mac': self.local_mac}
if local_client not in all_clients:
all_clients.append(local_client)
# Удаление дубликатов
unique_clients = {tuple(client.items()) for client in all_clients}
return [dict(client) for client in unique_clients]
def print_clients(self, clients):
"""Лист адресов клиентов"""
print("Доступные устройства в сети:")
print("IP" + " "*18+"MAC")
for client in sorted(clients, key=lambda x: x['ip']):
print(f"{client['ip']:<16} {client['mac']}")
if __name__ == "__main__":
scanner = NetworkScanner()
clients = scanner.scan_network()
scanner.print_clients(clients)

View file

@ -0,0 +1,129 @@
# services/auth_service.py
from jose import JWTError, jwt
from datetime import datetime, timedelta
from fastapi import HTTPException, Depends, Request
from fastapi.security import OAuth2PasswordBearer
from models.auth import UserCreate, UserLogin, UserResponse, Token
from storage.json_storage import JsonStorage
from storage.session_storage import SessionStorage
from typing import Optional
import configparser
import os
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
config = configparser.ConfigParser()
config_path = "/etc/gonets/config.ini"
if not os.path.exists(config_path):
raise RuntimeError(f"Конфигурационный файл {config_path} не найден")
config.read(config_path)
SECRET_KEY = config["JWT"]["secret_key"]
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
class AuthService:
"""Сервис для регистрации, авторизации и управления сессиями"""
def __init__(self):
self.storage = JsonStorage()
self.session_storage = SessionStorage()
def create_access_token(self, data: dict) -> str:
"""Создаёт токен"""
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def verify_token(self, token: str) -> Optional[dict]:
"""Проверяет токен"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
return None
return {"username": username}
except JWTError:
return None
def register(self, user: UserCreate) -> UserResponse:
"""Регистрирует нового пользователя"""
try:
db_user = self.storage.create_user(user)
return db_user
except ValueError as e:
logger.error(f"Ошибка при регистрации пользователя {user.username}: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Ошибка при регистрации пользователя {user.username}: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Ошибка регистрации")
def login(self, user: UserLogin, client_id: str) -> dict:
"""Авторизует пользователя, создаёт сессию и возвращает токен"""
try:
db_user = self.storage.get_user_by_username(user.username)
if not db_user:
logger.error(f"Пользователь {user.username} не найден")
raise HTTPException(status_code=401, detail="Неверный логин или пароль")
if not self.storage.verify_password(user.password, db_user["password_hash"]):
logger.error(f"Неверный пароль для пользователя {user.username}")
raise HTTPException(status_code=401, detail="Неверный логин или пароль")
access_token = self.create_access_token(data={"sub": user.username})
session_id = self.session_storage.create_session(user.username, access_token, client_id)
logger.info(f"Пользователь {user.username} успешно авторизован")
return {
"access_token": access_token,
"token_type": "bearer",
"session_id": session_id,
"user": UserResponse(id=db_user["id"], username=db_user["username"], is_admin=db_user["is_admin"])
}
except Exception as e:
logger.error(f"Ошибка при авторизации пользователя {user.username}: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Ошибка авторизации")
def get_current_user(self, token: str = Depends(oauth2_scheme)) -> UserResponse:
"""Получает текущего пользователя по токену"""
credentials = self.verify_token(token)
if not credentials:
logger.error("Неверный или истёкший токен")
raise HTTPException(status_code=401, detail="Неверный токен")
username = credentials["username"]
db_user = self.storage.get_user_by_username(username)
if not db_user:
logger.error(f"Пользователь {username} не найден")
raise HTTPException(status_code=401, detail="Пользователь не найден")
return UserResponse(id=db_user["id"], username=db_user["username"], is_admin=db_user["is_admin"])
def get_user_by_session(self, client_id: str) -> dict:
"""Получает пользователя и токен по client_id"""
session = self.session_storage.get_latest_session(client_id)
if not session:
logger.error(f"Сессия для client_id {client_id} не найдена или истекла")
raise HTTPException(status_code=401, detail="Сессия недействительна")
username = session["username"]
token = session["token"]
db_user = self.storage.get_user_by_username(username)
if not db_user:
logger.error(f"Пользователь {username} не найден")
raise HTTPException(status_code=401, detail="Пользователь не найден")
return {
"user": UserResponse(id=db_user["id"], username=db_user["username"], is_admin=db_user["is_admin"]),
"access_token": token
}
def logout(self, client_id: str):
"""Выполняет выход из системы, удаляя последнюю сессию для client_id"""
try:
session = self.session_storage.get_latest_session(client_id)
if session:
self.session_storage.delete_session(session["session_id"])
logger.info(f"Пользователь успешно вышел из системы для client_id {client_id}")
return {"message": "Успешный выход из системы"}
except Exception as e:
logger.error(f"Ошибка при выходе из системы: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Ошибка при выходе из системы")

View file

@ -0,0 +1,143 @@
from re import DEBUG
from typing import Dict, List, Optional
from pydbus import SystemBus
DEBUG = True
class FirewalldService:
def __init__(self):
self.bus = SystemBus()
self.proxy = self.bus.get(
"org.fedoraproject.FirewallD1", "/org/fedoraproject/FirewallD1"
)
def get_zones(self) -> List[str]:
"""Get list of all zones."""
try:
return self.proxy.getZones()
except Exception as e:
raise Exception(f"Failed to get zones: {str(e)}")
def get_default_zone(self) -> str:
"""Get the default zone."""
try:
return self.proxy.getDefaultZone()
except Exception as e:
raise Exception(f"Failed to get default zone: {str(e)}")
def set_default_zone(self, zone: str) -> None:
"""Set the default zone."""
try:
self.proxy.setDefaultZone(zone)
except Exception as e:
raise Exception(f"Failed to set default zone to {zone}: {str(e)}")
def add_service(self, zone: str, service: str, timeout: Optional[int] = 0) -> None:
"""Add a service to a zone."""
try:
self.proxy.addService(zone, service, timeout)
except Exception as e:
raise Exception(f"Failed to add service {service} to zone {zone}: {str(e)}")
def remove_service(self, zone: str, service: str) -> None:
"""Remove a service from a zone."""
try:
self.proxy.removeService(zone, service)
except Exception as e:
raise Exception(
f"Failed to remove service {service} from zone {zone}: {str(e)}"
)
def get_services(self, zone: str) -> List[str]:
"""Get list of services in a zone."""
try:
return self.proxy.getServices(zone)
except Exception as e:
raise Exception(f"Failed to get services for zone {zone}: {str(e)}")
def add_port(
self, zone: str, port: int, protocol: str, timeout: Optional[int] = 0
) -> None:
"""Add a port to a zone."""
try:
if DEBUG:
print(f"Adding port {port}/{protocol} to zone {zone}")
print(f"Timeout: {timeout}")
# Get the zone interface
zone_proxy = self.bus.get(
"org.fedoraproject.FirewallD1",
"/org/fedoraproject/FirewallD1"
)
zone_iface = zone_proxy['org.fedoraproject.FirewallD1.zone']
# Convert port to string
port_str = str(port)
# Convert timeout to integer
timeout_int = int(timeout) if timeout is not None else 0
# Call addPort with the correct signature (sssi)
zone_iface.addPort(zone, port_str, protocol, timeout_int)
except Exception as e:
raise Exception(
f"Failed to add port {port}/{protocol} to zone {zone}: {str(e)}"
)
def remove_port(self, zone: str, port: int, protocol: str) -> None:
"""Remove a port from a zone."""
try:
port_str = f"{port}/{protocol}"
self.proxy.removePort(zone, port_str)
except Exception as e:
raise Exception(
f"Failed to remove port {port}/{protocol} from zone {zone}: {str(e)}"
)
def get_ports(self, zone: str) -> List[str]:
"""Get list of ports in a zone."""
try:
ports = self.proxy.getPorts(zone)
# Convert port list items to strings in format "port/protocol"
return [f"{port[0]}/{port[1]}" for port in ports]
except Exception as e:
raise Exception(f"Failed to get ports for zone {zone}: {str(e)}")
def add_source(self, zone: str, source: str) -> None:
"""Add a source to a zone."""
try:
self.proxy.addSource(zone, source)
except Exception as e:
raise Exception(f"Failed to add source {source} to zone {zone}: {str(e)}")
def remove_source(self, zone: str, source: str) -> None:
"""Remove a source from a zone."""
try:
self.proxy.removeSource(zone, source)
except Exception as e:
raise Exception(
f"Failed to remove source {source} from zone {zone}: {str(e)}"
)
def get_sources(self, zone: str) -> List[str]:
"""Get list of sources in a zone."""
try:
return self.proxy.getSources(zone)
except Exception as e:
raise Exception(f"Failed to get sources for zone {zone}: {str(e)}")
def reload(self) -> None:
"""Reload firewall rules."""
try:
self.proxy.reload()
except Exception as e:
raise Exception(f"Failed to reload firewall rules: {str(e)}")
def get_zone_settings(self, zone: str) -> Dict:
"""Get all settings for a zone."""
try:
return {
"services": self.get_services(zone),
"ports": self.get_ports(zone),
"sources": self.get_sources(zone),
}
except Exception as e:
raise Exception(f"Failed to get zone settings for {zone}: {str(e)}")

112
SRV/services/ip_service.py Normal file
View file

@ -0,0 +1,112 @@
from sdbus_async.networkmanager import NetworkManager, NetworkManagerSettings, NetworkConnectionSettings, NetworkDeviceGeneric, ActiveConnection
import asyncio
from sdbus_async.networkmanager.settings import ConnectionProfile
from typing import Dict, Any
import sdbus
from services.arp_service import NetworkScanner
class IPService:
"""
Сервис проверки доступности IP-адреса и применения настроек сети
"""
def __init__(self):
self.arp_scanner = NetworkScanner()
self.bus = sdbus.sd_bus_open_system()
sdbus.set_default_bus(self.bus)
def _reset_bus(self):
""" Перезапускает соединение с системной шиной D-Bus """
new_bus = sdbus.sd_bus_open_system()
sdbus.set_default_bus(new_bus)
if hasattr(self, 'bus') and self.bus is not None:
self.bus = None
self.bus = new_bus
async def check_ip_config(self, args: Dict[str, Any]) -> Dict[str, Any]:
""" Проверяет доступность IP-адреса перед его применением """
self._reset_bus()
clients = self.arp_scanner.scan_network()
new_ip = args["ipv4_updates"]["ipv4.address-data"][0]["address"]
if any(client['ip'] == new_ip for client in clients):
return {"available": False, "message": f"IP address {new_ip} уже занят."}
return {"available": True, "message": "Network profile settings are available for use."}
async def apply_ip_config(self, args: Dict[str, Any]) -> None:
""" Применяет проверенные настройки IP """
self._reset_bus()
nm = NetworkManager(self.bus)
settings = NetworkManagerSettings(self.bus)
connection_paths = await settings.get_connections_by_id(args["connection_id"])
if not connection_paths:
raise ValueError(f"Подключение {args['connection_id']} не найдено")
connection = NetworkConnectionSettings(connection_paths[0], self.bus)
profile = await connection.get_profile()
settings_dict = profile.to_settings_dict()
# Обновляем настройки IPv4
for key_path, value in args["ipv4_updates"].items():
section, *props = key_path.split('.')
current = settings_dict[section]
for prop in props[:-1]:
current = current.setdefault(prop, {})
current[props[-1]] = value
new_profile = ConnectionProfile.from_settings_dict(settings_dict)
await connection.update(new_profile.to_dbus())
print(f"Обновлены настройки для {args['connection_id']}:")
# Проверяем interface-name
interface_name = settings_dict['connection'].get('interface-name')
if not interface_name:
# Ищем устройство, связанное с connection_id через активное соединение
devices = await nm.get_devices()
for device_path in devices:
device = NetworkDeviceGeneric(device_path, self.bus)
active_connection = await device.active_connection
if active_connection != '/':
active_conn = ActiveConnection(active_connection, self.bus)
conn_path = await active_conn.connection # Путь к настройкам соединения
if conn_path != '/':
conn_settings = NetworkConnectionSettings(conn_path, self.bus)
active_profile = await conn_settings.get_profile()
if active_profile.to_settings_dict()['connection']['id'] == args["connection_id"]:
interface_name = await device.interface
settings_dict['connection']['interface-name'] = interface_name
new_profile = ConnectionProfile.from_settings_dict(settings_dict)
await connection.update(new_profile.to_dbus())
print(f"Добавлен interface-name: {interface_name} для {args['connection_id']}")
break
if not interface_name:
# Если не нашли активное соединение, пробуем получить интерфейс из настроек устройства
devices = await nm.get_devices()
for device_path in devices:
device = NetworkDeviceGeneric(device_path, self.bus)
applied_connection = await device.get_applied_connection()
connection_settings = applied_connection[0]
conn_id = connection_settings.get('connection', {}).get('id', ('s', ''))[1]
if conn_id == args["connection_id"]:
interface_name = await device.interface
settings_dict['connection']['interface-name'] = interface_name
new_profile = ConnectionProfile.from_settings_dict(settings_dict)
await connection.update(new_profile.to_dbus())
print(f"Добавлен interface-name: {interface_name} для {args['connection_id']}")
break
if not interface_name:
raise ValueError(f"Не удалось определить interface-name для {args['connection_id']}")
# Переподключаем устройство
devices = await nm.get_devices()
for device_path in devices:
device = NetworkDeviceGeneric(device_path, self.bus)
if await device.interface == interface_name:
await device.disconnect()
print(f"Отключён от {interface_name}")
await nm.activate_connection(connection=connection_paths[0], device=device_path)
print(f"Переподключён к {args['connection_id']}")
return
raise RuntimeError(f"Устройство для интерфейса {interface_name} не найдено")

View file

@ -0,0 +1,200 @@
from typing import List, Dict, Tuple, Union, Optional
from sdbus_async.networkmanager import (
NetworkManager,
NetworkDeviceGeneric,
IPv4Config,
IPv6Config,
NetworkManagerSettings,
NetworkDeviceWireless,
AccessPoint,
DeviceType
)
from sdbus import sd_bus_open_system
from models.network import (
NetworkInterface,
IPv4Route,
IPv6Route,
IPConfig,
IndividualIPConfig,
NetworkInterfacesContainer,
WiFiDevice
)
import subprocess
class NetworkService:
"""Сервис для взаимодействия с NetworkManager через D-Bus"""
def __init__(self):
self.system_bus = sd_bus_open_system()
self.nm = NetworkManager(self.system_bus)
self.nm_settings = NetworkManagerSettings(self.system_bus)
async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str:
"""Получает метод IP для устройства"""
try:
config = await device.get_applied_connection()
settings = config[0].get(ip_type, {})
return str(settings.get("method", ('s', 'unknown'))[1])
except Exception:
return 'unknown'
async def get_connection_name(self, device: NetworkDeviceGeneric) -> str:
"""Извлекает имя соединения (GENERAL.CONNECTION) для устройства"""
try:
applied_connection = await device.get_applied_connection()
connection_settings = applied_connection[0]
connection_info = connection_settings.get('connection', {})
return str(connection_info.get('id', ('s', 'unknown'))[1])
except Exception as e:
# print(f"Ошибка при получении соединения для устройства {await device.interface}: {e}")
# срёт ошибки дл отключенных интерфейсов
# добавить потом условия для отключенных интерфейсов
return 'unknown'
async def get_ip_config(self, device: NetworkDeviceGeneric, ip_version: str) -> List[IndividualIPConfig]:
"""Получает конфигурацию IP для устройства"""
configs = []
device_ip = await getattr(device, f'ip{ip_version}_config')
if device_ip != '/':
ip_config = globals()[f'IPv{ip_version}Config'](device_ip, self.system_bus)
address_data = await ip_config.address_data
for address in address_data:
config = IndividualIPConfig(
address=str(address['address'][1]),
routes=[
globals()[f'IPv{ip_version}Route'](
destination=route.get('dest', ('s', '0.0.0.0' if ip_version == '4' else '::'))[1],
prefix=route.get('prefix', ('u', 0))[1],
next_hop=route.get('next-hop', ('s', '0.0.0.0' if ip_version == '4' else '::'))[1],
metric=route.get('metric', ('u', 0))[1]
) for route in await ip_config.route_data
],
method=await self.get_ip_method(device, f'ipv{ip_version}'),
gateway=str(await ip_config.gateway),
dns=[str(ns['address'][1]) if ip_version == '4' else str(ns) for ns in await getattr(ip_config, 'nameserver_data' if ip_version == '4' else 'nameservers')]
)
configs.append(config)
return configs
async def get_wifi_info(self, wifi_device: NetworkDeviceWireless) -> Dict[str, Optional[Union[str, int]]]:
"""Получает информацию о Wi-Fi устройстве"""
active_ap = await wifi_device.active_access_point
wifi_info = {
'ssid': None,
'bssid': None,
'bitrate': await wifi_device.bitrate,
'strength': None,
'frequency': None,
'mode': None
}
if active_ap != '/':
ap = AccessPoint(active_ap, self.system_bus)
wifi_info.update({
'ssid': await ap.ssid,
'bssid': await ap.hw_address,
'strength': await ap.strength,
'frequency': await ap.frequency,
'mode': await ap.mode
})
return wifi_info
async def get_network_interfaces(self, initial_positions: Dict[str, Tuple[int, int]] = None) -> List[Union[NetworkInterface, WiFiDevice]]:
"""Получает список сетевых интерфейсов"""
devices_paths = await self.nm.get_devices()
network_interfaces = []
initial_positions = initial_positions or {}
for device_path in devices_paths:
generic_device = NetworkDeviceGeneric(device_path, self.system_bus)
interface_name = str(await generic_device.interface)
device_type = DeviceType(await generic_device.device_type).name
if device_type == 'LOOPBACK':
continue
device_state = str(await generic_device.state)
if device_state == '10':
continue
common_data = {
'name': interface_name,
'type': device_type,
'device_state': device_state,
'active_connection': await self.get_connection_name(generic_device),
'mac_address': str(await generic_device.hw_address),
'device_mtu': str(await generic_device.mtu),
'ipv4': IPConfig(configurations=await self.get_ip_config(generic_device, '4')),
'ipv6': IPConfig(configurations=await self.get_ip_config(generic_device, '6')),
'x': initial_positions.get(interface_name, (100, 100))[0],
'y': initial_positions.get(interface_name, (100, 100))[1],
}
if device_type == "WIFI":
wifi_device = NetworkDeviceWireless(device_path, self.system_bus)
wifi_info = await self.get_wifi_info(wifi_device)
network_interfaces.append(WiFiDevice(**common_data, **wifi_info))
else:
network_interfaces.append(NetworkInterface(**common_data))
return network_interfaces
async def get_hostname(self) -> Dict[str, str]:
"""Возвращает имя хоста из команды hostname в формате JSON"""
try:
result = subprocess.run(['hostname'], capture_output=True, text=True, check=True)
hostname = result.stdout.strip()
return {"hostname": hostname}
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to execute hostname: {e}")
except Exception as e:
raise RuntimeError(f"Error processing hostname output: {e}")
async def get_interfaces_by_state(self, state: str, initial_positions: Dict[str, Tuple[int, int]] = None) -> List[NetworkInterface]:
"""
Получает список интерфейсов по состоянию
"""
all_interfaces = await self.get_network_interfaces(initial_positions)
return [interface for interface in all_interfaces if interface.device_state == state]
async def get_interfaces_container(self, state: Optional[str] = None, initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) -> NetworkInterfacesContainer:
""" Получает объект, содержащий список интерфейсов и координаты контейнера """
if state:
interfaces = await self.get_interfaces_by_state(state, initial_positions)
else:
interfaces = await self.get_network_interfaces(initial_positions)
return NetworkInterfacesContainer(
interfaces=interfaces,
container_x=container_x,
container_y=container_y
)
async def get_network_interfaces_container(self, initial_positions: Optional[Dict[str, Tuple[int, int]]] = None, ) -> NetworkInterfacesContainer:
""" Получает контейнер с сетевыми интерфейсами и координатами контейнера """
interfaces = await self.get_network_interfaces(initial_positions)
return NetworkInterfacesContainer(
interfaces=interfaces,
container_x=0,
container_y=0
)
async def get_activated_interfaces(self, initial_positions: Dict[str, Tuple[int, int]] = None) -> List[NetworkInterface]:
""" Получает список активных сетевых интерфейсов """
return await self.get_interfaces_by_state('100', initial_positions)
async def get_deactivated_interfaces(self, initial_positions: Dict[str, Tuple[int, int]] = None) -> List[NetworkInterface]:
""" Получает список неактивных сетевых интерфейсов """
all_interfaces = await self.get_network_interfaces(initial_positions)
return [interface for interface in all_interfaces if interface.device_state != '100']
async def get_activated_interfaces_container(self, initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) -> NetworkInterfacesContainer:
""" Получает объект, содержащий список активных интерфейсов и координаты контейнера """
return await self.get_interfaces_container('100', initial_positions, container_x, container_y)
async def get_deactivated_interfaces_container(self, initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) -> NetworkInterfacesContainer:
""" Получает объект, содержащий список неактивных интерфейсов и координаты контейнера """
interfaces = await self.get_deactivated_interfaces(initial_positions)
return NetworkInterfacesContainer(
interfaces=interfaces,
container_x=container_x,
container_y=container_y
)

393
SRV/services/ufw_service.py Normal file
View file

@ -0,0 +1,393 @@
import ipaddress
import subprocess
from typing import List, Union
from models.ufw import Direction, Protocol, UFWRule, UFWRulesResponse, UFWStatus
DEBUG = True
class UFWService:
"""
Сервис для взаимодействия с утилитой UFW (Uncomplicated Firewall) в Linux.
Позволяет получить список правил UFW и управлять ими.
"""
def _run_command(self, command: str) -> str:
"""
Выполняет команду в системе и возвращает ее вывод.
Args:
command (str): Команда для выполнения в системе.
Returns:
str: Вывод команды.
"""
try:
if DEBUG:
print(f"Running command: {command}")
# Используем shell=True для корректной обработки аргументов с пробелами
result = subprocess.run(
f"sudo {command}",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if DEBUG:
print(f"Command output: {result.stdout}")
print(f"Command error: {result.stderr}")
if result.returncode != 0:
raise subprocess.CalledProcessError(
result.returncode, command, result.stdout, result.stderr
)
return result.stdout
except subprocess.CalledProcessError as e:
if DEBUG:
print(f"Command failed with return code {e.returncode}")
print(f"Error output: {e.stderr}")
raise Exception(f"Command failed: {e.stderr}")
def _get_port_range_args(self, port: Union[int, str]) -> List[str]:
"""
Преобразует порт или диапазон портов в аргументы команды UFW.
Args:
port: Порт (число) или диапазон портов (строка вида "3000:3999")
Returns:
List[str]: Список аргументов для UFW
"""
if isinstance(port, int):
return [str(port)]
elif isinstance(port, str) and ":" in port:
# Диапазон портов уже в нужном формате
return [port]
else:
raise ValueError(f"Некорректный формат порта: {port}")
def get_ufw_rules(self) -> UFWRulesResponse:
"""
Получает список правил UFW.
Returns:
UFWRulesResponse: Объект, содержащий статус UFW и список правил
"""
# Check UFW status
status_result = self._run_command("ufw status numbered")
lines = status_result.splitlines()
if not lines:
return UFWRulesResponse(enabled=False, rules=[])
first_line = lines[0]
is_enabled = "Status: active" in first_line
if not is_enabled:
return UFWRulesResponse(enabled=False, rules=[])
rules = []
# Skip header lines
for line in lines[4:]: # Skip Status and header lines
if not line.strip() or not line.startswith("["):
continue
try:
rule = UFWRule.from_ufw_output(line)
rules.append(rule)
except Exception as e:
if DEBUG:
print(f"Ошибка парсинга правила: {line}, ошибка: {str(e)}")
continue
return UFWRulesResponse(enabled=is_enabled, rules=rules)
def get_ufw_status(self) -> UFWStatus:
"""
Проверяет статус UFW.
Returns:
UFWStatus: Объект с информацией о статусе UFW
"""
try:
status_result = self._run_command("ufw status")
first_line = (
status_result.splitlines()[0] if status_result.splitlines() else ""
)
is_enabled = "Status: active" in first_line
message = "UFW активен" if is_enabled else "UFW неактивен"
if DEBUG:
print(f"Результат статуса: {status_result}")
print(f"Включен: {is_enabled}")
return UFWStatus(enabled=is_enabled, message=message)
except Exception as e:
if DEBUG:
print(f"Ошибка при проверке статуса UFW: {e}")
return UFWStatus(
enabled=False,
message=f"Ошибка при проверке статуса: {str(e)}",
)
def enable_ufw(self) -> bool:
"""
Включает UFW.
Returns:
bool: True если успешно включен, False в противном случае
"""
try:
self._run_command("ufw enable")
return True
except Exception as e:
if DEBUG:
print(f"Ошибка при включении UFW: {e}")
return False
def disable_ufw(self) -> bool:
"""
Выключает UFW.
Returns:
bool: True если успешно выключен, False в противном случае
"""
try:
self._run_command("ufw disable")
return True
except Exception as e:
if DEBUG:
print(f"Ошибка при выключении UFW: {e}")
return False
def add_rules_base(self, rules: List[UFWRule], direction: str = "") -> bool:
"""
Базовый метод для добавления правил UFW.
Args:
rules: Список правил для добавления
direction: Направление трафика ("in" или "out")
Returns:
bool: True если все правила были успешно добавлены, False в противном случае
"""
try:
for rule in rules:
# Устанавливаем направление правила
rule.direction = Direction.IN if direction == "in" else Direction.OUT
# Формируем базовую команду
command = f"ufw {rule.action.value}"
# Добавляем направление
command += f" {rule.direction.value}"
# Добавляем from IP если он указан
if rule.from_ip and rule.from_ip != "any":
command += f" from {rule.from_ip}"
else:
command += " from any"
# Добавляем to IP если он указан
if rule.to_ip and rule.to_ip != "any":
command += f" to {rule.to_ip}"
else:
command += " to any"
# Добавляем порт если он указан
if rule.port:
command += f" port {rule.port}"
# Добавляем протокол
if rule.protocol != Protocol.BOTH:
command += f" proto {rule.protocol.value}"
# Добавляем комментарий если он есть
if rule.comment:
command += f" comment '{rule.comment}'"
self._run_command(command)
return True
except Exception as e:
if DEBUG:
print(f"Error adding UFW rules: {str(e)}")
return False
def add_ufw_in_rules(self, rules: List[UFWRule]) -> bool:
"""
Добавляет входящие (in) правила UFW.
Args:
rules (List[UFWRule]): Список правил для добавления
Returns:
bool: True если все правила были успешно добавлены, False в противном случае
"""
return self.add_rules_base(rules, "in")
def add_ufw_out_rules(self, rules: List[UFWRule]) -> bool:
"""
Добавляет исходящие (out) правила UFW.
Args:
rules (List[UFWRule]): Список правил для добавления
Returns:
bool: True если все правила были успешно добавлены, False в противном случае
"""
return self.add_rules_base(rules, "out")
def add_rules(self, rules: List[UFWRule]) -> dict:
"""
Добавляет правила UFW.
Args:
rules: Список правил для добавления
Returns:
dict: Результат добавления правил
"""
results = []
for rule in rules:
try:
# Определяем версию IP
from_ip = ipaddress.ip_network(rule.from_ip, strict=False)
is_ipv6 = isinstance(from_ip, ipaddress.IPv6Network)
# Получаем список протоколов
protocols = rule.protocol.protocols
print(f"Обработка правила для протоколов: {protocols}")
for proto in protocols:
# Формируем базовую команду
command = f"ufw {rule.action.value}"
# Добавляем IP адреса
command += f" from {rule.from_ip}"
if rule.to_ip:
command += f" to {rule.to_ip}"
# Добавляем порт и протокол
port_args = self._get_port_range_args(rule.port)
command += f" port {' '.join(port_args)} proto {proto}"
# Добавляем флаг IPv6 если нужно
if is_ipv6:
command += " v6"
# Добавляем комментарий если есть
if rule.comment:
command += f" comment '{rule.comment}'"
print(f"Подготовлена команда: {command}")
# Выполняем команду
try:
output = self._run_command(command)
results.append({"success": True, "message": output})
except Exception as e:
error_msg = f"Ошибка выполнения команды: {str(e)}"
print(error_msg)
results.append({"success": False, "message": error_msg})
except Exception as e:
error_msg = f"Ошибка при добавлении правила: {str(e)}"
print(error_msg)
results.append({"success": False, "message": error_msg})
# Выводим результаты
print("\nРезультаты добавления правил:")
for result in results:
status = "Успешно" if result["success"] else "Ошибка"
print(f" - {status}: {result['message']}")
return {"success": True, "results": results}
def delete_rules(self, rule_numbers: List[int]) -> dict:
"""
Удаляет правила UFW по их номерам.
Args:
rule_numbers (List[int]): Список номеров правил для удаления
Returns:
dict: Результат операции удаления
"""
try:
# Проверяем существование правил
status_result = self._run_command("ufw status numbered")
lines = status_result.splitlines()
# Пропускаем заголовок (первые 4 строки)
rules = []
for line in lines[4:]: # Skip header lines
if not line.strip() or not line.startswith("["):
continue
try:
# Извлекаем номер правила из строки [N]
rule_num = int(line[1 : line.index("]")])
rules.append(rule_num)
except (ValueError, IndexError):
continue
if DEBUG:
print(f"Найдены правила: {rules}")
results = []
# Сортируем номера в обратном порядке, чтобы удаление происходило с конца
# Это предотвратит смещение номеров при удалении
for rule_number in sorted(rule_numbers, reverse=True):
try:
# Проверяем существует ли правило с таким номером
if rule_number not in rules:
results.append(
{
"number": rule_number,
"success": False,
"error": f"Правило с номером {rule_number} не найдено",
}
)
continue
# Удаляем правило
self._run_command(f"ufw --force delete {rule_number}")
results.append(
{
"number": rule_number,
"success": True,
"message": f"Правило {rule_number} успешно удалено",
}
)
except Exception as e:
if DEBUG:
print(f"Ошибка при удалении правила {rule_number}: {e}")
results.append(
{"number": rule_number, "success": False, "error": str(e)}
)
# Подсчитываем статистику
total = len(rule_numbers)
successful = len([r for r in results if r["success"]])
return {
"success": True,
"total": total,
"successful": successful,
"results": results,
}
except Exception as e:
if DEBUG:
print(f"Ошибка при удалении правил: {e}")
return {"success": False, "error": str(e), "results": []}