Инициализация в новом репозитории

This commit is contained in:
commit eb1f18f53e
374 changed files with 226678 additions and 0 deletions

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

23
SRV/models/auth.py Normal file
View file

@ -0,0 +1,23 @@
# models.py
from pydantic import BaseModel
from typing import Optional
class UserCreate(BaseModel):
username: str
password: str
is_admin: bool = False
class UserLogin(BaseModel):
username: str
password: str
class UserResponse(BaseModel):
id: int
username: str
is_admin: bool
class Token(BaseModel):
access_token: str
token_type: str
session_id: Optional[str] = None
user: Optional[UserResponse] = None

115
SRV/models/firewalld.py Normal file
View file

@ -0,0 +1,115 @@
from enum import Enum
from typing import Annotated, List, Optional
from pydantic import BaseModel, Field, field_validator
from pydantic.types import StringConstraints
class FirewallTarget(str, Enum):
"""Firewall zone target types"""
ACCEPT = "ACCEPT"
REJECT = "REJECT"
DROP = "DROP"
DEFAULT = "default"
class Protocol(str, Enum):
"""Network protocols supported by firewalld"""
TCP = "tcp"
UDP = "udp"
class FirewallZone(BaseModel):
"""Represents a firewalld zone configuration"""
name: Annotated[str, StringConstraints(min_length=1)] = Field(
..., description="Name of the zone"
)
target: Optional[FirewallTarget] = Field(
default=FirewallTarget.DEFAULT, description="Zone target type"
)
services: List[str] = Field(
default_factory=list, description="List of enabled services"
)
ports: List[str] = Field(default_factory=list, description="List of enabled ports")
sources: List[str] = Field(
default_factory=list, description="List of source addresses"
)
interfaces: List[str] = Field(
default_factory=list, description="List of network interfaces"
)
class FirewallService(BaseModel):
"""Represents a firewalld service configuration"""
name: Annotated[str, StringConstraints(min_length=1)] = Field(
..., description="Name of the service"
)
ports: List[str] = Field(
default_factory=list, description="List of ports used by the service"
)
protocols: List[str] = Field(
default_factory=list, description="List of protocols used by the service"
)
modules: List[str] = Field(
default_factory=list,
description="List of kernel modules required by the service",
)
timeout: Optional[int] = Field(
default=0, ge=0, description="Timeout in seconds (0 for permanent)"
)
class FirewallPort(BaseModel):
"""Represents a firewalld port configuration"""
port: int = Field(..., ge=1, le=65535, description="Port number (1-65535)")
protocol: Protocol = Field(..., description="Network protocol (tcp/udp)")
timeout: Optional[int] = Field(
default=0, ge=0, description="Timeout in seconds (0 for permanent)"
)
def __str__(self) -> str:
return f"{self.port}/{self.protocol.value}"
class FirewallSource(BaseModel):
"""Represents a firewalld source configuration"""
address: Annotated[
str,
StringConstraints(pattern=r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?:/[0-9]{1,2})?$"),
] = Field(..., description="IP address or network in CIDR notation")
zone: Optional[str] = Field(default=None, description="Zone name for the source")
@field_validator("address")
def validate_ip_address(cls, v):
# Validate IP address octets
ip = v.split("/")[0]
octets = ip.split(".")
if not all(0 <= int(octet) <= 255 for octet in octets):
raise ValueError("Invalid IP address octets")
# Validate CIDR if present
if "/" in v:
cidr = int(v.split("/")[1])
if not 0 <= cidr <= 32:
raise ValueError("CIDR must be between 0 and 32")
return v
class FirewallRule(BaseModel):
"""Represents a complete firewalld rule"""
zone: str = Field(..., description="Zone name")
source: Optional[FirewallSource] = Field(
default=None, description="Source configuration"
)
port: Optional[FirewallPort] = Field(default=None, description="Port configuration")
service: Optional[str] = Field(default=None, description="Service name")
timeout: Optional[int] = Field(
default=0, ge=0, description="Rule timeout in seconds"
)

70
SRV/models/network.py Normal file
View file

