5210ba3333c1f0eb06f0ec7a8fab48f56a453cf1
Текст описывает текущее состояние проекта (конфигурация, архитектура, правила данных, API, безопасность, эксплуатация, UI, тесты) без описаний отдельных доработок; история изменений 001-030 и отчёты ревью вынесены в таблицы со ссылками на docs/changes и docs/reviews. Исправлены устаревшие сведения: структура app/, миграции 0001-0009, команда тестов с проектом ipam_control_006, срок жизни токена. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IPAM Manager
Реестр IP-адресов и адресных префиксов в разрезе организаций. Проект построен по принципу API-first: backend на FastAPI и PostgreSQL,
UI-админка — лёгкий SPA без сборки (web/), который только отображает ответы API. Макеты — страница «IPAM Manager» дизайн-канваса ros_control.
Быстрый старт
python3 scripts/gen_env.py # .env со случайными паролями и секретами (файл в .gitignore)
docker compose up -d --build # postgres + app; миграции применяются при старте
python3 -m venv venv && venv/bin/pip install -r requirements-dev.txt
venv/bin/python scripts/seed_demo.py # по желанию: демо-данные из макетов
- UI:
http://<хост>:8088/, Swagger:http://<хост>:8088/docs. - Вход:
ADMIN_USERNAME/ADMIN_PASSWORDиз.env. Администратор создаётся при первом старте на пустой БД.
Конфигурация (.env)
| Переменная | Назначение |
|---|---|
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD |
База данных |
JWT_SECRET |
Ключ подписи токенов. Обязателен, не короче 32 символов, заглушки отклоняются: без корректного значения приложение не стартует |
ADMIN_USERNAME, ADMIN_PASSWORD |
Первый администратор; пароль не короче 8 символов |
APP_PORT |
Порт UI и API на хосте (8088) |
APP_BIND |
Адрес публикации порта: по умолчанию 0.0.0.0; 127.0.0.1 — только за reverse-proxy |
DB_HOST_PORT |
Порт PostgreSQL на хосте, публикуется только на 127.0.0.1 |
TRUSTED_PROXIES |
CIDR доверенных прокси через запятую; только от них принимается X-Forwarded-For |
Архитектура
| Слой | Технологии |
|---|---|
| API | FastAPI, pydantic v2, JWT (срок 8 ч), пароли в argon2, роли admin (запись) и viewer (чтение) |
| БД | PostgreSQL 16, SQLAlchemy 2, Alembic (миграции 0001–0009), типы CIDR/INET |
| UI | Статический SPA (vanilla JS, ES-модуль) раздаётся приложением; шрифты IBM Plex хранятся локально, внешних зависимостей нет |
app/ main.py config.py db.py security.py models.py schemas.py services.py request_context.py rotation.py
app/api/v1/ auth.py refs.py prefixes.py overview.py journal.py users.py
web/ index.html styles.css app.js fonts/
alembic/ миграции схемы
scripts/ gen_env.py seed_demo.py find_duplicate_addresses.py find_unusable_addresses.py
tests/ автотесты (pytest)
docs/changes/ планы и итоги доработок docs/reviews/ отчёты ревью
Модель данных и правила
organizations → vrfs → prefixes (дерево) → addresses; devices + device_types; isps + isp_networks; users; audit_log.
VRF и префиксы
- У каждой организации автоматически создаётся VRF
default. Имя VRF уникально в пределах организации без учёта регистра. - Префикс принадлежит VRF своей организации; это гарантирует составной FK в БД.
- Вложенность префиксов (
parent_id) определяется автоматически по CIDR. Смена VRF переносит префикс вместе с вложенными, только в пределах организации. - Автовыделение вложенного префикса: система выбирает первый свободный выровненный блок заданного размера.
- Изменения дерева одного VRF выполняются по одному (advisory-lock); разные VRF друг друга не блокируют.
Адреса
- IP уникален в пределах VRF и хранится в самом узком содержащем его префиксе.
- Адрес сети и broadcast (IPv4, префикс ≤ /30) назначить нельзя.
- Свободные адреса не хранятся, а вычисляются. В общем списке они показываются только для подсетей до /20.
Ёмкость и «Обзор»
- Ёмкость префикса — размер его подсети (для IPv4 без адреса сети и broadcast). Занятость считается по всему поддереву.
- «Обзор» учитывает только IPv4: ёмкость — сумма корневых активных префиксов, назначенные адреса — адреса внутри них.
Удаление
- Объекты с зависимыми данными не удаляются (409). Отказ фиксируется в журнале с перечнем мешающих объектов.
API (/api/v1)
| Область | Эндпоинты |
|---|---|
| Вход | POST /auth/login, GET /auth/me |
| Справочники | /organizations, /vrfs, /isps, /device-types, /devices |
| Префиксы | /prefixes, GET|POST /prefixes/{id}/subnets/next (предпросмотр и автовыделение вложенного) |
| Адреса | GET|POST /prefixes/{id}/addresses, POST /prefixes/{id}/addresses/next (автоназначение из пула), PATCH|DELETE /addresses/{id} |
| Пользователи | /users, POST /users/me/password |
| Журнал | GET /audit, /audit/summary, /audit/facets, /audit/{uid}, GET|PUT /journal/settings, POST /journal/clear |
| Сводка | GET /overview |
Соглашения
- Ошибки возвращаются в формате
{code, message, fields}; для лимитов добавляютсяretry_after_secondsиattempts_left. - Пагинация:
limit≥ 1,offsetот 0 до 10 000 000. nullв текстовом полеPATCHочищает его.- Каждое изменение данных пишется в журнал аудита.
Безопасность
Роли и учётные записи
viewerтолько читает. Раздел «Пользователи» доступен толькоadmin. Роль по умолчанию —viewer.- Логин уникален без учёта регистра.
- Свою учётную запись нельзя понизить, отключить или удалить; то же относится к последнему активному администратору.
Пароли и токены
- Смена пароля отзывает ранее выданные токены. Свой пароль меняется только с подтверждением текущего.
Вход
- Лимит неудачных попыток за 10 минут:
- 5 — на пару логин + IP;
- 20 — на IP;
- 50 — на логин со всех IP, кроме тех, с которых пользователь успешно входил за 30 дней.
- Сверх лимита — 429 с
Retry-After.
Журнал
- Хранит IP клиента и метаданные запроса.
- Ротация по сроку (90 дней) и по количеству записей (100 000); значения настраиваются.
- Очистка журнала требует пароль.
Ответы
- Заголовки CSP,
X-Frame-Options,X-Content-Type-Options,Referrer-Policy.
Публикация и эксплуатация
- Контейнер приложения работает от непривилегированного пользователя, у него есть healthcheck (
/healthz) и перезапускunless-stopped. - Миграции выполняются при старте под advisory-lock. Зависимости зафиксированы в
requirements.lock(обновление:venv/bin/pip-compile --strip-extras -o requirements.lock requirements.txt). - Приложение отдаёт HTTP. Для TLS поставьте reverse-proxy и задайте
APP_BIND=127.0.0.1иTRUSTED_PROXIES. Пример для Caddy:ipam.example.com { reverse_proxy 127.0.0.1:8088 } - Перед обновлением рабочей БД проверьте данные скриптами только для чтения:
scripts/find_duplicate_addresses.pyиscripts/find_unusable_addresses.py. - Имя compose-проекта задаётся флагом
-p. Текущий стенд поднят какipam_control_006(docker compose -p ipam_control_006 …); без флага команды работают с проектомipam_control.
Интерфейс
- Экраны: «Обзор», «Префиксы» (дерево по VRF), «Адреса» подсети, «Организации», «Операторы», «Устройства», «Журнал», «Пользователи» (только admin).
- Строка реестра кликабельна целиком. Действия над строкой — в меню «⋯».
- Групповые операции через чекбоксы (кроме «Журнала»): удаление, смена типа устройств, статус префиксов и адресов, доступ пользователей.
- Экран «Префиксы» загружает до 20 000 префиксов организации и сообщает, если загружены не все.
Тесты
Тесты работают с приложением и БД, заданными в .env, то есть с запущенным стендом: они создают и удаляют временные данные.
docker compose -p ipam_control_006 up -d --build && venv/bin/python -m pytest -q
История изменений
Каждая доработка описана в docs/changes/<номер>/: PLAN.md — план, SUMMARY.md — итог.
| № | Изменение | Документы |
|---|---|---|
| 001 | Backend и UI-админка | план · итог |
| 002 | Смена VRF у префикса, целостность «VRF ⊂ организация» | план · итог |
| 003 | Журнал: поиск, ротация, очистка | план · итог |
| 004 | Журнал: IP клиента и метаданные запроса | план · итог |
| 005 | Кликабельные строки реестров | план · итог |
| 006 | Раздел «Пользователи» | план · итог |
| 007 | Исправление удаления организации | план · итог |
| 008 | Журнал отклонённых удалений | план · итог |
| 009 | Групповые операции в UI | план · итог |
| 010 | Автовыделение вложенного префикса | план · итог |
| 011 | Уникальность IP в VRF | план · итог |
| 012 | Ограничение попыток входа | план · итог |
| 013 | Границы пагинации | план · итог |
| 014 | Производительность экрана адресов | план · итог |
| 015 | Запрет адреса сети и broadcast | план · итог |
| 016 | Роль по умолчанию — «Просмотр» | план · итог |
| 017 | Проверка секретов при старте | план · итог |
| 018 | null в PATCH |
план · итог |
| 019 | Устранение N+1 запросов | план · итог |
| 020 | Автоназначение адреса с учётом вложенных префиксов | план · итог |
| 021 | Блокировки: администраторы, регистр логина | план · итог |
| 022 | Эксплуатация: контейнер, миграции, TLS, зависимости | план · итог |
| 023 | Безопасность и журнал: мелкие улучшения | план · итог |
| 024 | Исправление находок ревью 011–023 | план · итог |
| 025 | Ёмкость частично разбитого префикса | план · итог |
| 026 | Политика блокировки входа | план · итог |
| 027 | Сериализация попыток входа по IP | план · итог |
| 028 | Экран «Префиксы» без усечения | план · итог |
| 029 | Целостность дерева префиксов | план · итог |
| 030 | Исправление замечаний ревью 025–029 | план · итог |
Отчёты ревью
- Ревью кодовой базы (находки → изменения 011–023)
- Ревью изменений 011–023 (→ 024)
- Повторный анализ кодовой базы (→ 025–029)
- Ревью и тестирование изменений 025–029 (→ 030)
- Ревью и тестирование изменения 030
Languages
Python
64.5%
JavaScript
30.1%
CSS
4.9%
HTML
0.2%
Dockerfile
0.2%
Other
0.1%