From 09ffa4c385184156df805b8b76cb75679b724aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 30 May 2026 07:22:49 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=BA=D0=B0=D0=BD=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=BD=D1=8B=D0=B9=20plan.md,=20=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=BB=D0=B0=D0=BD=D1=8B?= =?UTF-8?q?=20=E2=86=92=20[DEPRECATED]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-actual.md => [DEPRECATED]-plan-actual.md} | 0 ...-gemini.md => [DEPRECATED]-plan-gemini.md} | 0 docs/[DEPRECATED]-plan-gpt54.md | 552 ++++++++++++++++++ docs/{plan-v2.md => [DEPRECATED]-plan-v2.md} | 0 ...orking.md => [DEPRECATED]-plan-working.md} | 0 docs/plan-gpt54.md | 529 ----------------- docs/plan.md | 195 +++---- 7 files changed, 644 insertions(+), 632 deletions(-) rename docs/{plan-actual.md => [DEPRECATED]-plan-actual.md} (100%) rename docs/{plan-gemini.md => [DEPRECATED]-plan-gemini.md} (100%) create mode 100644 docs/[DEPRECATED]-plan-gpt54.md rename docs/{plan-v2.md => [DEPRECATED]-plan-v2.md} (100%) rename docs/{plan-working.md => [DEPRECATED]-plan-working.md} (100%) delete mode 100644 docs/plan-gpt54.md diff --git a/docs/plan-actual.md b/docs/[DEPRECATED]-plan-actual.md similarity index 100% rename from docs/plan-actual.md rename to docs/[DEPRECATED]-plan-actual.md diff --git a/docs/plan-gemini.md b/docs/[DEPRECATED]-plan-gemini.md similarity index 100% rename from docs/plan-gemini.md rename to docs/[DEPRECATED]-plan-gemini.md diff --git a/docs/[DEPRECATED]-plan-gpt54.md b/docs/[DEPRECATED]-plan-gpt54.md new file mode 100644 index 0000000..8f8053b --- /dev/null +++ b/docs/[DEPRECATED]-plan-gpt54.md @@ -0,0 +1,552 @@ +# Актуальный план реализации — IP WhiteList + +> Автор: GitHub Copilot (GPT-5.4) +> Дата: 2026-05-30 +> Основание: ТЗ из WhiteIPlist.txt + фактический код в ipwhitelist-app +> Статус: заменяет предыдущую версию плана + +## 1. Вывод по текущему состоянию + +Предыдущий план был неполным, потому что строился не от ТЗ, а от видимого MVP. После сверки с WhiteIPlist.txt картина такая: + +1. Основа сервиса уже написана лучше, чем казалось: схема БД, soft delete, аудит, custom_limit, updateEntry и полный список запрещённых диапазонов уже есть в коде. +2. Основной разрыв находится не в модели данных, а между слоями: часть функций реализована в src/queries.js, но не выведена в server.js и views. +3. Главные недостающие вещи для соответствия ТЗ: нормальная OIDC-авторизация, admin-сценарии, редактирование из UI, клиентская валидация, агрегирующий экспорт. +4. Переписывать стек или БД не нужно. Нужно довести существующую реализацию до полноты требований. + +## 2. Что требует ТЗ + +Сервис по ТЗ обязан поддерживать: + +1. Self-service страницу управления whitelist-записями. +2. Авторизацию через Keycloak OIDC. +3. Роли client и admin. +4. Возможность работы пользователя с одной или несколькими компаниями. +5. Создание, редактирование и soft delete записей. +6. Серверную и клиентскую валидацию IPv4/CIDR. +7. Глобальный лимит и индивидуальные лимиты по компаниям. +8. Журнал аудита. +9. Внешний endpoint выдачи агрегированного списка активных CIDR. +10. Admin-функции: просмотр всех компаний, фильтрация, просмотр удалённых записей, изменение лимитов. + +## 3. Что уже реализовано сейчас + +### 3.1 База данных + +В sql/schema.sql уже есть всё базовое, что нужно для ТЗ: + +1. Таблица companies с client_id и custom_limit. +2. Таблица whitelist_entries с updated_by, updated_at, deleted_by, deleted_at. +3. Таблица audit_log. +4. Индекс активных записей и индекс по audit_log(company_id). + +Вывод: схему БД переписывать не нужно. + +### 3.2 Серверная логика + +В src/queries.js уже реализованы: + +1. getOrCreateCompany +2. getLimit +3. listEntries с includeDeleted +4. createEntry +5. updateEntry +6. deleteEntry как soft delete +7. getExportCIDRs +8. getAudit + +Вывод: слой работы с БД уже покрывает значительную часть ТЗ. + +### 3.3 Валидация + +В src/validators.js уже есть: + +1. Только IPv4. +2. Маски только /22–/32. +3. Нормализация host-битов. +4. Запрет всех диапазонов из Приложения А ТЗ, включая CGNAT, Benchmarking, Multicast, Reserved и Limited broadcast. +5. Проверка пересечений. + +Вывод: серверная валидация по составу требований почти полная. + +### 3.4 UI и маршруты + +В server.js и views/index.ejs уже есть: + +1. Главная страница со списком записей. +2. Форма добавления. +3. Soft delete через POST /delete/:id. +4. Экспорт через GET /export. +5. Вывод текущего лимита и числа использованных записей. +6. UI, близкий к стилю Nubes. + +Вывод: клиентский сценарий создания и удаления уже работает как MVP. + +## 4. Что не соответствует ТЗ или не доведено до конца + +### 4.1 Авторизация + +Сейчас в server.js не OIDC, а упрощённый decode токена через base64 без проверки подписи. Это подходит только как временная заглушка, но не соответствует ТЗ. + +Нужно: + +1. Реальная проверка JWT через Keycloak/JWKS или openid-client. +2. Нормальный маппинг claims в user-модель. +3. Явное определение роли admin. +4. Поддержка сценария с несколькими компаниями. + +Это главный блокер продакшна. + +### 4.2 Редактирование записи + +updateEntry уже написана, но: + +1. Нет роута POST /update/:id. +2. Нет формы редактирования в index.ejs. +3. Нет пользовательского сценария изменения записи. + +То есть требование ТЗ формально не закрыто, хотя код на уровне queries уже есть. + +### 4.3 Admin-функции + +По ТЗ администратор должен: + +1. Видеть записи всех компаний. +2. Фильтровать по компании. +3. Видеть soft-deleted записи. +4. Смотреть аудит. +5. Менять лимиты компаний. + +Сейчас ничего из этого не выведено в server.js и views. + +### 4.4 Экспорт + +GET /export уже есть, но он отдаёт просто список value_cidr без агрегации. ТЗ требует суммаризацию в минимальный набор CIDR по всем активным записям. + +Это функциональный разрыв, а не косметика. + +### 4.5 Клиентская валидация + +ТЗ требует валидацию на клиенте и сервере. Сейчас есть только серверная. + +Нужно добавить в форму как минимум: + +1. Проверку формата IPv4/CIDR. +2. Проверку диапазона маски. +3. Ограничение длины комментария. +4. Сообщение о возможной нормализации. + +### 4.6 Multi-company сценарий + +ТЗ явно говорит, что пользователь может принадлежать нескольким компаниям. Сейчас всё построено вокруг одного clientId в req.user. + +Нужно: + +1. Понять реальный формат claims. +2. Ввести activeCompany в контекст пользователя. +3. Добавить переключатель активной компании в UI. + +## 5. Что не надо перепридумывать + +1. Не надо переписывать проект на другой язык или другой фреймворк. +2. Не надо менять схему БД ради самой схемы. +3. Не надо переписывать validators.js целиком. +4. Не надо переписывать queries.js целиком. +5. Не надо делать SPA. + +Правильный путь: минимально нарастить уже существующую Node.js/Express/EJS реализацию. + +## 6. Риски и спорные места + +### 6.1 overlaps в validators.js + +Условие в overlaps написано нестандартно и плохо читается. Прямого доказанного бага по одной только формуле сейчас нет, но это место требует отдельного теста на: + +1. полное совпадение, +2. вложенность, +3. непересекающиеся диапазоны, +4. соседние диапазоны, +5. одиночный IP против подсети. + +Решение: сначала добавить точечные тесты, и только потом менять формулу, если тест покажет дефект. + +### 6.2 Admin-роль + +ТЗ говорит: admin это clientId = WZ01112 и отдельный чек-бокс. Сейчас неясно, как этот чек-бокс попадает в токен. Без этого нельзя финально закрыть auth-модель. + +### 6.3 Multi-company claims + +ТЗ требует сценарий нескольких компаний, но в списке claims указан только clientID. Здесь нужна конкретика от команды Keycloak. + +## 7. Новый план реализации + +### Этап 1. Довести до полноты пользовательский сценарий + +Цель: закрыть основной client-flow без смены архитектуры. + +1. Добавить роут POST /update/:id в server.js. +2. Добавить UI редактирования в views/index.ejs. +3. Показать updated_at и updated_by, если запись менялась. +4. Добавить клиентскую валидацию формы добавления и редактирования. +5. Добавить точечные тесты на overlaps и нормализацию. + +Результат этапа: client сможет не только добавлять и удалять, но и редактировать записи, как требует ТЗ. + +### Этап 2. Закрыть admin-функциональность + +Цель: реализовать недостающую управленческую часть ТЗ. + +1. Ввести определение роли admin в auth-слое. +2. Добавить GET /admin. +3. Добавить фильтр по компании. +4. Добавить показ удалённых записей. +5. Добавить GET /admin/audit. +6. Добавить POST /admin/limit/:companyId. +7. Добавить отдельный admin view. + +Результат этапа: появляется реальная административная панель, а не только клиентский экран. + +### Этап 3. Привести авторизацию к ТЗ + +Цель: убрать временную заглушку и сделать реальную интеграцию с Keycloak. + +1. Заменить наивный decode токена на верификацию подписи. +2. Добавить конфигурацию issuer, audience, jwks/oidc. +3. Нормализовать claims в req.user. +4. Поддержать admin-claim. +5. Поддержать multi-company claims. +6. Оставить DEV_MODE только для локальной разработки. + +Результат этапа: сервис можно выводить из чисто dev-сценария. + +### Этап 4. Довести экспорт до требований ТЗ + +Цель: сделать экспорт пригодным для систем фильтрации. + +1. Добавить суммаризацию активных записей в минимальный набор CIDR. +2. Суммаризировать по всем компаниям совместно. +3. Исключать soft-deleted записи. +4. Оставить выдачу text/plain, одна строка на объект. +5. Отдельно решить, где ограничивается доступ к export endpoint: ingress, app или оба уровня. + +Результат этапа: экспорт соответствует ТЗ, а не является просто дампом таблицы. + +### Этап 5. Завершение и проверка полноты + +1. Сверить все пункты ТЗ с реализованным поведением. +2. Обновить тестовый сценарий. +3. Проверить UX ошибок и предупреждений. +4. Проверить поведение при снижении custom_limit ниже текущего числа активных записей. +5. Проверить сценарии admin/client отдельно. + +## 8. Практический приоритет + +Если делать не всё сразу, а по реальной важности, порядок такой: + +1. Редактирование записи из UI. +2. Клиентская валидация. +3. Admin-панель и лимиты. +4. Реальный OIDC. +5. Multi-company. +6. Суммаризация export. + +Почему именно так: + +1. Редактирование уже почти готово и закрывает явный пробел ТЗ. +2. Admin-функции сейчас отсутствуют полностью. +3. OIDC блокирует продакшн, но не мешает локально добить функциональность. +4. Экспорт уже работает как черновой endpoint, но должен быть доведён до суммаризации до релиза. + +## 9. Итог + +Правильный план для этого проекта не “переписать всё правильно”, а “довести уже написанное до требований ТЗ”. + +Текущее состояние проекта: + +1. Data-layer в основном готов. +2. Server-layer частично готов. +3. UI-layer закрывает только часть client-сценария. +4. Auth-layer пока временный. +5. Admin-layer почти отсутствует. +6. Export-layer не завершён по требованиям агрегации. + +Главный вывод: проект ближе к рабочему состоянию, чем казалось по старым планам, но прошлый план был методологически неверен, потому что не опирался на ТЗ и не различал “не написано” и “написано, но не подключено”. + +Эта часть критична. Её нужно делать одной из первых и сразу покрывать тестами. + +### Поддерживаемый ввод + +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/[DEPRECATED]-plan-v2.md similarity index 100% rename from docs/plan-v2.md rename to docs/[DEPRECATED]-plan-v2.md diff --git a/docs/plan-working.md b/docs/[DEPRECATED]-plan-working.md similarity index 100% rename from docs/plan-working.md rename to docs/[DEPRECATED]-plan-working.md diff --git a/docs/plan-gpt54.md b/docs/plan-gpt54.md deleted file mode 100644 index 58caaf6..0000000 --- a/docs/plan-gpt54.md +++ /dev/null @@ -1,529 +0,0 @@ -# Полный план реализации — 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.md b/docs/plan.md index 5fd3e8b..f2c24c4 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -1,118 +1,107 @@ -# ~~План разработки — IP WhiteList Microservice~~ [LEGACY] +# IP WhiteList — План разработки -> ⚠️ **УСТАРЕЛО.** Этот план содержит ошибки (async ORM, неполные требования, отсутствие тестов). -> Актуальный план: `plan-v2.md` - -> **Автор:** GitHub Copilot (DeepSeek V4 Flash) -> **Дата:** 2026-05-29 +> **Дата:** 2026-05-30 +> **Основание:** [WhiteIPlist.txt](../WhiteIPlist.txt) (ТЗ) + фактический код в [ipwhitelist-app](../../ipwhitelist-app) +> **Статус:** каноничный — единственный актуальный план --- -## Стек +## 1. Текущее состояние -| Слой | Технология | -|---|---| -| Бэкенд | Python 3.11+ / FastAPI | -| БД | PostgreSQL | -| ORM | SQLAlchemy (async) + Alembic (миграции) | -| Фронтенд | Jinja2 + HTMX + минимальный CSS | -| Авторизация | Keycloak OIDC (на старте — заглушка/мок) | -| Валидация | Pydantic + встроенный `ipaddress` | +### Готово ✅ + +| Слой | Файлы | Что есть | +|---|---|---| +| БД | `sql/schema.sql` | `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы | +| Валидатор | `src/validators.js` | Все 14 запрещённых диапазонов Приложения А, /22–/32, нормализация host-битов, проверка пересечений | +| CRUD | `src/queries.js` | `createEntry`, `updateEntry`, `deleteEntry` (soft), `listEntries`, `getExportCIDRs`, `getAudit`, `getLimit` | +| UI | `views/index.ejs` | Список, форма добавления, кнопка удаления, стиль Nubes | +| Роуты | `server.js` | `GET /`, `POST /add`, `POST /delete/:id`, `GET /export`, `GET /healthz` | + +### Написано в queries.js, но не подключено к роутам + +- `updateEntry` — нет `POST /update/:id`, нет UI редактирования +- `getAudit` — нет страницы аудита +- `listEntries(companyId, includeDeleted=true)` — флаг есть, не используется --- -## Этапы +## 2. Что требует ТЗ, но отсутствует -### Этап 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 или ручная инструкция +| # | Требование | Готовность | +|---|---|---| +| 1 | Редактирование записи из UI | ❌ queries есть, роута нет | +| 2 | Админ-панель (все компании, лимиты, аудит, фильтры) | ❌ | +| 3 | OIDC Keycloak вместо base64-заглушки | ❌ | +| 4 | Суммаризация CIDR в `/export` | ❌ отдаёт сырой список | +| 5 | Клиентская валидация (JS в форме) | ❌ | +| 6 | Переключатель компаний (multi-company) | ❌ | +| 7 | Просмотр soft-deleted записей админом | ❌ | +| 8 | Изменение `custom_limit` для компании | ❌ | --- -## Файловая структура (план) +## 3. Порядок реализации + +### Этап 1 — Пользовательский сценарий (client-flow) + +- [x] Валидатор IPv4/CIDR — полный +- [x] Создание / удаление / экспорт +- [ ] **Добавить `POST /update/:id`** в `server.js` +- [ ] **Добавить inline-форму редактирования** в `views/index.ejs` +- [ ] **Клиентская валидация** (JS: формат, маска, длина комментария) + +### Этап 2 — Административная панель + +- [ ] Определение admin-роли (`clientId === 'WZ01112'` + чекбокс) +- [ ] `GET /admin` — страница со всеми компаниями +- [ ] `POST /admin/limit/:companyId` — изменение custom_limit +- [ ] `GET /admin/audit` — журнал аудита +- [ ] Фильтр по компании + показ удалённых записей + +### Этап 3 — Авторизация Keycloak OIDC + +- [ ] `npm install openid-client` +- [ ] Замена base64-decode на проверку подписи JWT +- [ ] Маппинг claims → `req.user` (clientId, email, role) +- [ ] Оставить `DEV_MODE` только для локальной разработки + +### Этап 4 — Экспорт и Multi-company + +- [ ] `npm install cidr-tools` — суммаризация в `GET /export` +- [ ] Переключатель активной компании (если несколько `clientId` в claims) + +### Этап 5 — Завершение + +- [ ] Автотесты (`jest` + `supertest`) +- [ ] Пагинация (если лимит > 50) +- [ ] Сверка всех пунктов ТЗ + +--- + +## 4. Что НЕ делать + +- ❌ Не переписывать на Python/FastAPI +- ❌ Не менять схему БД +- ❌ Не переписывать `validators.js` (он полный) +- ❌ Не переписывать `queries.js` +- ❌ Не делать SPA + +--- + +## 5. Зависимости для установки ``` -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 +npm install cidr-tools openid-client +npm install --save-dev jest supertest ``` + +Всё остальное — в рамках Express + EJS + pg. + +--- + +## 6. Риски + +- **Admin-роль:** неясно как «отдельный чек-бокс» из ТЗ попадает в токен — требует уточнения с командой Keycloak +- **Multi-company claims:** ТЗ говорит о нескольких компаниях, но в claims только `clientID` — нужен реальный формат +- **IP-ограничение `/export`:** делать в приложении или на уровне ingress — решить при деплое