@ -0,0 +1,70 @@
from typing import List, Optional, Union
from pydantic import BaseModel
# Класс для маршрутов IPv4
class IPv4Route(BaseModel):
destination: str
prefix: int
next_hop: str
metric: int
# Класс для маршрутов IPv6
class IPv6Route(BaseModel):
destination: str
prefix: int
next_hop: str
metric: int
# Класс для индивидуальной конфигурации IP
class IndividualIPConfig(BaseModel):
address: str
routes: List[Union[IPv4Route, IPv6Route]]
method: Optional[str]
gateway: str
dns: List[str]
# Класс для конфигурации IP
class IPConfig(BaseModel):
configurations: List[IndividualIPConfig]
# Класс для сетевого интерфейса
class NetworkInterface(BaseModel):
name: str
type: str
mac_address: str
device_state: str
active_connection: str
device_mtu: str
ipv4: IPConfig
ipv6: IPConfig
container_x: int = 0
container_y: int = 0
# Класс для WiFi устройства
class WiFiDevice(NetworkInterface):
ssid: Optional[str] = None
bssid: Optional[str] = None
bitrate: Optional[int] = None
strength: Optional[int] = None
frequency: Optional[float] = None
mode: Union[str, int] = None
class NetworkInterfacesContainer(BaseModel):
interfaces: List[Union[NetworkInterface, WiFiDevice]]
container_x: int
container_y: int
class Activation(BaseModel):
id: int
interfaces: List[Union[NetworkInterface, WiFiDevice]]
container_x: int
container_y: int
isOpen: bool = True
class Deactivation(BaseModel):
id: int
interfaces: List[Union[NetworkInterface, WiFiDevice]]
container_x: int
container_y: int
isOpen: bool = True

14
SRV/models/subnet.py Normal file
View file

@ -0,0 +1,14 @@
from pydantic import BaseModel
from typing import Optional, List
class Device(BaseModel):
ip: str
mac: Optional[str] = None
hostname: Optional[str] = None
vendor: Optional[str] = None
open_ports: Optional[List[str]] = None
status: str = "online"
last_seen: Optional[str] = None
class SubnetScanResult(BaseModel):
devices: List[Device]

263
SRV/models/ufw.py Normal file
View file

