529 lines
22 KiB
Markdown
529 lines
22 KiB
Markdown
# Полный план реализации — IP WhiteList Microservice
|
||
|
||
> Автор: GitHub Copilot (GPT-5.4)
|
||
> Дата: 2026-05-29
|
||
> Статус: рабочий план для реализации
|
||
|
||
## 1. Цель системы
|
||
|
||
Нужно реализовать внутренний веб-сервис, в котором клиенты облачного провайдера смогут самостоятельно управлять доверенными IPv4-адресами и подсетями. Эти записи должны исключаться из блокировки во время DDoS-митигции.
|
||
|
||
Сервис должен решать 3 задачи:
|
||
|
||
1. Дать клиенту self-service интерфейс для управления whitelist.
|
||
2. Дать администраторам и сетевым инженерам централизованный просмотр и контроль.
|
||
3. Отдавать агрегированный экспорт всех активных записей в текстовом формате для внешних систем фильтрации.
|
||
|
||
## 2. Что именно должно быть в первой рабочей версии
|
||
|
||
Первая версия должна включать:
|
||
|
||
1. Авторизацию через Keycloak OIDC.
|
||
2. Разделение прав client/admin.
|
||
3. Поддержку одной или нескольких компаний у пользователя.
|
||
4. Таблицу whitelist-записей.
|
||
5. Создание, редактирование и soft delete записей.
|
||
6. Проверку лимитов по компаниям.
|
||
7. Аудит всех изменяющих действий.
|
||
8. Экспорт агрегированного списка активных CIDR в text/plain.
|
||
9. Серверную валидацию IPv4 и CIDR по правилам ТЗ.
|
||
10. Клиентскую валидацию формы для UX.
|
||
|
||
## 3. Обязательные уточнения до начала интеграции с Keycloak
|
||
|
||
До кодирования OIDC-части нужно получить точные ответы на 3 вопроса:
|
||
|
||
1. Какой claim или role означает администратора.
|
||
Сейчас в ТЗ сказано: clientId = WZ01112 и отдельный чек-бокс. Нужно точно знать, во что это превращается в токене.
|
||
|
||
2. Как кодируется принадлежность к нескольким компаниям.
|
||
В ТЗ упомянут мультикомпанейный сценарий, но в claims перечислен только clientID. Нужно уточнить, это строка, массив, groups или другой формат.
|
||
|
||
3. Где именно ограничивается внешний экспортный endpoint по IP.
|
||
Нужно решить, это делает приложение, Nginx/Ingress, или оба уровня сразу.
|
||
|
||
Без этих 3 ответов можно делать каркас, БД, валидацию, CRUD и UI, но нельзя окончательно зафиксировать auth-слой.
|
||
|
||
## 4. Рекомендуемый стек
|
||
|
||
### Бэкенд
|
||
|
||
- Python 3.11+
|
||
- FastAPI
|
||
- Uvicorn
|
||
|
||
Причина: Python понятен команде, FastAPI даёт простой роутинг, типизацию, dependency injection и удобную основу для API и HTML-эндпоинтов.
|
||
|
||
### База данных
|
||
|
||
- PostgreSQL
|
||
- psycopg2-binary
|
||
- Alembic для миграций
|
||
|
||
Причина: нужны надёжные транзакции, аудит, индексы и понятный деплой. Здесь нет выгоды от тяжёлой ORM-магии, поэтому лучше простой и читаемый SQL.
|
||
|
||
### UI
|
||
|
||
- Jinja2
|
||
- обычные HTML-формы
|
||
- HTMX по желанию, только если реально упрощает частичные обновления
|
||
- минимальный JS для inline-валидации и уведомлений
|
||
|
||
Причина: задача не требует SPA. Простая серверная отрисовка снизит сложность и упростит поддержку.
|
||
|
||
### Авторизация
|
||
|
||
- Keycloak OIDC
|
||
- JWT-проверка по JWKS
|
||
- отдельный dev-режим без Keycloak только для локальной разработки
|
||
|
||
### Работа с IP
|
||
|
||
- стандартный модуль ipaddress
|
||
- netaddr только если стандартной библиотеки окажется недостаточно для агрегирования
|
||
|
||
Примечание: начать можно вообще без netaddr. Для суммаризации сначала стоит проверить, хватает ли ipaddress.collapse_addresses.
|
||
|
||
## 5. Архитектурные принципы
|
||
|
||
1. Синхронный код по умолчанию.
|
||
Для этой системы async не нужен. Он только повысит стоимость поддержки.
|
||
|
||
2. Серверная валидация является источником истины.
|
||
Клиентская валидация только помогает пользователю.
|
||
|
||
3. Аудит append-only.
|
||
Записи аудита нельзя изменять и удалять.
|
||
|
||
4. Все проверки прав и лимитов выполняются на сервере внутри транзакций.
|
||
|
||
5. Soft delete обязателен для whitelist-записей.
|
||
|
||
6. Значение лимита по умолчанию должно меняться через конфиг без пересборки.
|
||
|
||
7. Dev-заглушка авторизации должна быть жёстко отключаемой в production.
|
||
|
||
## 6. Предлагаемая структура проекта
|
||
|
||
```text
|
||
IPWhiteList/
|
||
├── app/
|
||
│ ├── main.py
|
||
│ ├── config.py
|
||
│ ├── db.py
|
||
│ ├── security.py
|
||
│ ├── validators.py
|
||
│ ├── cidr_utils.py
|
||
│ ├── services/
|
||
│ │ ├── entries.py
|
||
│ │ ├── companies.py
|
||
│ │ ├── audit.py
|
||
│ │ └── export.py
|
||
│ ├── repositories/
|
||
│ │ ├── entries.py
|
||
│ │ ├── companies.py
|
||
│ │ └── audit.py
|
||
│ ├── routers/
|
||
│ │ ├── ui.py
|
||
│ │ ├── admin.py
|
||
│ │ └── export.py
|
||
│ ├── auth/
|
||
│ │ ├── oidc.py
|
||
│ │ ├── dev_stub.py
|
||
│ │ └── deps.py
|
||
│ └── templates/
|
||
│ ├── base.html
|
||
│ ├── index.html
|
||
│ ├── entry_form.html
|
||
│ ├── login_error.html
|
||
│ └── admin/
|
||
│ ├── audit.html
|
||
│ └── limits.html
|
||
├── static/
|
||
│ └── style.css
|
||
├── migrations/
|
||
│ └── versions/
|
||
├── tests/
|
||
│ ├── test_validators.py
|
||
│ ├── test_entries_service.py
|
||
│ ├── test_export.py
|
||
│ └── test_auth_mapping.py
|
||
├── docs/
|
||
│ ├── plan.md
|
||
│ ├── plan-v2.md
|
||
│ └── plan-gpt54.md
|
||
├── requirements.txt
|
||
├── .env.example
|
||
├── alembic.ini
|
||
├── docker-compose.yml
|
||
├── Dockerfile
|
||
└── README.md
|
||
```
|
||
|
||
## 7. Модель данных
|
||
|
||
### Таблица companies
|
||
|
||
Назначение: хранение компаний и переопределённых лимитов.
|
||
|
||
Поля:
|
||
|
||
1. id
|
||
2. client_id
|
||
3. name
|
||
4. custom_limit
|
||
5. created_at
|
||
6. updated_at
|
||
|
||
Правила:
|
||
|
||
1. client_id уникален.
|
||
2. custom_limit может быть null, тогда используется глобальный лимит.
|
||
|
||
### Таблица whitelist_entries
|
||
|
||
Назначение: активные и удалённые whitelist-записи.
|
||
|
||
Поля:
|
||
|
||
1. id
|
||
2. company_id
|
||
3. value_cidr
|
||
4. comment
|
||
5. created_by
|
||
6. created_at
|
||
7. updated_by
|
||
8. updated_at
|
||
9. deleted_by
|
||
10. deleted_at
|
||
|
||
Правила:
|
||
|
||
1. value_cidr хранится только в нормализованном виде.
|
||
2. deleted_at is null означает активную запись.
|
||
3. comment ограничен 255 символами.
|
||
|
||
### Таблица audit_log
|
||
|
||
Назначение: неизменяемый журнал действий.
|
||
|
||
Поля:
|
||
|
||
1. id
|
||
2. user_email
|
||
3. company_id
|
||
4. action
|
||
5. old_value
|
||
6. new_value
|
||
7. created_at
|
||
|
||
Дополнительно желательно хранить:
|
||
|
||
1. target_entry_id
|
||
2. request_id
|
||
3. actor_role
|
||
|
||
Это не противоречит ТЗ и упростит разбор инцидентов.
|
||
|
||
## 8. Правила авторизации и ролей
|
||
|
||
### Клиент
|
||
|
||
1. Видит только записи своей активной компании.
|
||
2. Может создавать, редактировать и удалять записи только в допустимом контексте компании.
|
||
3. Может переключать активную компанию, если в токене действительно есть доступ к нескольким компаниям.
|
||
|
||
### Администратор
|
||
|
||
1. Видит записи всех компаний.
|
||
2. Может менять записи всех компаний.
|
||
3. Может видеть удалённые записи.
|
||
4. Может смотреть аудит.
|
||
5. Может менять custom_limit для компании.
|
||
|
||
### Что нужно реализовать в коде
|
||
|
||
1. Унифицированную модель текущего пользователя.
|
||
2. Отдельную функцию маппинга claims в внутреннюю роль.
|
||
3. Жёсткие проверки прав на уровне service-слоя, не только роутеров.
|
||
|
||
## 9. Валидация IPv4 и CIDR
|
||
|
||
Эта часть критична. Её нужно делать одной из первых и сразу покрывать тестами.
|
||
|
||
### Поддерживаемый ввод
|
||
|
||
1. Одиночный IPv4 адрес, который трактуется как /32.
|
||
2. IPv4 подсеть в CIDR нотации.
|
||
|
||
### Запрещённый ввод
|
||
|
||
1. IPv6.
|
||
2. Доменное имя.
|
||
3. Маска шире допустимой.
|
||
4. Любые private или special ranges из приложения А.
|
||
|
||
### Правила маски
|
||
|
||
Допустимы только /22 ... /32.
|
||
|
||
### Нормализация
|
||
|
||
Если пользователь ввёл адрес с host-битами, сервис должен:
|
||
|
||
1. Нормализовать значение до адреса сети.
|
||
2. Сохранить нормализованное значение.
|
||
3. Вернуть пользователю явное сообщение, что адрес был нормализован.
|
||
|
||
### Проверки в пределах компании
|
||
|
||
1. Запрет полного дубликата активной записи.
|
||
2. Запрет любого пересечения активной записи с существующими активными записями той же компании.
|
||
3. Между разными компаниями пересечения допускаются.
|
||
|
||
### Список запрещённых диапазонов
|
||
|
||
Нужно захардкодить как конфигурацию приложения и покрыть тестами:
|
||
|
||
1. 10.0.0.0/8
|
||
2. 172.16.0.0/12
|
||
3. 192.168.0.0/16
|
||
4. 100.64.0.0/10
|
||
5. 127.0.0.0/8
|
||
6. 169.254.0.0/16
|
||
7. 192.0.0.0/24
|
||
8. 192.0.2.0/24
|
||
9. 198.51.100.0/24
|
||
10. 203.0.113.0/24
|
||
11. 198.18.0.0/15
|
||
12. 224.0.0.0/4
|
||
13. 240.0.0.0/4
|
||
14. 255.255.255.255/32
|
||
|
||
## 10. Лимиты и конкурентность
|
||
|
||
Это важное место, которого обычно недооценивают.
|
||
|
||
### Правила лимитов
|
||
|
||
1. Есть глобальный DEFAULT_LIMIT, по умолчанию 15.
|
||
2. Для компании может быть custom_limit.
|
||
3. При снижении лимита ниже текущего количества записей существующие записи не удаляются.
|
||
4. Пока число активных записей больше лимита, новые записи создавать нельзя.
|
||
|
||
### Риск гонок
|
||
|
||
Если два запроса одновременно создают записи в одной компании, возможны:
|
||
|
||
1. Пробитие лимита.
|
||
2. Пропуск пересечения.
|
||
3. Пропуск дубликата.
|
||
|
||
### Что делать
|
||
|
||
Операцию создания и обновления записи нужно делать в транзакции с сериализацией логики на уровне компании. Практически это можно решить так:
|
||
|
||
1. Брать advisory lock по company_id перед проверками и записью.
|
||
2. Либо делать SELECT ... FOR UPDATE по строке компании, если этого достаточно для вашей схемы доступа.
|
||
|
||
Для первой версии я бы выбрал advisory lock по company_id. Это проще и надёжнее для бизнес-ограничений, которые нельзя полностью выразить обычным unique index.
|
||
|
||
## 11. Экспорт агрегированного списка
|
||
|
||
### Требования
|
||
|
||
1. В экспорт попадают только активные записи.
|
||
2. Данные берутся по всем компаниям.
|
||
3. Пересечения между компаниями допустимы на уровне хранения, но в export должны агрегироваться в минимальный набор CIDR.
|
||
4. Формат ответа: text/plain.
|
||
5. Одна строка = один CIDR.
|
||
|
||
### Что нужно зафиксировать реализационно
|
||
|
||
1. Результат должен быть отсортирован для стабильности.
|
||
2. В ответе должен быть завершающий перевод строки.
|
||
3. Content-Type должен быть text/plain; charset=utf-8.
|
||
4. Желательно отдавать Content-Disposition с понятным именем файла.
|
||
|
||
### Защита endpoint
|
||
|
||
Если endpoint на старте работает без auth, то доступ надо ограничить минимум одним из способов:
|
||
|
||
1. Проверка client IP в приложении.
|
||
2. Ограничение на reverse proxy.
|
||
3. Оба сразу.
|
||
|
||
## 12. UI-потоки
|
||
|
||
### Экран клиента
|
||
|
||
Должны быть:
|
||
|
||
1. Селектор активной компании, если компаний несколько.
|
||
2. Таблица записей.
|
||
3. Индикатор использовано X из N.
|
||
4. Форма создания записи.
|
||
5. Возможность редактирования.
|
||
6. Возможность soft delete.
|
||
|
||
### Экран администратора
|
||
|
||
Должны быть:
|
||
|
||
1. Таблица по всем компаниям.
|
||
2. Фильтр по компании.
|
||
3. Фильтр показа удалённых записей.
|
||
4. Просмотр журнала аудита.
|
||
5. Управление лимитами компании.
|
||
|
||
### UX-детали, которые обязательно сделать
|
||
|
||
1. Понятные сообщения об ошибках валидации.
|
||
2. Явное сообщение о нормализации адреса.
|
||
3. Явное сообщение о превышении лимита.
|
||
4. Явное сообщение о пересечении с существующей записью.
|
||
|
||
## 13. Пошаговый план реализации
|
||
|
||
### Этап 1. Каркас проекта
|
||
|
||
1. Создать структуру каталогов.
|
||
2. Подготовить requirements.txt.
|
||
3. Подготовить .env.example.
|
||
4. Подключить FastAPI, Jinja2, static.
|
||
5. Подготовить docker-compose.yml с PostgreSQL.
|
||
|
||
Результат этапа: приложение стартует, открывается базовая страница, есть подключение к БД.
|
||
|
||
### Этап 2. Схема БД и миграции
|
||
|
||
1. Настроить Alembic.
|
||
2. Создать initial migration.
|
||
3. Поднять таблицы companies, whitelist_entries, audit_log.
|
||
4. Добавить нужные индексы.
|
||
|
||
Результат этапа: схема БД фиксирована и воспроизводима.
|
||
|
||
### Этап 3. Валидатор CIDR
|
||
|
||
1. Реализовать разбор IPv4 и CIDR.
|
||
2. Реализовать проверку маски.
|
||
3. Реализовать нормализацию.
|
||
4. Реализовать проверку запрещённых диапазонов.
|
||
5. Написать тесты на валидатор.
|
||
|
||
Результат этапа: независимый, протестированный модуль бизнес-валидации.
|
||
|
||
### Этап 4. Сервисный слой для записей
|
||
|
||
1. Реализовать list.
|
||
2. Реализовать create.
|
||
3. Реализовать update.
|
||
4. Реализовать soft delete.
|
||
5. Реализовать проверки лимитов, дубликатов и пересечений.
|
||
6. Добавить транзакционную защиту от гонок.
|
||
|
||
Результат этапа: бизнес-операции работают без UI.
|
||
|
||
### Этап 5. Аудит
|
||
|
||
1. Добавить запись CREATE.
|
||
2. Добавить запись UPDATE со старым и новым состоянием.
|
||
3. Добавить запись DELETE.
|
||
4. Добавить интерфейс чтения для admin.
|
||
|
||
Результат этапа: все изменяющие действия фиксируются.
|
||
|
||
### Этап 6. Авторизация
|
||
|
||
1. Реализовать dev-заглушку.
|
||
2. Реализовать чтение и валидацию JWT из Keycloak.
|
||
3. Реализовать преобразование claims в current user.
|
||
4. Реализовать проверки client/admin.
|
||
5. Реализовать переключение компании.
|
||
|
||
Результат этапа: права и контекст пользователя работают сквозным образом.
|
||
|
||
### Этап 7. HTML-интерфейс
|
||
|
||
1. Реализовать страницу списка.
|
||
2. Реализовать формы создания и редактирования.
|
||
3. Реализовать soft delete из UI.
|
||
4. Реализовать админские экраны.
|
||
|
||
Результат этапа: сервис пригоден для ручной эксплуатации.
|
||
|
||
### Этап 8. Экспорт
|
||
|
||
1. Реализовать сбор всех активных CIDR.
|
||
2. Реализовать агрегацию.
|
||
3. Реализовать endpoint export.
|
||
4. Реализовать сетевое ограничение.
|
||
|
||
Результат этапа: внешняя система может забирать текстовый агрегированный whitelist.
|
||
|
||
### Этап 9. Финализация
|
||
|
||
1. Написать README.
|
||
2. Подготовить Dockerfile.
|
||
3. Подготовить пример systemd unit при необходимости.
|
||
4. Прогнать ручной smoke-test.
|
||
|
||
Результат этапа: сервис можно разворачивать и передавать коллегам.
|
||
|
||
## 14. Тестовая стратегия
|
||
|
||
Минимально обязательные тесты:
|
||
|
||
1. Валидный одиночный IPv4 превращается в /32.
|
||
2. Валидная подсеть принимается.
|
||
3. Host-биты нормализуются.
|
||
4. Маски шире допустимой границы отклоняются.
|
||
5. IPv6 отклоняется.
|
||
6. Все запрещённые диапазоны отклоняются.
|
||
7. Дубликат в одной компании запрещён.
|
||
8. Пересечение в одной компании запрещено.
|
||
9. Тот же CIDR в другой компании разрешён.
|
||
10. Soft delete освобождает лимит.
|
||
11. Export не включает soft-deleted записи.
|
||
12. Export агрегирует CIDR корректно.
|
||
13. Client не видит чужие компании.
|
||
14. Admin видит все компании.
|
||
15. Аудит создаётся для create, update, delete.
|
||
|
||
## 15. Что можно отложить после первой версии
|
||
|
||
Это не нужно тащить в MVP:
|
||
|
||
1. Полноценный SPA.
|
||
2. Сложная ORM.
|
||
3. WebSocket.
|
||
4. Фоновая очередь.
|
||
5. Исторические версии записей кроме audit log.
|
||
6. Автоматическое уведомление по email.
|
||
|
||
## 16. Главные риски проекта
|
||
|
||
1. Неясный формат claims из Keycloak.
|
||
2. Гонки при одновременном создании записей.
|
||
3. Ошибки в трактовке пересечений CIDR.
|
||
4. Неправильная нормализация адресов без понятного сообщения пользователю.
|
||
5. Слишком раннее усложнение фронтенда.
|
||
|
||
## 17. Что я бы делал первым
|
||
|
||
Если начинать реализацию прямо сейчас, порядок такой:
|
||
|
||
1. Каркас проекта.
|
||
2. Схема БД.
|
||
3. Валидатор и тесты.
|
||
4. Сервис create/update/delete с транзакциями.
|
||
5. Только потом UI и Keycloak.
|
||
|
||
Это самый безопасный путь: сначала фиксируется ядро бизнес-логики, потом уже внешний слой.
|
||
|
||
## 18. Итоговое решение
|
||
|
||
За основу реализации стоит брать простой Python/FastAPI сервис с PostgreSQL, синхронной серверной логикой, жёсткой серверной валидацией, транзакционной защитой от гонок и минималистичным HTML UI.
|
||
|
||
Главная мысль: сложность здесь не во фронтенде и не в фреймворке, а в корректной реализации правил CIDR, лимитов, ролей и аудита. План должен защищать именно эти части, а не раздувать стек. |