Collector daemon, FastAPI server (addresses, diff, collect, sources), SQLite storage with change journal, Docker Compose deployment, tests, documentation and project rules. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.3 KiB
7.3 KiB
План: надёжность и безопасность ripe_cidr_collector
Context
Сейчас данные пишутся неатомарно, при повреждении JSON молча превращаются в {} (следующий сбор перезапишет хорошие данные), записи копятся бессрочно, POST /schedule открыт всем, а сервис слушает 0.0.0.0 без защиты. Цель: сделать хранение устойчивым, ограничить срок жизни адресов (TTL 90 дней), закрыть управляющий эндпоинт токеном. Формат ответа GET /addresses не меняется, поэтому потребители не ломаются.
Решения пользователя: токен только на POST; TTL 90 дней, настраиваемый (0 = бессрочно).
Артефакты по правилам проекта (создаются при реализации)
docs/plan-reliability-security.md- этот план (копия в проект первым шагом)docs/summary-reliability-security.md- итоги, в конце- обновить
README.md(токен, TTL, /health, non-root systemd, схема данных)
Изменения
1. Новый модуль storage.py (общий, убирает дублирование load/save из обоих файлов)
load_json(path, default): при отсутствии файла возвращает default; при битом JSON логирует ошибку, переименовывает файл в<name>.corrupt-<ts>и бросаетStorageError(сборщик прерывается, не затирая данные; API отвечает 503).save_json_atomic(path, data): запись во временный файл в том же каталоге,flush+fsync,os.replace.file_lock(path): контекстный менеджер наfcntl.flockпо<path>.lock. Чтение-изменение-запись в сборщике идёт под блокировкой (защита от одновременного cron и APScheduler).
2. cidr_collector.py
- Заменить локальные
load_*/save_*иsave_full_configна функции изstorage.py. - Схема записи:
prefixes/ipsостаются списком (совместимость с API), добавляетсяseen: {value: {first_seen, last_seen}}. - Слияние при успешном получении данных: обновить
last_seenу увиденных, добавить новые сfirst_seen, удалить те, у когоlast_seenстаршеttl_days. При ошибке RIPE/DNS (None/пусто) ничего не удаляется. - Миграция на лету: если
seenнет, инициализироватьfirst_seen = last_seen = last_updated(старые данные не теряются). - Частота записи: файл пишется, если изменился состав адресов, либо если
last_seenу какой-то записи старше 24 ч (иначе при запуске каждые 15 минут файл переписывался бы постоянно). Так TTL остаётся корректным, а лишних записей нет. printзаменить наlogging(INFO/WARNING/ERROR).ttl_daysчитается изconfig.json(ключttl_days, по умолчанию 90).
3. api_server.py
POST /schedule: зависимостьverify_token- заголовокX-API-Key, сравнение черезsecrets.compare_digest. Токен берётся из переменной окруженияRIPE_API_TOKEN(не изconfig.json). Если переменная не задана,POSTвозвращает 503 (fail closed).- Запись конфига через
storage.save_json_atomicпод блокировкой вместо прямогоopen(...,'w'). - Валидация тела через pydantic-модель (
type: Literal["asn","fqdn"],cron: str) вместоDict[str,str]. @app.on_eventзаменить наlifespan; вshutdownиспользоватьscheduler.shutdown(wait=False).- Добавить
GET /health: время последнего успешного сбора по asn/fqdn, число записей, статус планировщика. - Ошибки чтения данных (
StorageError) -> 503 вместо тихого пустого списка. __main__: хост по умолчанию127.0.0.1.
4. Развёртывание (только документация в README.md)
- systemd: запуск от отдельного пользователя
ripe,EnvironmentFile=/etc/ripe-api.env(RIPE_API_TOKEN=..., права 600), опцииNoNewPrivileges=true,ProtectSystem=strict,ReadWritePaths=/opt/ripe_collector. - OpenRC: аналогично (
command_user,envфайл). - Пояснение:
0.0.0.0оставлен, т.к. потребители удалённые; ограничивать доступ к порту 8000 файрволом. - Добавить
.gitignore(venv/,*.corrupt-*,*.lock,*.tmp).
5. Тесты (минимум, tests/test_core.py, pytest)
- Слияние и TTL: старый адрес удаляется по истечении срока, новый добавляется, при ошибке источника ничего не удаляется.
- Битый JSON:
load_jsonбросаетStorageError, файл переименован, исходные данные не перезаписываются. - Авторизация:
POST /scheduleбез токена -> 401, с верным токеном -> 200, без заданногоRIPE_API_TOKEN-> 503 (fastapi.testclient, сеть замокана). Добавитьpytestиhttpxвrequirements.txt(илиrequirements-dev.txt).
Критичные файлы
cidr_collector.py, api_server.py, config.json (+ttl_days), новый storage.py, README.md, requirements.txt.
Проверка
source venv/bin/activate && pytest -q- 3 теста зелёные.- На копии текущих
data.json/fqdn_data.json:python cidr_collector.py run- миграция без потери адресов (сравнить количество до/после), повторный запуск не переписывает файл. - Испортить копию
data.json- сборщик завершается с ошибкой, файл переименован в.corrupt-*, API отвечает 503. - Запустить
uvicorn api_server:app:curl /addressesбез токена работает;curl -X POST /scheduleбез ключа -> 401, сX-API-Key-> 200,config.jsonобновлён;curl /healthвозвращает статусы. - Параллельный запуск двух
run --mode asn- данные не повреждены (lock работает). - Выставить
ttl_daysмалым значением и проверить удаление устаревшей записи.