commit 65de34ba4ad2e26a1f1f8deab9d80e73c401fa79 Author: “Naeel” Date: Fri May 29 19:54:41 2026 +0300 init: ТЗ, правила copilot, планы от моделей (DeepSeek, Claude, GPT-5.4, Gemini) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..9338e3b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,27 @@ +# IPWhiteList Copilot Instructions + +## Главное правило +**НЕ ПЕРЕУСЛОЖНЯТЬ.** Это CRUD-сервис на 3 таблицы. Минимум абстракций, минимум папок, минимум зависимостей. + +## Коммуникация +- Продвигаемся вместе шаг за шагом +- ВСЕГДА спрашивать если что-то неясно — не додумывать, не фантазировать +- **НЕ ВРАТЬ.** Если не знаешь — скажи «не знаю» +- Пользователь объяснит устройство личного кабинета облака, Keycloak и т.д. — не придумывать + +## Стек (согласован) +- Python 3.11+ / FastAPI +- PostgreSQL +- Jinja2 + простые HTML-формы (без SPA, без тяжёлого JS) +- Минимум зависимостей + +## Принципы разработки +- Не плодить слои (services/, repositories/, routers/ отдельно) без реальной необходимости +- Сначала ядро (валидация, CRUD), потом UI, потом Keycloak +- Никаких ORM без явного запроса пользователя +- Никакого async без реальной необходимости +- Любое изменение кода — только после явного «делай» от пользователя + +## Процесс +- Сначала уточнить непонятное → предложить простой шаг → сделать → повторить +- Не пытаться сделать всё сразу diff --git a/WhiteIPlist.docx b/WhiteIPlist.docx new file mode 100755 index 0000000..12e8fc6 Binary files /dev/null and b/WhiteIPlist.docx differ diff --git a/docs/plan-gemini.md b/docs/plan-gemini.md new file mode 100644 index 0000000..b1c0839 --- /dev/null +++ b/docs/plan-gemini.md @@ -0,0 +1,86 @@ +# План разработки — IP WhiteList Microservice + +> **Автор:** GitHub Copilot (Gemini 3.1 Pro Preview) +> **Дата:** 2026-05-29 + +--- + +## 1. Анализ предыдущих планов и моё видение + +Я изучил ТЗ и варианты коллег (DeepSeek, Claude, GPT-5.4). +- **DeepSeek** предложил избыточный Async/ORM подход. +- **Claude** упростил до psycopg2 и HTMX, но оставил много "белых пятен" в работе с Keycloak. +- **GPT-5.4** дал отличный продуктовый разбор рисков (гонки, лимиты, нормализация), но оставил проект заблокированным до "уточнения с командой Keycloak". + +**Мой подход (Gemini 3.1 Pro):** +Мы не будем блокировать разработку в ожидании ответов от админов Keycloak. Разночтения с форматами claims (как выглядит админ, как выглядит мульти-аккаунт) мы вынесем в **гибкую конфигурацию (.env)**. Если формат токена изменится, нам не придется править код, мы просто поменяем переменные окружения. +Также мы откажемся от сторонней библиотеки `netaddr`, так как встроенный модуль Python `ipaddress` имеет встроенную функцию `collapse_addresses()`, которая идеально решает задачу агрегации по ТЗ. + +--- + +## 2. Технологический стек + +* **Бэкенд:** FastAPI (Python 3.11+). Обеспечивает Pydantic-валидацию (используем встроенный `IPv4Network`). +* **СУБД:** PostgreSQL. +* **Доступ к данным:** SQLModel (надстройка над SQLAlchemy). Дает удобство ORM без избыточной сложности, синхронный режим. +* **Суммаризация (Агрегация):** Standard Library Python `ipaddress.collapse_addresses`. +* **Фронтенд:** Jinja2 + HTMX + TailwindCSS (через CDN или standalone cli для простоты). +* **Авторизация:** Dependency injection в FastAPI для OIDC/JWT. Заглушка (MockOIDC) для локальной разработки. + +--- + +## 3. Решение узких мест (Архитектурные решения) + +**Проблема 1: Как определять администратора и принадлежность к компаниям из токена?** +*Решение:* Выносим структуру токена в `Config`. +```env +OIDC_COMPANY_CLAIM="client_id" # Может быть списком или строкой, обработаем оба варианта +OIDC_ADMIN_CLAIM_KEY="roles" +OIDC_ADMIN_CLAIM_VALUE="whitelist-admin" +``` +Код будет динамически проверять, совпали ли значения, указанные в конфиге, с данными из токена. + +**Проблема 2: Гонки при записи и лимиты** +*Решение:* +1. Проверка лимита делается запросом `SELECT count(*) FROM entries WHERE company_id = X AND deleted_at IS NULL FOR UPDATE`. Блокировка на чтение защитит транзакцию от гонок. +2. В БД создадим уникальный индекс `CREATE UNIQUE INDEX unique_active_cidr ON entries (company_id, value_cidr) WHERE deleted_at IS NULL;` для защиты от дубликатов на уровне СУБД. + +**Проблема 3: Пересечения внутри компании** +*Решение:* Перед INSERT/UPDATE выгружаем все активные подсети компании и проверяем через `new_cidr.overlaps(existing_cidr)`. Выгрузка делается в рамках заблокированной транзакции (см. пункт выше). + +--- + +## 4. Поэтапный план реализации + +### Этап 1. Ядро и База данных (Бизнес-логика) +- [ ] Инициализация FastAPI проекта, настройка SQLModel. +- [ ] Определение сущностей БД: `Company`, `WhitelistEntry` (CIDR хранится как `String`, но Pydantic проверяет `IPv4Network`), `AuditLog`. +- [ ] Валидаторы (запрещенные списки Приложения А, маска /32 - /22). Нормализация `strict=False` в `ipaddress`, чтобы `192.168.1.5/24` автоматически перегонялось в `192.168.1.0/24`. +- [ ] Написание Unit-тестов для валидаторов. + +### Этап 2. Слой данных (CRUD) и защита от гонок +- [ ] Сервис создания записи: проверка макс. лимита (15 по умолчанию или `company.custom_limit`), поиск пересечений, запись AuditLog. +- [ ] Сервис Soft-Delete и редактирования. +- [ ] Тесты CRUD-сервисов. + +### Этап 3. Авторизация (Keycloak) +- [ ] Настройка `auth/jwt.py` для валидации RS256 подписей. +- [ ] Парсинг токена на основе гибких правил из `.env` (роли, список компаний). +- [ ] FastAPI Security Depends (`get_current_user`). + +### Этап 4. Внешний API (Export Endpoint) +- [ ] Роут `GET /api/v1/export/whitelist.txt`. +- [ ] Выборка всех `value_cidr` где `deleted_at IS NULL`. +- [ ] Агрегация: `[str(net) for net in ipaddress.collapse_addresses(net_list)]`. +- [ ] Middleware для ограничения доступа по списку разрешенных `EXPORT_ALLOWED_IPS`. + +### Этап 5. Пользовательский Интерфейс (UI) +- [ ] Jinja2 шаблоны и использование HTMX для добавления/удаления строк таблицы без перезагрузки всей страницы. +- [ ] Отображение предупреждений (нормализация, превышение лимита). +- [ ] Селектор активной компании. +- [ ] Панель администратора (все компании, настройка `custom_limit`, просмотр аудита). + +### Этап 6. Инфраструктура +- [ ] Dockerfile. +- [ ] docker-compose окружение (App + Postgres). +- [ ] Документация (README с описанием переменных окружения). \ No newline at end of file diff --git a/docs/plan-gpt54.md b/docs/plan-gpt54.md new file mode 100644 index 0000000..58caaf6 --- /dev/null +++ b/docs/plan-gpt54.md @@ -0,0 +1,529 @@ +# Полный план реализации — 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, лимитов, ролей и аудита. План должен защищать именно эти части, а не раздувать стек. \ No newline at end of file diff --git a/docs/plan-v2.md b/docs/plan-v2.md new file mode 100644 index 0000000..1f52b11 --- /dev/null +++ b/docs/plan-v2.md @@ -0,0 +1,188 @@ +# План разработки — IP WhiteList Microservice v2 + +> **Автор:** GitHub Copilot (Claude Sonnet 4.6) +> **Дата:** 2026-05-29 + +--- + +## Стек + +| Слой | Технология | Обоснование | +|---|---|---| +| Бэкенд | Python 3.11+ / FastAPI | Коллеги знают Python, авто-документация | +| БД | PostgreSQL | Надёжно, поддерживает аудит и сложные запросы | +| Работа с БД | psycopg2 + сырой SQL | Проще чем ORM, понятно всем, никакой магии | +| Миграции | Alembic | Только для версионирования схемы | +| Фронтенд | Jinja2 + обычные HTML-формы | Без JS-фреймворков, минимум зависимостей | +| Авторизация | Keycloak OIDC (JWT) | Заглушка только в dev через `.env` флаг | +| IP-логика | stdlib `ipaddress` + `netaddr` | Суммаризация CIDR через `netaddr` | + +--- + +## Открытые вопросы (нужно прояснить до кодирования) + +1. **Чекбокс администратора** — ТЗ: admin = `clientId == WZ01112` + «отдельный чек-бокс». Что это: отдельный claim в Keycloak-токене (`is_admin: true`)? Роль? Нужно уточнить у команды Keycloak. +2. **Создание Company в БД** — когда появляется запись: при первом входе пользователя автоматически, или администратор заводит вручную? +3. **Кто потребляет внешний endpoint** — endpoint без авторизации, доступ по IP. Список доверенных IP задаётся конфигом? Nginx ACL? + +--- + +## Файловая структура + +``` +IPWhiteList/ +├── app/ +│ ├── main.py # FastAPI app, роутеры, startup +│ ├── config.py # Настройки из .env (DEFAULT_LIMIT, DEV_MODE, DB_DSN и др.) +│ ├── db.py # psycopg2 connection pool +│ ├── models/ +│ │ └── sql.py # DDL-схема (только для документации, не ORM) +│ ├── validators.py # IPv4/CIDR: формат, маска, серые адреса, нормализация +│ ├── crud/ +│ │ ├── entries.py # CRUD whitelist_entries +│ │ ├── companies.py # Компании и лимиты +│ │ └── audit.py # Запись в audit_log +│ ├── auth/ +│ │ ├── oidc.py # Валидация JWT Keycloak +│ │ ├── stub.py # Dev-заглушка (только при DEV_MODE=true) +│ │ └── deps.py # FastAPI Depends: current_user +│ ├── routers/ +│ │ ├── entries.py # CRUD UI-роуты + HTMX-фрагменты +│ │ ├── admin.py # Аудит, лимиты (только admin) +│ │ └── external.py # GET /api/v1/export — txt-файл +│ └── cidr_utils.py # Суммаризация через netaddr +├── templates/ +│ ├── base.html +│ ├── index.html # Таблица записей + индикатор лимита +│ ├── partials/ +│ │ ├── table.html # HTMX-фрагмент таблицы +│ │ └── form.html # Форма создания/редактирования +│ └── admin/ +│ ├── audit.html +│ └── limits.html +├── static/ +│ └── style.css +├── migrations/ +│ ├── env.py +│ └── versions/ +├── tests/ +│ ├── test_validators.py # Юниты для IPv4-валидации (критично!) +│ └── test_crud.py +├── docs/ +│ ├── plan.md # LEGACY +│ ├── plan-v2.md # Этот файл +│ └── WhiteIPlist.docx # Исходное ТЗ +├── .env.example +├── alembic.ini +├── docker-compose.yml # PostgreSQL для dev +├── requirements.txt +└── README.md +``` + +--- + +## Схема БД + +```sql +-- Компании (создаются автоматически при первом входе или вручную админом — уточнить) +CREATE TABLE companies ( + id SERIAL PRIMARY KEY, + client_id VARCHAR(64) UNIQUE NOT NULL, -- из Keycloak claim + name VARCHAR(255), + custom_limit INTEGER DEFAULT NULL -- NULL = использовать глобальный DEFAULT_LIMIT +); + +-- Whitelist-записи +CREATE TABLE whitelist_entries ( + id SERIAL PRIMARY KEY, + company_id INTEGER NOT NULL REFERENCES companies(id), + value CIDR NOT NULL, -- нормализованный CIDR + comment VARCHAR(255), + created_by VARCHAR(255) NOT NULL, -- email из токена + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_by VARCHAR(255), + updated_at TIMESTAMPTZ, + deleted_by VARCHAR(255), + deleted_at TIMESTAMPTZ -- NULL = активная запись +); + +-- Аудит (только append, без UPDATE/DELETE) +CREATE TABLE audit_log ( + id SERIAL PRIMARY KEY, + user_email VARCHAR(255) NOT NULL, + company_id INTEGER NOT NULL, + action VARCHAR(32) NOT NULL, -- CREATE | UPDATE | DELETE + old_value TEXT, + new_value TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Индексы +CREATE INDEX ON whitelist_entries(company_id) WHERE deleted_at IS NULL; +CREATE INDEX ON audit_log(company_id); +``` + +--- + +## Этапы + +### Этап 1 — Каркас + конфиг +- [ ] Структура папок +- [ ] `requirements.txt`: fastapi, uvicorn, psycopg2-binary, alembic, jinja2, python-jose, netaddr +- [ ] `.env.example` со всеми переменными: `DB_DSN`, `DEFAULT_LIMIT=15`, `DEV_MODE=false`, `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_CLIENT_ID`, `ALLOWED_EXPORT_IPS` +- [ ] `config.py` — читает `.env`, все параметры типизированы +- [ ] `db.py` — psycopg2 connection pool (SimpleConnectionPool) +- [ ] `docker-compose.yml` с PostgreSQL + +### Этап 2 — Миграции (схема БД) +- [ ] Alembic init +- [ ] Initial migration: `companies`, `whitelist_entries`, `audit_log` + индексы +- [ ] Проверка `alembic upgrade head` + +### Этап 3 — Валидатор IPv4 (с тестами) +- [ ] `validators.py`: принимает строку → возвращает нормализованный CIDR или ошибку +- [ ] Проверка формата: одиночный IP или CIDR +- [ ] Проверка маски: /22 – /32 (шире /21 — `ValidationError`) +- [ ] Нормализация host-битов: `192.168.1.5/24` → `192.168.1.0/24` + флаг `was_normalized=True` +- [ ] Запрет серых диапазонов (все из Приложения А ТЗ) +- [ ] `tests/test_validators.py` — покрыть все граничные случаи + +### Этап 4 — CRUD-логика +- [ ] `crud/companies.py`: get_or_create по client_id, get_limit (custom_limit ?? DEFAULT_LIMIT) +- [ ] `crud/entries.py`: список активных, создание (лимит + дубликаты + пересечения), редактирование, soft-delete +- [ ] `crud/audit.py`: append-only запись + +### Этап 5 — Авторизация +- [ ] `auth/oidc.py` — валидация JWT через JWKS Keycloak, извлечение `clientID`, `email`, определение роли +- [ ] Логика роли admin: `clientID == WZ01112` + (claim `is_admin == true` — **уточнить**) +- [ ] `auth/stub.py` — только при `DEV_MODE=true`: читает `X-Dev-User` из заголовка +- [ ] `auth/deps.py` — `Depends(current_user)` для роутеров + +### Этап 6 — Роутеры + UI +- [ ] `routers/entries.py`: список, форма создания, форма редактирования, удаление, переключатель компании +- [ ] `routers/admin.py`: журнал аудита, управление лимитами +- [ ] Шаблоны Jinja2: base.html, index.html, form.html, admin/audit.html, admin/limits.html +- [ ] Индикатор лимита «X из N» на странице +- [ ] Уведомление о нормализации адреса пользователю + +### Этап 7 — Внешний endpoint +- [ ] `GET /api/v1/export` — только активные записи всех компаний +- [ ] Суммаризация через `netaddr.cidr_merge()` +- [ ] Ответ: `text/plain`, одна строка — один CIDR +- [ ] IP-фильтр из `ALLOWED_EXPORT_IPS` (middleware или Depends) + +### Этап 8 — Деплой +- [ ] `Dockerfile` (python:3.11-slim, uvicorn) +- [ ] Systemd unit как альтернатива +- [ ] Nginx конфиг: reverse proxy + location для static +- [ ] README: как поднять с нуля + +--- + +## Ключевые принципы + +- **Синхронный код везде** — никакого async/await. FastAPI поддерживает синхронные роутеры. +- **Серверная валидация — авторитетная**. Клиентская — только UX. +- **`DEV_MODE=true`** — единственный способ обойти Keycloak. В prod недоступен. +- **Audit log — append only**. Никаких UPDATE/DELETE в `audit_log`. +- **Лимит `DEFAULT_LIMIT`** — всегда из `config.py`, который читает `.env`. Без пересборки. diff --git a/docs/plan.md b/docs/plan.md new file mode 100644 index 0000000..5fd3e8b --- /dev/null +++ b/docs/plan.md @@ -0,0 +1,118 @@ +# ~~План разработки — IP WhiteList Microservice~~ [LEGACY] + +> ⚠️ **УСТАРЕЛО.** Этот план содержит ошибки (async ORM, неполные требования, отсутствие тестов). +> Актуальный план: `plan-v2.md` + +> **Автор:** GitHub Copilot (DeepSeek V4 Flash) +> **Дата:** 2026-05-29 + +--- + +## Стек + +| Слой | Технология | +|---|---| +| Бэкенд | Python 3.11+ / FastAPI | +| БД | PostgreSQL | +| ORM | SQLAlchemy (async) + Alembic (миграции) | +| Фронтенд | Jinja2 + HTMX + минимальный CSS | +| Авторизация | Keycloak OIDC (на старте — заглушка/мок) | +| Валидация | Pydantic + встроенный `ipaddress` | + +--- + +## Этапы + +### Этап 1 — Каркас проекта +- [ ] Структура проекта: `app/`, `templates/`, `static/`, `migrations/` +- [ ] `requirements.txt` (FastAPI, SQLAlchemy, asyncpg, Alembic, Jinja2, python-keycloak) +- [ ] Конфигурация (`.env`, `config.py`) +- [ ] `docker-compose.yml` с PostgreSQL + +### Этап 2 — Модели БД и миграции +- [ ] Модель `Company` (id, clientId, name, individual_limit) +- [ ] Модель `WhitelistEntry` (id, company_id, value, comment, created_by, created_at, updated_at, deleted_at, deleted_by) +- [ ] Модель `AuditLog` (id, user_email, company_id, action, old_value, new_value, timestamp) +- [ ] Alembic initial migration + +### Этап 3 — Валидация IPv4 +- [ ] Валидатор: одиночный IPv4 / CIDR +- [ ] Проверка маски: /32 – /22 (шире /21 — отказ) +- [ ] Нормализация host-битов в 0 +- [ ] Запрет серых/приватных диапазонов (Приложение А из ТЗ) +- [ ] Проверка дубликатов и пересечений в пределах компании + +### Этап 4 — CRUD + Бизнес-логика +- [ ] Создание записи (с проверкой лимита) +- [ ] Просмотр таблицы записей (для клиента — свои компании, для админа — все) +- [ ] Редактирование (с повторной валидацией) +- [ ] Soft delete (deleted_at, deleted_by) +- [ ] Лимиты: глобальный default 15, индивидуальный per-company + +### Этап 5 — Аудит +- [ ] Запись всех изменяющих операций в `AuditLog` +- [ ] Просмотр журнала (только админ) + +### Этап 6 — Внешний endpoint +- [ ] `GET /api/v1/whitelist/aggregated` — txt-файл +- [ ] Суммаризация (агрегация) CIDR всех компаний +- [ ] Только активные (не soft-deleted) записи + +### Этап 7 — Авторизация (заглушка → Keycloak) +- [ ] Заглушка: header `X-Client-ID`, `X-User-Email`, `X-Role` +- [ ] Роли: client / admin +- [ ] Переключатель компаний (для пользователей в нескольких компаниях) +- [ ] Позже: полноценный OIDC через Keycloak + +### Этап 8 — UI (Jinja2 + HTMX) +- [ ] Страница входа / редирект на Keycloak +- [ ] Таблица записей с фильтрами +- [ ] Форма создания/редактирования (с клиентской валидацией) +- [ ] Индикатор лимита: «использовано X из N» +- [ ] Админка: фильтр по компаниям, просмотр удалённых, журнал аудита + +### Этап 9 — Деплой +- [ ] Systemd unit / Dockerfile +- [ ] Nginx reverse proxy (если нужно) +- [ ] CI/CD или ручная инструкция + +--- + +## Файловая структура (план) + +``` +IPWhiteList/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI app +│ ├── config.py # Настройки из .env +│ ├── models.py # SQLAlchemy модели +│ ├── schemas.py # Pydantic схемы +│ ├── validators.py # IPv4/CIDR валидация +│ ├── crud.py # CRUD-операции +│ ├── auth.py # Авторизация (заглушка → Keycloak) +│ ├── routers/ +│ │ ├── __init__.py +│ │ ├── entries.py # CRUD whitelist +│ │ ├── admin.py # Админка +│ │ └── external.py # Внешний endpoint +│ └── utils.py # Суммаризация CIDR, лимиты +├── templates/ +│ ├── base.html +│ ├── index.html # Таблица записей +│ ├── entry_form.html # Форма создания/редактирования +│ └── admin/ +│ ├── audit.html # Журнал аудита +│ └── limits.html # Управление лимитами +├── static/ +│ └── style.css +├── migrations/ +│ └── alembic/ +├── docs/ +│ ├── plan.md # Этот файл +│ └── WhiteIPlist.docx # Исходное ТЗ +├── .env.example +├── docker-compose.yml +├── requirements.txt +└── README.md +```