# План разработки — 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`. Без пересборки.