ayurishchevandClaude Opus 5.5 9301069c29 Задача 041: favicon из логотипа бренда
web/favicon.svg повторяет .brand-logo шапки: синий скруглённый квадрат с белым глобусом
(геометрия I.globe без изменений, пропорции логотипа), подключён в index.html.
Итог 039 дополнен результатом pytest (17 passed) после включения учётной записи admin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 18:26:52 +03:00

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
TOTP_ENC_KEY Ключ Fernet для шифрования секретов 2FA (изменение 037). Не задан — приложение стартует, но 2FA недоступна (503); некорректный ключ — отказ старта; потеря ключа означает сброс 2FA всем пользователям

Архитектура

Слой Технологии
API FastAPI, pydantic v2, JWT (срок 8 ч), пароли в argon2, роли superadmin, admin, viewer с привязкой к организации
БД PostgreSQL 16, SQLAlchemy 2, Alembic (миграции 0001–0016), типы 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.

Пользователи и организации

  • admin/viewer привязаны к одной организации (organization_id), superadmin — ни к одной. БД допускает admin/viewer без организации только у отключённой записи (состояние после миграции 0011).
  • Смена роли на superadmin снимает организацию автоматически; понижение до admin/viewer требует указать organization_id.
  • Изоляция: admin/viewer видят и меняют только данные своей организации, включая «Обзор», журнал и счётчики типов устройств. Чужой объект неотличим от несуществующего — 404.

VRF и префиксы

  • У каждой организации автоматически создаётся VRF default. Имя VRF уникально в пределах организации без учёта регистра.
  • Префикс принадлежит VRF своей организации; это гарантирует составной FK в БД.
  • Вложенность префиксов (parent_id) определяется автоматически по CIDR. Смена VRF переносит префикс вместе с вложенными, только в пределах организации.
  • Автовыделение вложенного префикса: система выбирает первый свободный выровненный блок заданного размера.
  • Изменения дерева одного VRF выполняются по одному (advisory-lock); разные VRF друг друга не блокируют.

Устройства

  • Эксплуатационный статус устройства (изменение 039): «Активен» (по умолчанию), «Выключен», «На обслуживании». Назначается вручную на экране устройств.

Адреса

  • IP уникален в пределах VRF и хранится в самом узком содержащем его префиксе.
  • Адрес сети и broadcast (IPv4, префикс ≤ /30) назначить нельзя.
  • Свободные адреса не хранятся, а вычисляются. В общем списке они показываются только для подсетей до /20.
  • В списке адресов префикса видны тип, имя и статус привязанного устройства (изменение 039); у адресов без устройства — «—».

Ёмкость и «Обзор»

  • Ёмкость префикса — размер его подсети (для IPv4 без адреса сети и broadcast). Занятость считается по всему поддереву.
  • «Обзор» учитывает только IPv4: ёмкость — сумма корневых активных префиксов, назначенные адреса — адреса внутри них.

Удаление

  • Объекты с зависимыми данными не удаляются (409). Отказ фиксируется в журнале с перечнем мешающих объектов.
  • Организация не удаляется, если у неё есть привязанные пользователи.

Журнал аудита

  • Событие привязано к организации своей сущности (organization_id); admin/viewer видят только события своей организации, включая отклонённые удаления.
  • Системные события (пользователи, вход, настройки и очистка журнала, типы устройств) не привязаны к организации и видны только superadmin.
  • События организации показывают IP и User-Agent исполнителя, в том числе суперадминистратора.
  • После удаления организации её события сохраняются с organization_id = NULL.

API (/api/v1)

Область Эндпоинты
Вход POST /auth/login, POST /auth/login/2fa (изменение 037), 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, POST /users/me/2fa/setup|enable|disable, POST /users/{id}/2fa/reset (изменение 037)
Журнал 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 очищает его.
  • Каждое изменение данных пишется в журнал аудита.

Безопасность

Роли и учётные записи

Роль Права
superadmin Все организации; пользователи, создание и удаление организаций, типы устройств, настройки и очистка журнала
admin Запись в своей организации: VRF, префиксы, адреса, устройства, операторы, карточка организации
viewer Чтение своей организации. Роль по умолчанию
  • Логин уникален без учёта регистра.
  • Свою учётную запись нельзя понизить, отключить или удалить; последний активный superadmin защищён от понижения, отключения и удаления.

Пароли и токены

  • Смена пароля отзывает ранее выданные токены. Свой пароль меняется только с подтверждением текущего.

