From ea56d17ea7d858fa0cfcb0334a9b829b2cfbc616 Mon Sep 17 00:00:00 2001 From: "s.galanskiy" Date: Thu, 12 Jun 2025 13:45:55 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=B5=D1=82=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=BE=D0=B2=20=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D0=B5=D1=82=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SRV/api/v1/subnet.py | 30 +++- SRV/docs/firewalld.md | 276 --------------------------------- SRV/docs/network.md | 0 SRV/docs/ufw.md | 208 ------------------------- SRV/services/subnet_service.py | 74 +++++++-- 5 files changed, 86 insertions(+), 502 deletions(-) delete mode 100644 SRV/docs/firewalld.md delete mode 100644 SRV/docs/network.md delete mode 100644 SRV/docs/ufw.md diff --git a/SRV/api/v1/subnet.py b/SRV/api/v1/subnet.py index 1942c62..9dc4da0 100644 --- a/SRV/api/v1/subnet.py +++ b/SRV/api/v1/subnet.py @@ -33,9 +33,15 @@ def add_note(mac: str, request: NoteRequest): """Добавляет заметку для устройства по MAC-адресу""" try: subnet_service = SubnetService() - if not subnet_service.add_note(mac, request.note): - raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + success, error_message = subnet_service.add_note(mac, request.note) + if not success: + if "не найдено" in error_message: + raise HTTPException(status_code=404, detail=error_message) + elif "уже существует" in error_message: + raise HTTPException(status_code=400, detail=error_message) return {"message": f"Заметка '{request.note}' добавлена для устройства {mac}"} + except HTTPException as e: + raise e except Exception as e: raise HTTPException(status_code=500, detail=f"Ошибка добавления заметки: {str(e)}") @@ -44,9 +50,15 @@ def update_note(mac: str, request: NoteRequest): """Обновляет заметку для устройства по MAC-адресу""" try: subnet_service = SubnetService() - if not subnet_service.update_note(mac, request.note): - raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + success, error_message = subnet_service.update_note(mac, request.note) + if not success: + if "не найдено" in error_message: + raise HTTPException(status_code=404, detail=error_message) + elif "не существует" in error_message: + raise HTTPException(status_code=400, detail=error_message) return {"message": f"Заметка '{request.note}' обновлена для устройства {mac}"} + except HTTPException as e: + raise e except Exception as e: raise HTTPException(status_code=500, detail=f"Ошибка обновления заметки: {str(e)}") @@ -55,8 +67,14 @@ def delete_note(mac: str): """Удаляет заметку для устройства по MAC-адресу""" try: subnet_service = SubnetService() - if not subnet_service.delete_note(mac): - raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + success, error_message = subnet_service.delete_note(mac) + if not success: + if "не найдено" in error_message: + raise HTTPException(status_code=404, detail=error_message) + elif "не существует" in error_message: + raise HTTPException(status_code=400, detail=error_message) return {"message": f"Заметка удалена для устройства {mac}"} + except HTTPException as e: + raise e except Exception as e: raise HTTPException(status_code=500, detail=f"Ошибка удаления заметки: {str(e)}") \ No newline at end of file diff --git a/SRV/docs/firewalld.md b/SRV/docs/firewalld.md deleted file mode 100644 index ef5c824..0000000 --- a/SRV/docs/firewalld.md +++ /dev/null @@ -1,276 +0,0 @@ -# Документация API FirewallD - -## Общее описание -API предоставляет интерфейс для управления межсетевым экраном FirewallD в Linux системах. API построено на основе взаимодействия с FirewallD через D-Bus интерфейс. - -## Внутренняя архитектура - -### 1. Уровни взаимодействия -``` -[HTTP API] -> [FastAPI Router] -> [FirewallD Service] -> [D-Bus] -> [FirewallD] -``` - -### 2. Компоненты системы - -#### 2.1 D-Bus интерфейс -FirewallD предоставляет D-Bus интерфейс `org.fedoraproject.FirewallD1` для управления. Наш сервис подключается к системной шине D-Bus и взаимодействует с FirewallD через этот интерфейс. - -Основные методы D-Bus: -- `getZones()` - получение списка зон -- `getDefaultZone()` - получение зоны по умолчанию -- `addService()` - добавление сервиса -- `removeService()` - удаление сервиса -и т.д. - -#### 2.2 Модели данных -В `models/firewalld.py` определены Pydantic модели для валидации данных: - -##### FirewallZone -Используется для представления зоны файрвола: -```python -class FirewallZone(BaseModel): - name: str - target: Optional[str] = None - services: List[str] = Field(default_factory=list) - ports: List[str] = Field(default_factory=list) - sources: List[str] = Field(default_factory=list) - interfaces: List[str] = Field(default_factory=list) -``` -Применяется при получении настроек зоны через API. - -##### FirewallService -Модель для конфигурации сервиса: -```python -class FirewallService(BaseModel): - name: str - ports: List[str] = Field(default_factory=list) - protocols: List[str] = Field(default_factory=list) - modules: List[str] = Field(default_factory=list) -``` -Используется при добавлении новых сервисов через API. - -##### FirewallPort -Модель для конфигурации порта с валидацией протокола: -```python -class FirewallPort(BaseModel): - port: int - protocol: Optional[str] = Field(default=None) - timeout: Optional[int] = 0 -``` -Применяется в API endpoint POST /api/v1/firewalld/zones/{zone}/ports для валидации входных данных. - -##### FirewallSource -Модель для конфигурации источника с валидацией IP-адреса: -```python -class FirewallSource(BaseModel): - address: str = Field(..., pattern=r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?:/[0-9]{1,2})?$') - zone: Optional[str] = None -``` -Используется в API endpoint POST /api/v1/firewalld/zones/{zone}/sources для валидации IP-адресов. - -##### FirewallRule -Обобщенная модель правила файрвола: -```python -class FirewallRule(BaseModel): - zone: str - source: Optional[FirewallSource] = None - port: Optional[FirewallPort] = None - service: Optional[str] = None -``` -Используется для комплексных операций с правилами. - -В API (`api/v1/firewalld.py`) определены дополнительные модели для запросов: - -##### PortRule -```python -class PortRule(BaseModel): - port: int - protocol: str - timeout: Optional[int] = 0 -``` -Используется для валидации входных данных при добавлении портов. - -##### ServiceRule -```python -class ServiceRule(BaseModel): - service: str - timeout: Optional[int] = 0 -``` -Используется для валидации входных данных при добавлении сервисов. - -##### SourceRule -```python -class SourceRule(BaseModel): - source: str -``` -Используется для валидации входных данных при добавлении источников. - -Все модели используют Pydantic для: -- Валидации входных данных -- Автоматической конвертации типов -- Генерации OpenAPI документации -- Проверки бизнес-правил (например, валидация протокола в FirewallPort) - -#### 2.3 Сервисный слой -`FirewallService` в `services/firewalld_service.py` обеспечивает: -- Подключение к D-Bus -- Обработку ошибок -- Преобразование данных между API и D-Bus форматами - -## API Endpoints - -### Управление зонами - -#### GET /api/v1/firewalld/zones -Получение списка всех доступных зон. - -**Ответ:** -```json -[ - "public", - "internal", - "external", - ... -] -``` - -**Внутренняя работа:** -1. Запрос поступает в FastAPI router -2. Router вызывает `FirewallService.get_zones()` -3. Сервис делает вызов `getZones()` через D-Bus -4. Результат преобразуется в JSON и возвращается клиенту - -#### GET /api/v1/firewalld/zones/default -Получение зоны по умолчанию. - -**Ответ:** -```json -{ - "name": "public" -} -``` - -**Внутренняя работа:** -1. Вызов `FirewallService.get_default_zone()` -2. D-Bus запрос `getDefaultZone()` -3. Преобразование результата - -#### PUT /api/v1/firewalld/zones/default/{zone} -Установка зоны по умолчанию. - -**Параметры:** -- `zone`: имя зоны - -**Внутренняя работа:** -1. Валидация имени зоны -2. Вызов `FirewallService.set_default_zone()` -3. D-Bus запрос `setDefaultZone()` - -### Управление сервисами - -#### GET /api/v1/firewalld/zones/{zone}/services -Получение списка сервисов в зоне. - -**Ответ:** -```json -{ - "services": ["http", "https", "ssh"] -} -``` - -**Внутренняя работа:** -1. Проверка существования зоны -2. Вызов `FirewallService.get_services()` -3. D-Bus запрос `getServices()` - -#### POST /api/v1/firewalld/zones/{zone}/services -Добавление сервиса в зону. - -**Тело запроса:** -```json -{ - "service": "http", - "timeout": 0 -} -``` - -**Внутренняя работа:** -1. Валидация данных через Pydantic модель -2. Вызов `FirewallService.add_service()` -3. D-Bus запрос `addService()` - -#### DELETE /api/v1/firewalld/zones/{zone}/services/{service} -Удаление сервиса из зоны. - -**Внутренняя работа:** -1. Проверка существования сервиса -2. Вызов `FirewallService.remove_service()` -3. D-Bus запрос `removeService()` - -### Управление портами - -#### GET /api/v1/firewalld/zones/{zone}/ports -Получение списка портов в зоне. - -**Ответ:** -```json -{ - "ports": ["80/tcp", "443/tcp"] -} -``` - -#### POST /api/v1/firewalld/zones/{zone}/ports -Добавление порта в зону. - -**Тело запроса:** -```json -{ - "port": 80, - "protocol": "tcp", - "timeout": 0 -} -``` - -#### DELETE /api/v1/firewalld/zones/{zone}/ports -Удаление порта из зоны. - -### Управление источниками - -#### GET /api/v1/firewalld/zones/{zone}/sources -Получение списка источников в зоне. - -#### POST /api/v1/firewalld/zones/{zone}/sources -Добавление источника в зону. - -#### DELETE /api/v1/firewalld/zones/{zone}/sources/{source} -Удаление источника из зоны. - -## Обработка ошибок - -API возвращает следующие коды ошибок: -- 400: Неверный запрос (например, неверный формат данных) -- 404: Ресурс не найден (зона, сервис и т.д.) -- 500: Внутренняя ошибка сервера - -При ошибке D-Bus или FirewallD, ошибка логируется и возвращается соответствующий HTTP код. - -## Возможные улучшения - -1. **Безопасность:** - - Добавить аутентификацию - - Добавить rate limiting - - Логирование действий - -2. **Функциональность:** - - Добавить поддержку rich rules - - Управление ICMP правилами - - Управление forward портами - -3. **Производительность:** - - Кэширование часто запрашиваемых данных - - Batch операции для множественных изменений - -4. **Надёжность:** - - Retry механизм для D-Bus операций - - Более детальная обработка ошибок - - Валидация состояния FirewallD diff --git a/SRV/docs/network.md b/SRV/docs/network.md deleted file mode 100644 index e69de29..0000000 diff --git a/SRV/docs/ufw.md b/SRV/docs/ufw.md deleted file mode 100644 index 9ef016c..0000000 --- a/SRV/docs/ufw.md +++ /dev/null @@ -1,208 +0,0 @@ -# UFW API Documentation - -## Overview - -API для управления файрволом UFW (Uncomplicated Firewall) в Linux. Позволяет добавлять, удалять и просматривать правила межсетевого экрана. - -## Архитектура - -### Модели данных - -#### Protocol (Протокол) -```python -class Protocol(str, Enum): - TCP = "tcp" # Только TCP протокол - UDP = "udp" # Только UDP протокол - BOTH = "both" # Оба протокола (TCP и UDP) -``` - -При указании `BOTH`, система автоматически создает правила для обоих протоколов, используя `proto tcp/udp` в команде UFW. - -#### Action (Действие) -```python -class Action(str, Enum): - ALLOW = "allow" # Разрешить трафик - DENY = "deny" # Запретить трафик без уведомления - REJECT = "reject" # Запретить трафик с уведомлением отправителю -``` - -#### Direction (Направление) -```python -class Direction(str, Enum): - IN = "in" # Входящий трафик - OUT = "out" # Исходящий трафик -``` - -#### UFWRule (Правило UFW) -```python -class UFWRule(BaseModel): - action: Action # Действие (allow/deny/reject) - direction: Direction # Направление трафика (in/out) - from_ip: str # IP-адрес источника - to_ip: str # IP-адрес назначения - port: Union[int, str] # Порт или диапазон портов - protocol: Protocol # Протокол - comment: Optional[str] # Комментарий к правилу -``` - -Модель выполняет следующие проверки: -1. Валидация IP адресов: - - Проверяет корректность IPv4/IPv6 адресов - - Поддерживает CIDR нотацию - - Проверяет совместимость версий IP (IPv4/IPv6) - -2. Валидация портов: - - Одиночный порт: число от 1 до 65535 - - Диапазон портов: строка вида "3000:4000" - - Проверяет корректность диапазона (start <= end) - -## API Endpoints - -### Получение правил - -#### GET /ufw/rules -Получает список всех правил UFW. - -**Response:** -```json -{ - "enabled": true, - "rules": [ - { - "action": "allow", - "direction": "in", - "from_ip": "0.0.0.0/0", - "to_ip": "0.0.0.0/0", - "port": 80, - "protocol": "tcp", - "comment": "Allow HTTP" - } - ] -} -``` - -### Добавление правил - -#### POST /ufw/rules/in -Добавляет входящие правила UFW. - -**Request:** -```json -{ - "rules": [ - { - "action": "allow", - "from_ip": "0.0.0.0/0", - "to_ip": "0.0.0.0/0", - "port": 80, - "protocol": "tcp", - "comment": "Allow HTTP" - } - ] -} -``` - -**Response:** -```json -{ - "status": "success", - "message": "Входящие правила UFW успешно добавлены" -} -``` - -#### POST /ufw/rules/out -Добавляет исходящие правила UFW. - -**Request:** -```json -{ - "rules": [ - { - "action": "allow", - "from_ip": "192.168.1.0/24", - "to_ip": "0.0.0.0/0", - "port": 443, - "protocol": "tcp", - "comment": "Allow HTTPS outbound" - } - ] -} -``` - -**Response:** -```json -{ - "status": "success", - "message": "Исходящие правила UFW успешно добавлены" -} -``` - -### Удаление правил - -#### DELETE /ufw/rules -Удаляет правила UFW по их номерам. - -**Request:** -```json -{ - "numbers": [1, 2, 3] -} -``` - -**Response:** -```json -{ - "status": "success", - "message": "Правила UFW успешно удалены" -} -``` - -### Управление состоянием - -#### POST /ufw/enable -Включает UFW. - -**Response:** -```json -{ - "status": "success", - "message": "UFW успешно включен" -} -``` - -#### POST /ufw/disable -Выключает UFW. - -**Response:** -```json -{ - "status": "success", - "message": "UFW успешно выключен" -} -``` - -## Обработка ошибок - -### HTTP Status Codes - -1. Успешные операции (200): - - Получение списка правил - - Успешное добавление/удаление правил - - Включение/выключение UFW - -2. Ошибки клиента (400): - - UFW отключен при попытке добавления правил - - Некорректный IP адрес или CIDR - - Неверный номер порта или диапазон - - Несовместимые версии IP (IPv4/IPv6) - - Неверные значения enum (action/protocol/direction) - -3. Ошибки сервера (500): - - Ошибки выполнения команд UFW - - Внутренние ошибки сервера - -### Формат ошибок -```json -{ - "detail": "Описание ошибки" -} \ No newline at end of file diff --git a/SRV/services/subnet_service.py b/SRV/services/subnet_service.py index 3ae2fc8..ab395c8 100644 --- a/SRV/services/subnet_service.py +++ b/SRV/services/subnet_service.py @@ -268,44 +268,94 @@ class SubnetService: logger.warning(f"Не удалось получить локальное устройство: {str(e)}") return None - def add_note(self, mac: str, note: str) -> bool: + def add_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]: + """Добавляет заметку к устройству по MAC-адресу. + + Args: + mac (str): MAC-адрес устройства. + note (str): Текст заметки. + + Returns: + Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке). + - Успех: (True, None). + - Ошибка: (False, сообщение об ошибке). + + Raises: + Exception: Если произошла ошибка при сохранении. + """ try: saved_devices = self.storage.load_devices() if mac not in saved_devices: logger.warning(f"Устройство с MAC {mac} не найдено") - return False + return False, f"Устройство с MAC {mac} не найдено" + if saved_devices[mac].note is not None: + logger.error(f"Заметка для устройства с MAC {mac} уже существует") + return False, f"Заметка для устройства с MAC {mac} уже существует" saved_devices[mac].note = note self.storage.save_devices(list(saved_devices.values())) logger.info(f"Добавлена заметка '{note}' для устройства с MAC {mac}") - return True + return True, None except Exception as e: logger.error(f"Ошибка при добавлении заметки для MAC {mac}: {str(e)}") - return False + return False, f"Внутренняя ошибка сервера: {str(e)}" - def update_note(self, mac: str, note: str) -> bool: + def update_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]: + """Обновляет заметку устройства по MAC-адресу. + + Args: + mac (str): MAC-адрес устройства. + note (str): Новый текст заметки. + + Returns: + Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке). + - Успех: (True, None). + - Ошибка: (False, сообщение об ошибке). + + Raises: + Exception: Если произошла ошибка при сохранении. + """ try: saved_devices = self.storage.load_devices() if mac not in saved_devices: logger.warning(f"Устройство с MAC {mac} не найдено") - return False + return False, f"Устройство с MAC {mac} не найдено" + if saved_devices[mac].note is None: + logger.error(f"Заметка для устройства с MAC {mac} не существует") + return False, f"Заметка для устройства с MAC {mac} не существует" saved_devices[mac].note = note self.storage.save_devices(list(saved_devices.values())) logger.info(f"Обновлена заметка '{note}' для устройства с MAC {mac}") - return True + return True, None except Exception as e: logger.error(f"Ошибка при обновлении заметки для MAC {mac}: {str(e)}") - return False + return False, f"Внутренняя ошибка сервера: {str(e)}" - def delete_note(self, mac: str) -> bool: + def delete_note(self, mac: str) -> Tuple[bool, Optional[str]]: + """Удаляет заметку устройства по MAC-адресу. + + Args: + mac (str): MAC-адрес устройства. + + Returns: + Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке). + - Успех: (True, None). + - Ошибка: (False, сообщение об ошибке). + + Raises: + Exception: Если произошла ошибка при сохранении. + """ try: saved_devices = self.storage.load_devices() if mac not in saved_devices: logger.warning(f"Устройство с MAC {mac} не найдено") - return False + return False, f"Устройство с MAC {mac} не найдено" + if saved_devices[mac].note is None: + logger.error(f"Заметка для устройства с MAC {mac} не существует") + return False, f"Заметка для устройства с MAC {mac} не существует" saved_devices[mac].note = None self.storage.save_devices(list(saved_devices.values())) logger.info(f"Удалена заметка для устройства с MAC {mac}") - return True + return True, None except Exception as e: logger.error(f"Ошибка при удалении заметки для MAC {mac}: {str(e)}") - return False \ No newline at end of file + return False, f"Внутренняя ошибка сервера: {str(e)}" \ No newline at end of file