@ -0,0 +1,263 @@
from typing import List, Optional, Annotated, Union
from enum import Enum
from pydantic import BaseModel, Field, validator
import ipaddress
class Protocol(str, Enum):
"""
Enum для сетевых протоколов UFW.
"""
TCP = "tcp"
UDP = "udp"
BOTH = "both"
@property
def protocols(self) -> List[str]:
"""Возвращает список протоколов для использования в команде UFW."""
return ["tcp", "udp"] if self == Protocol.BOTH else [self.value]
class Action(str, Enum):
"""
Enum для действий UFW.
Attributes:
ALLOW: Разрешить трафик
DENY: Запретить трафик без уведомления
REJECT: Запретить трафик с уведомлением отправителю
"""
ALLOW = "allow"
DENY = "deny"
REJECT = "reject"
class Direction(str, Enum):
"""
Enum для направления трафика UFW.
Attributes:
IN: Входящий трафик
OUT: Исходящий трафик
"""
IN = "in"
OUT = "out"
class UFWRule(BaseModel):
"""Модель правила UFW"""
action: Action
direction: Direction
from_ip: str
to_ip: str
port: Union[int, str]
protocol: Protocol
comment: Optional[str] = None
@validator("port")
def validate_port(cls, v):
"""
Проверяет корректность порта или диапазона портов.
Args:
v: Порт (число) или диапазон портов (строка вида "3000:3999")
Returns:
Union[int, str]: Валидированное значение порта
Raises:
ValueError: Если порт или диапазон портов некорректен
"""
if isinstance(v, int):
if not (1 <= v <= 65535):
raise ValueError(f"Порт должен быть в диапазоне 1-65535, получено: {v}")
return v
elif isinstance(v, str):
try:
start, end = map(int, v.split(':'))
if not (1 <= start <= end <= 65535):
raise ValueError
return v
except (ValueError, TypeError):
raise ValueError(f"Некорректный диапазон портов: {v}. Формат: start:end, где 1 <= start <= end <= 65535")
else:
raise ValueError(f"Порт должен быть числом или строкой с диапазоном, получено: {type(v)}")
@validator("from_ip", "to_ip")
def validate_ip(cls, v):
"""Проверяет корректность IP адреса"""
try:
# Пробуем создать объект IP-сети, что автоматически проверит корректность адреса
ipaddress.ip_network(v, strict=False)
return v
except ValueError:
raise ValueError(f"Некорректный IP адрес или подсеть: {v}")
@validator("to_ip")
def validate_ip_version_compatibility(cls, v, values):
"""Проверяет совместимость версий IP адресов"""
if "from_ip" not in values:
return v
# Определяем версии IP
from_ip = ipaddress.ip_network(values["from_ip"], strict=False)
to_ip = ipaddress.ip_network(v, strict=False)
if isinstance(from_ip, ipaddress.IPv6Network) != isinstance(to_ip, ipaddress.IPv6Network):
raise ValueError("Несовместимые версии IP: нельзя смешивать IPv4 и IPv6 в одном правиле")
return v
@validator("action", pre=True)
def validate_action(cls, v):
"""Приводит значение action к нижнему регистру перед валидацией"""
if isinstance(v, str):
return v.lower()
return v
@validator("protocol", pre=True)
def validate_protocol(cls, v):
"""Приводит значение protocol к нижнему регистру перед валидацией"""
if isinstance(v, str):
return v.lower()
return v
def to_ufw_command(self) -> str:
"""Преобразует правило в команду UFW."""
cmd_parts = ["ufw"]
# Добавляем действие
cmd_parts.append(self.action.value)
# Добавляем направление
cmd_parts.append(self.direction.value)
# Добавляем from с IP
cmd_parts.extend(["from", self.from_ip])
# Добавляем to с IP
cmd_parts.extend(["to", self.to_ip])
# Добавляем порт и протокол
port_str = str(self.port) # Работает как с числами, так и с диапазонами
cmd_parts.extend(["port", port_str])
# Добавляем протокол
proto = self.protocol.value
if proto == "both":
cmd_parts.append("proto tcp/udp")
else:
cmd_parts.extend(["proto", proto])
# Добавляем комментарий если есть
if self.comment:
cmd_parts.extend(["comment", f"'{self.comment}'"])
return " ".join(cmd_parts)
@classmethod
def from_ufw_output(cls, line: str) -> "UFWRule":
"""
Создает экземпляр UFWRule из строки вывода команды 'ufw status numbered'.
Args:
line: Строка вывода UFW (например, "[1] 80/tcp ALLOW IN 192.168.1.0/24 # Web Server")
Returns:
UFWRule: Новый экземпляр правила
"""
import re
# Регулярное выражение для разбора строки правила UFW
pattern = r"""^\s*
\[\s*(?P<number>\d+)\s*\]\s*
(?P<port>\d+(?::\d+)?)/(?P<protocol>tcp(?:/udp)?|udp)\s+
(?P<action>ALLOW|DENY|REJECT)\s+(?P<direction>IN|OUT)\s+
(?P<from_ip>[\w\d\./]+|\bAnywhere\b)
(?:\s+\(v6\))?
(?:\s+\((?:out|in)\))?
\s*
(?:\#\s*(?P<comment>.*?))?
\s*$
"""
match = re.match(pattern, line, re.VERBOSE | re.IGNORECASE)
if not match:
raise ValueError(f"Невозможно разобрать строку правила UFW: {line}")
groups = match.groupdict()
# Преобразуем протокол в enum
proto_str = groups['protocol'].lower()
protocol = Protocol.BOTH if proto_str == "tcp/udp" else \
Protocol.UDP if proto_str == "udp" else Protocol.TCP
# Преобразуем действие в enum (игнорируем регистр)
action_str = groups['action'].lower()
action = Action.ALLOW if action_str == "allow" else \
Action.DENY if action_str == "deny" else Action.REJECT
# Преобразуем направление в enum
direction_str = groups['direction'].lower()
direction = Direction.IN if direction_str == "in" else Direction.OUT
# Обрабатываем IP адреса
from_ip = groups['from_ip'].strip()
if from_ip.lower() == "anywhere":
from_ip = "0.0.0.0/0"
# Для исходящих правил меняем местами from и to
to_ip = "0.0.0.0/0" # По умолчанию anywhere
# Обрабатываем порт
port = groups['port']
if ':' in port:
# Оставляем как строку для диапазона
port = port.strip()
else:
# Преобразуем в число для одиночного порта
port = int(port)
# Обрабатываем комментарий
comment = groups.get('comment', '').strip() if groups.get('comment') else None
return cls(
action=action,
direction=direction,
from_ip=from_ip,
to_ip=to_ip,
port=port,
protocol=protocol,
comment=comment
)
class UFWRulesResponse(BaseModel):
"""
Модель для представления списка правил UFW.
Attributes:
enabled: Включен ли UFW
rules: Список правил UFW
total_rules: Общее количество правил
"""
enabled: bool = Field(..., description="Включен ли UFW")
rules: List[UFWRule] = Field(default_factory=list, description="Список правил UFW")
@property
def total_rules(self) -> int:
"""Возвращает общее количество правил."""
return len(self.rules)
class UFWStatus(BaseModel):
"""
Модель для представления статуса UFW.
Attributes:
enabled: Включен ли UFW
message: Сообщение о статусе
last_checked: Время последней проверки статуса
"""
enabled: bool = Field(..., description="Включен ли UFW")
message: str = Field(..., description="Сообщение о статусе")

15
SRV/models/wifi.py Normal file
View file

@ -0,0 +1,15 @@
from pydantic import BaseModel
from typing import Optional, List
class WifiAccessPoint(BaseModel):
ssid: Optional[str] = None
bssid: str
channel: Optional[int] = None
signal: Optional[int] = None
encryption: Optional[str] = None
frequency: Optional[str] = None
vendor: Optional[str] = None
hidden: bool = False
class WifiScanResult(BaseModel):
access_points: List[WifiAccessPoint]