Files
IPWhiteList/docs/plan-v2.md
T

189 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План разработки — 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`. Без пересборки.