Вход

  • Лимит неудачных попыток за 10 минут:
    • 5 — на пару логин + IP;
    • 20 — на IP;
    • 50 — на логин со всех IP, кроме тех, с которых пользователь успешно входил за 30 дней.
  • Сверх лимита — 429 с Retry-After.
  • Двухфакторная аутентификация TOTP — по желанию пользователя (изменение 037), не обязательна. Включается в меню логина в шапке: пароль → QR-код (Google Authenticator, Aegis, 1Password, Bitwarden и подобные) → код подтверждения → 10 одноразовых кодов восстановления (показываются один раз). При включённой 2FA вход идёт в два шага: POST /auth/login возвращает mfa_token вместо токена доступа, POST /auth/login/2fa принимает код из приложения или код восстановления. Неверный код учитывается в тех же лимитах перебора, что и пароль. Секрет хранится в БД зашифрованным ключом TOTP_ENC_KEY; коды восстановления — хэшем sha256. Суперадминистратор может сбросить 2FA другому пользователю (POST /users/{id}/2fa/reset), не отключая свою.

Журнал

  • Хранит 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. Стенд опубликован как https://rxipam.rxmsk.ru через общий Caddy хоста (/opt/lvraid/apps/caddy/Caddyfile, блок rxipam.rxmsk.ru):
    • Caddy проксирует на опубликованный порт приложения (172.19.0.1:8088 — шлюз его Docker-сети). Сети не объединяются: в сети Caddy уже есть сервисы с именами app и db.
    • Из интернета доступны UI и /api/v1. /docs, /redoc, /openapi.json — только из частных сетей (RFC 1918/4193, loopback), иначе 404.
    • TRUSTED_PROXIES=172.16.0.0/12 в .env: Caddy приходит в приложение с адреса шлюза Docker-моста, реальный IP клиента берётся из X-Forwarded-For. Без этого все интернет-клиенты делят один IP — и лимит входа, и журнал.
    • LAN-клиенты по публичному имени идут через hairpin NAT шлюза и видны как 192.168.5.253. Чтобы видеть их реальные адреса, нужен split DNS: rxipam.rxmsk.ru → 192.168.5.9 внутри LAN.
  • Переход на ролевую модель (миграция 0011): прежние admin становятся superadmin, прежние viewer отключаются до назначения организации суперадминистратором.
  • Перед обновлением рабочей БД проверьте данные скриптами только для чтения: scripts/find_duplicate_addresses.py и scripts/find_unusable_addresses.py.
  • Имя compose-проекта задаётся флагом -p. Текущий стенд поднят как ipam_control_006 (docker compose -p ipam_control_006 …); без флага команды работают с проектом ipam_control.

Интерфейс

  • Экраны: «Обзор», «Префиксы» (дерево по VRF), «Адреса» подсети, «Организации», «Операторы», «Устройства», «Журнал», «Пользователи» (только superadmin).
  • В «Пользователях» видна дата и IP последнего входа (изменение 035); «—», если пользователь ещё не входил.
  • Логин в шапке раскрывает меню «Сменить пароль» / «Двухфакторная аутентификация» / «Выйти» (изменения 036, 037).
  • В «Пользователях» у записей с включённой 2FA — бейдж «2FA»; в меню строки суперадминистратора для чужих записей — «Сбросить 2FA» (изменение 037).
  • Тёмная тема (изменение 038): по умолчанию следует настройке ОС/браузера (prefers-color-scheme); переключатель-пиктограмма в шапке (солнце/луна/монитор), выбор хранится в localStorage браузера.
  • «Устройства»: колонка «Статус» (бейдж) и поле «Статус» в диалоге добавления/редактирования; «Адреса» подсети: колонки «Тип устройства», «Устройство», «Статус устройства» после «Описание» (изменение 039).
  • Переключатель организации — только у superadmin; admin/viewer работают в своей организации.
  • Строка реестра кликабельна целиком. Действия над строкой — в меню «⋯».
  • Групповые операции через чекбоксы (кроме «Журнала»): удаление, смена типа устройств, статус префиксов и адресов, доступ пользователей.
  • Экран «Префиксы» загружает до 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 план · итог
032 Ролевая модель с привязкой к организации и суперадминистратором план · итог
033 Исправление находок ревью 032 план · итог
034 Публикация через Caddy: rxipam.rxmsk.ru план · итог
035 Последний вход пользователя: дата и IP план · итог
036 Меню пользователя в шапке: логин с выпадающим списком план · итог
037 Двухфакторная аутентификация TOTP, по выбору пользователя план · итог
038 Тёмная тема UI план · итог
039 Статус устройства и сведения об устройстве в списке адресов префикса план · итог
040 Название в UI: «IPAM Manager» план · итог
041 Favicon из логотипа бренда план · итог

Отчёты ревью

S
Description
IPAM Management Tool
Readme
1.1 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 64.5%
JavaScript 30.1%
CSS 4.9%
HTML 0.2%
Dockerfile 0.2%
Other 0.1%