control-api: every route now carries a mandatory access level (admin / agent / open) in a route table. All /api/v1/admin/* require the admin token; the write calls of validator-agent and prober (self-check, events, results, complete) require a separate static agent token; register, heartbeat and fetching the assignment stay open. Tokens come from env vars, are compared in constant time and never logged. An empty token leaves that level open with a startup warning (backward compatible). validator-agent / prober: apiclient sends the agent token only to control-api. admin-dashboard: login/password (from env) with a stateless HMAC session cookie, Origin-based CSRF check, per-IP brute-force throttle, HX-Redirect for htmx polls, logout in the sidebar; the dashboard calls control-api with the admin token. Login page layout fixed after review. Also: env plumbing in docker-compose/rxprod-compose/systemd/config examples, e2e script with token assertions, tests, docs (API, SETUP, USAGE, DASHBOARD, README), plan and review under docs/changes/, bin/ rebuilt with new SHA256SUMS. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Cloud IP Validator
Система проверки освобождённых публичных IPv4-адресов перед их повторной выдачей. Каждый адрес временно привязывается как Floating IP
к ВМ-валидатору в облаке (OpenStack) и проверяется в двух направлениях одновременно: исходящий трафик валидатора (egress: HTTPS/ICMP/опционально SSH
до внешних целей) и входящая доступность самого адреса с нескольких независимых внешних площадок (inbound: TCP 22/80/443/8080 + ICMP).
Итог по каждому адресу — pass/partial/fail; полная история проверок сохраняется в реестре даже после удаления адреса из очереди.
Быстрый старт
go build ./... && go test ./... # сборка и юнит-тесты
scripts/run-local-e2e.sh # офлайн-прогон всей системы: mock OpenStack, без облака и интернета
cd deploy/docker
cp .env.example .env
cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml
docker compose up -d --build # весь стенд на одной машине в mock-режиме (профили control-plane, dashboard, prober, validator)
- UI:
http://<хост>:8090/, API:http://<хост>:8080/api/v1, проверка живости:GET /healthz. - Docker-стенд работает с
openstack.mode: mockи тестовыми адресами203.0.113.10–12; реальный OpenStack и учётные данные не нужны. - Реальный стенд (systemd или Docker на нескольких хостах) — по шагам в docs/SETUP.md.
Конфигурация
Каждый компонент читает свой YAML (примеры — configs/*.example.yaml). Учётные данные OpenStack в YAML не хранятся — только имена переменных окружения.
control-api (control-api.yaml)
| Ключ | Назначение |
|---|---|
server.listen_addr |
Адрес API (:8080) |
database.path |
Файл SQLite (/var/lib/cloud-ip-validator/control-api.db) |
openstack.mode |
real или mock (встроенная заглушка OpenStack для разработки и тестов) |
openstack.auth_method |
token (готовый токен проекта из OS_TOKEN, сам не обновляется) или password (логин/пароль Keystone, токен перевыпускается автоматически) |
openstack.*_env |
Имена переменных окружения: OS_AUTH_URL, OS_PROJECT_ID, OS_REGION_NAME, OS_INTERFACE, OS_TOKEN либо OS_USERNAME/OS_USER_DOMAIN_NAME/OS_PASSWORD |
orchestrator.poll_interval_seconds |
Период такта оркестратора (5) |
orchestrator.self_check_timeout_seconds, max_self_check_retries |
Ожидание self-check валидатора (60) и число его повторов (3) |
orchestrator.checking_window_seconds |
Сколько ждать результаты проверок, прежде чем подвести итог (120) |
orchestrator.lease_ttl_seconds, max_retries |
Лизинг адреса за валидатором (180) и число возвратов в очередь при его истечении (3) |
orchestrator.heartbeat_timeout_seconds |
После скольких секунд тишины валидатор или площадка считаются потерянными (30) |
orchestrator.fip_settle_seconds |
Только начальное значение: пауза между привязкой FIP и self-check; далее управляется на лету. Должно выполняться fip_settle_seconds + self_check_timeout_seconds < lease_ttl_seconds |
orchestrator.fip_scan_interval_seconds |
Периодический скан Floating IP (0 — выключен). Не выполняется, пока включён автоматический цикл |
auth.admin_token_env, auth.agent_token_env |
Имена переменных окружения с токеном администратора (CONTROL_API_ADMIN_TOKEN) и токеном агентов (CONTROL_API_AGENT_TOKEN). Значения в YAML не хранятся; пустой токен — соответствующий уровень API открыт (с предупреждением в логе) |
aggregation.missing_counts_as_fail |
Отсутствие ответа источника засчитывается как провал (true) |
validators, sites, check_types, targets, inbound_checks |
Начальная загрузка пустой БД: валидаторы (validator_id + os_port_id), внешние площадки, типы и цели egress-проверок, порты inbound-проверок. Дальше источник истины — БД, правки через API/UI |
ip_addresses |
Адреса, которые доливаются в очередь при каждом старте (только новые) |
Остальные компоненты
| Компонент | Ключи |
|---|---|
validator-agent |
validator_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, self_check.* (таймаут, ip_echo_urls), checks.* (таймауты HTTPS/ICMP, число ICMP-пакетов, ssh.*) |
prober |
site_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, checks.* (таймауты TCP/ICMP, число ICMP-пакетов) |
admin-dashboard |
server.listen_addr (:8090), control_api.base_url, control_api.timeout_seconds, control_api.token_env (ADMIN_DASHBOARD_CONTROL_API_TOKEN), auth.username_env / password_env / session_secret_env (ADMIN_DASHBOARD_USERNAME / _PASSWORD / _SESSION_SECRET), auth.session_ttl_minutes (480), overview.last_completed_count (20), overview.poll_interval_seconds (5) |
Секреты (токены, пароль дашборда, ключ сессии) задаются только переменными окружения; генерация — openssl rand -hex 32. Подробности и порядок включения — docs/SETUP.md.
Настройки, меняющиеся на лету (пауза перед self-check, глубина истории, типы inbound-проверок, автоматический цикл, валидаторы, площадки, цели), хранятся в БД и правятся через API или страницу /settings.
Архитектура
| Слой | Технологии |
|---|---|
| Backend | Go 1.26, net/http (маршруты Go 1.22), SQLite (чистый Go-драйвер modernc.org/sqlite, WAL, одно соединение), встроенные миграции 0001–0008 |
| Облако | Интерфейс FloatingIPClient: реальный клиент OpenStack Neutron и MockClient |
| UI | Серверный рендеринг (Go-шаблоны) + htmx и Alpine.js, шрифты и скрипты лежат в репозитории, внешних зависимостей нет |
| Сборка | Статические бинарники (CGO_ENABLED=0) в bin/, Docker-образы, systemd-юниты |
| Компонент | Роль | Состояние | Где работает |
|---|---|---|---|
control-api |
Ведёт очередь адресов и реестр их истории, назначает валидаторов, агрегирует результаты. Единственная точка, с которой общаются все остальные компоненты | Хранит (SQLite) | Одна управляющая машина |
validator-agent |
Привязывает к себе выданный Floating IP и прогоняет egress-проверки до внешних целей | Без состояния | Каждая ВМ-валидатор в облаке |
prober |
Проверяет входящую доступность адреса снаружи (TCP/ICMP) — с независимой от облака сети | Без состояния | Каждая внешняя тестовая площадка |
admin-dashboard |
Браузерная админ-панель — то же, что API control-api, но графически. Опционален |
Без состояния | Любая машина с доступом к control-api |
Все четыре общаются только через HTTP API control-api. Pull-модель: validator-agent и prober сами опрашивают control-api, он к ним не обращается.
Схемы control/data plane — docs/DIAGRAMS.md.
cmd/ control-api validator-agent prober admin-dashboard
internal/ config db orchestrator httpapi openstack dashboard agentcore probercore checkrunner apiclient
db/migrations/ схема SQLite (0001–0008)
configs/ примеры конфигураций компонентов
deploy/ docker/ (compose, Dockerfile, RUN.txt) systemd/ (юниты)
rxprod-compose/ compose реального стенда (control-api на :8081, дашборд на :8091)
scripts/ run-local-e2e.sh httpstub/ (заглушки целей для e2e)
bin/ готовые бинарники linux/amd64 и SHA256SUMS
docs/ документация и планы доработок
Модель данных и правила
ip_queue (текущая очередь) · ip_registry (все адреса, когда-либо бывшие в очереди) · checks (результаты по циклам) · events (журнал) · validators · sites · target_groups · check_types · settings · inbound_checks_settings · auto_cycle.
Очередь и состояния
- Путь адреса:
queued→assigning_fip→awaiting_self_check→checking→aggregating→doneилиfailed. Все переходы, кроме команд оператора, выполняет оркестратор сам по таймеру. - Терминальные состояния:
done,failed,occupied.occupied— Floating IP к моменту привязки уже занят чужим портом: цикл проверки не стартует,overall_resultпуст, это неfail. - Оператор может отменить проверку (
failed+cancelled), перепроверить завершённый адрес (возврат вqueued) или удалить адрес из очереди. - Адреса обрабатываются в порядке
sequence; каждый свободный валидатор получает следующийqueued-адрес под лизинг. Истёкший лизинг возвращает адрес в очередь, послеmax_retries— вfailed.
Итоговый результат
pass— прошли все проверки: исходящие и все настроенные площадки по всем портам и ICMP.partial— часть прошла, часть нет.fail— не прошла ни одна проверка или адрес не дошёл до проверок (self-check не подтвердился).cancelled— остановлено оператором.- Итог подводится, когда отчитались валидатор и все настроенные площадки либо истекло
checking_window_seconds. Молчание источника — провал (aggregation.missing_counts_as_fail). - Площадки опциональны: с пустым списком
sitesитог строится только по egress-проверкам.
Реестр
- Запись реестра создаётся при первой постановке адреса и не удаляется: она переживает удаление из очереди и повторное добавление.
- История проверок хранится по циклам;
history_retention_cyclesограничивает глубину (0 — без ограничения), сама запись реестра остаётся.
Автоматический цикл (опционально, по умолчанию выключен)
- Один цикл: очистить очередь → просканировать Floating IP → дождаться, пока все адреса станут терминальными → пауза
interval_seconds→ заново. Пауза считается от завершения цикла. interval_seconds— по умолчанию 3600, минимум 60;max_run_seconds— максимальное ожидание проверок (0 — без лимита), по истечении исходtimeout.- Исходы цикла:
completed,no_free_ips,timeout,error,stopped. Опустевшая очередь посреди цикла считается завершением. - Состояние хранится в БД и переживает перезапуск; выключение не прерывает идущие проверки. Подробности — docs/USAGE.md.
Удаление и сканирование
- Удаление (точечное, списком, «очистить всё») убирает строку очереди, но не историю в реестре.
- Скан Floating IP ставит в очередь только свободные адреса (не привязанные ни к одному порту); уже идущие проверки не трогаются.
API (/api/v1)
| Область | Эндпоинты |
|---|---|
| Валидатор | POST /agents/register, POST /agents/{id}/heartbeat, GET /agents/{id}/assignment, POST /agents/{id}/self-check|events|results|complete |
| Пробер | POST /probers/register, POST /probers/{site_id}/heartbeat, GET /probers/{site_id}/assignments, POST /probers/{site_id}/results |
| Очередь | GET /admin/status, GET|POST /admin/ips, GET /admin/ips/{ip}, POST /admin/ips/{ip}/cancel, DELETE /admin/ips/{ip}, POST /admin/ips/delete|clear|scan |
| Реестр | GET /admin/registry, GET /admin/registry/{ip} |
| Автоцикл | GET|PUT /admin/auto-cycle, POST /admin/auto-cycle/start|stop |
| Конфигурация | /admin/config/validators, /sites, /targets, /check-types, GET|PUT /admin/config/orchestrator, GET|PUT /admin/config/inbound-checks |
| Служебное | GET /admin/validators, GET /healthz |
Соглашения
- Тело запросов и ответов — JSON. Успех —
200,204— когда данных нет (например, у валидатора нет назначения). - Ошибки —
4xx/5xxс телом{"error": "..."}; нет или неверен токен —401(сWWW-Authenticate: Bearer), неизвестная сущность —404, конфликт состояния —409, неверные данные —400, ошибка OpenStack при скане —502. - Токен передаётся заголовком
Authorization: Bearer <токен>(docs/API.md). - Времена — RFC 3339. Полная спецификация и примеры
curl— docs/API.md.
Безопасность
- Доступ к API — два статических Bearer-токена (без срока жизни, из переменных окружения, сравнение в константное время):
Уровень Токен Методы admin CONTROL_API_ADMIN_TOKENвсе /api/v1/admin/*agent CONTROL_API_AGENT_TOKENзапись результатов валидатора и пробера: self-check,events,results,completeоткрыто — GET /healthz,register,heartbeatи получение задания (GET assignment/assignments)Токены разные: административный не открывает методы агентов, и наоборот. Валидатор и пробер получают настройку и задание без токена, но не могут отправить результат без токена агентов. - Пустой токен — уровень открыт (обратная совместимость):
control-apiстартует с предупреждением в логе. На реальном стенде задайте оба токена и ограничьте доступ на уровне сети (docs/SETUP.md); токены идут открытым текстом без TLS — публикуйте через reverse-proxy с TLS. Включать токен агентов нужно после его раздачи валидаторам и проберам (порядок). - Дашборд закрыт логином и паролем (один администратор; пароль и ключ сессии — из env). Сессия — подписанная cookie (
HttpOnly,SameSite=Strict, без состояния на сервере), CSRF-защита поOrigin, 5 неудачных входов за 10 минут с одного IP →429. Без заданных логина/пароля дашборд открыт (с предупреждением в логе). Дашборд ходит в API с токеном администратора. Подробности — docs/DASHBOARD.md. - Учётные данные OpenStack передаются только через переменные окружения процесса (
EnvironmentFile=в systemd,OS_*в Docker) и не попадают в YAML; режимpasswordперевыпускает токен сам, режимtoken— нет. - Компоненты работают по pull-модели: на валидаторах и площадках не нужно открывать входящие порты для
control-api. - Бинарники статические, без
cgo; целостность проверяетсяsha256sum -c bin/SHA256SUMS.
Публикация и эксплуатация
- Бинарники. Готовые linux/amd64 лежат в
bin/и не обновляются автоматически: после правок кода пересоберите их и обновитеSHA256SUMS(команды — docs/SETUP.md); Dockerfile копируют именноbin/*. - systemd. Юниты в
deploy/systemd/; уcontrol-api—EnvironmentFileс учётными данными OpenStack. - Docker.
deploy/docker/docker-compose.yml+docker-compose.override.yml(dev, mock) илиdocker-compose.prod.yml(без публикации портов,restart: unless-stopped). Какие сервисы поднимаются на хосте, задаётCOMPOSE_PROFILES:control-plane,dashboard,prober,validator. БД — volumecloud-ip-validator-db. - Реальный стенд.
rxprod-compose/— compose с готовыми образами, собственнымcontrol-api.yamlи каталогом БДcapi-db/;.envс учётными данными в репозиторий не входит. - Миграции применяются при старте
control-api; версия схемы —PRAGMA user_version. Начальная загрузка (validators,sites,targets,check_types,inbound_checks) выполняется только в пустые таблицы. - Остановка (
SIGTERM) корректно завершает HTTP-сервер и фоновые циклы. Состояние автоцикла и очереди сохраняется в БД.
Интерфейс
Страницы admin-dashboard (подробно — docs/DASHBOARD.md):
| Страница | Назначение |
|---|---|
/overview |
Счётчики по состояниям, «текущая» и «последние завершённые» проверки, поиск по IP и фильтр по статусу, индикатор автоцикла; обновляется без перезагрузки |
/ips, /ips/{ip} |
Очередь: добавление адресов, «Сканировать Floating IP», перепроверка, отмена, удаление (в том числе списком и «Очистить всё»); детали и события адреса |
/registry, /registry/{ip} |
Реестр всех адресов и полная история проверок адреса; поиск и фильтр сохраняются в адресной строке |
/validators, /sites, /targets, /check-types |
Управление валидаторами, внешними площадками, группами целей и типами проверок |
/settings |
Панель «Автоматический цикл», пауза перед self-check, глубина истории, TCP-порты и ICMP для inbound-проверок |
- Порядок блоков на
/overviewфиксирован: статистика → фильтр → таблицы; поллится только блок таблиц, поэтому набранный в фильтре текст не сбрасывается. - Ошибки control-api показываются баннером; при недоступном API страница остаётся рабочей.
- Тёмная и светлая темы, переключатель в шапке.
Тесты
go build ./... && go vet ./... && go test ./... # юнит-тесты: db, orchestrator, httpapi, dashboard, agentcore, probercore, checkrunner, openstack
go test -race ./internal/orchestrator ./internal/httpapi ./internal/dashboard ./internal/db
scripts/run-local-e2e.sh # сквозной прогон: lease-reclaim, перепроверка, автоматический цикл
Юнит-тесты используют временную SQLite и MockClient, внешних ресурсов не требуют. E2E поднимает все компоненты локальными процессами с включёнными токенами (проверяет 401/открытые маршруты и работу агента и пробера с токеном) и завершается ненулевым кодом при провале проверок автоцикла — docs/LOCAL_E2E.md.
Документация
| Документ | Для чего |
|---|---|
| docs/SETUP.md | Развёртывание с нуля: бинарники или сборка из исходников, конфигурация, systemd и Docker/docker-compose — пошагово |
| docs/USAGE.md | Повседневная работа: очередь, сканирование Floating IP, автоматический цикл, статус, реестр и история, разбор результатов |
| docs/API.md | Спецификация HTTP API control-api и примеры запросов (curl) |
| docs/DASHBOARD.md | Устройство admin-dashboard: страницы, поиск и фильтр, обработка ошибок |
| docs/DIAGRAMS.md | Диаграммы потоков данных: control plane, egress-проверка, телеметрия |
| docs/LOCAL_E2E.md | Полностью офлайн-прогон всей системы одним скриптом |
| docs/changes/ | Планы доработок и отчёты ревью с отметкой времени в имени файла (последняя: аутентификация) |
| docs/CONTROL_DATA_PLANE.html | Презентационные схемы control/data plane для docker-compose-деплоя — открыть в браузере |
История изменений
Дизайн крупных доработок зафиксирован в планах docs/PLAN_*.md; остальное — в истории git (git log).
| Изменение | Документ |
|---|---|
Веб-панель администратора admin-dashboard |
план · описание |
| Динамическое управление конфигурацией и очередью через API | план · API |
| Удаление адресов: точечное, массовое, «очистить всё» | план |
Пауза перед self-check после привязки Floating IP (fip_settle_seconds) |
план · USAGE |
| Механизм аутентификации OpenStack-клиента (token / password) | план |
| Дата | Веха | Документ |
|---|---|---|
| 2026-10-01 | Аутентификация: токены администратора и агентов для API, логин и пароль для дашборда | план · ревью и тесты · API |
| 2026-10-01 | Автоматический цикл проверок по сценарию: очистка → скан FIP → проверка → пауза | USAGE · API |
| 2026-09-23 | Сканирование Floating IP и устойчивый реестр адресов с настраиваемой глубиной истории | USAGE |
| 2026-09-23 | Поиск по IP и фильтр по статусу на «Обзоре» и «Реестре» | DASHBOARD |
| 2026-09-23 | Пошаговое руководство по развёртыванию в Docker | SETUP |
| 2026-09-18 | Массовая перепроверка в дашборде; compose реального стенда rxprod-compose |
DASHBOARD |
| 2026-09-13 | Состояние occupied: пропуск цикла для уже занятого Floating IP; схемы control/data plane |
DIAGRAMS |
| 2026-08-26 | Управление внешними площадками и heartbeat пробера, новый UI дашборда, переключатель темы | DASHBOARD |