Skip to content

v4.1 — аудит флота: честные сенсоры, дрейф дефолтов conf, RPS без гонки, рейт лога анти-скана - #42

Merged
jestivald merged 14 commits into
mainfrom
v4.1-fleet-audit
Sep 2, 2026
Merged

jestivald merged 14 commits into
mainfrom
v4.1-fleet-audit

Conversation

@jestivald

Copy link
Copy Markdown
Owner

Релиз по итогам аудита боевого флота на v4.0.1 (11 нод + панель, Debian 12/13, XanMod 6.18). Десять issue, почти все — про то, что тулкит врал о своём состоянии: сенсоры считали не то, что обещали, юнит рапортовал успех при неприменённом тюнинге, сохранённый конфиг молча держал дефолты прошлой версии. Полный разбор — в CHANGELOG.md (секция v4.1).

Что меняется

Тесты

Новые: common-unit (35), diagnose-unit (42), protect-unit (61), rps-unit, psi-unit — все ИСПОЛНЯЮТ код против стабов, на v4.0.1 красные. CI: +5 юнит-шагов, +DRY_RUN-проверки дрейфа дефолтов (warn/adopt/explicit), PORTSCAN_LOG_RATE=0/кривого значения, дедупа и широкого CIDR в WHITELIST. Подписи 6 модулей обновлены последним коммитом.

Closes #27, closes #28, closes #30, closes #31, closes #32, closes #34, closes #35, closes #37, closes #38, closes #39. (Дубли #29, #33, #36 закрыты ранее.)

jestivald and others added 14 commits September 2, 2026 13:56
…дрейф дефолтов conf (#34) + tests/common-unit.sh
…аздел о дрейфе дефолтов), CI: common-unit + DRY_RUN-проверка #34
…SI/NA_JOURNAL_MAX_USE (#28 #30 #37 #35)

#28: tput/`\r\033[K` только когда stdout — терминал (is_tty/tty_tput из common.sh).
Под `nohup … > log` escape-коды уезжали в лог раскатки и ломали grep по нему.
Вне терминала прогресс-бар не рисуется вовсе — остаются "Ставлю $p…" и итог.

#30: na-rps рапортовал success при неприменённом RPS — network-online.target на буте
достигается раньше, чем появляется default route, и скрипт молча делал exit 0.
Теперь: имя NIC вшито в ExecStart (детект перенесён в секцию 5, до генерации юнита) →
если интерфейса нет (переименование после смены ядра) — ожидание маршрута до 20с →
если маршрута так и нет — все физические интерфейсы (/sys/class/net/*/device, без
lo/veth/docker/br-) → если кандидатов нет — exit 1 «giving up» + Restart=on-failure,
RestartSec=5, StartLimitBurst=5. Строка-маркер `na-rps: NIC=… mask=… cpus=…` печатается
на каждый затюненный интерфейс.

#37: ручка ENABLE_PSI (дефолт 0, opt-in — учёт PSI не бесплатен для планировщика).
При 1 дописывает psi=1 в GRUB_CMDLINE_LINUX_DEFAULT (идемпотентно, с backup_file и
update-grub) → /proc/pressure после ребута, и сенсор давления в diagnose перестаёт
быть заглушкой. Без /etc/default/grub или без CONFIG_PSI в ядре — info и пропуск.

#35 (часть): потолок journald вынесен в NA_JOURNAL_MAX_USE (дефолт 300M) — на
публичной ноде поток логов фаервола съедает 300M меньше чем за сутки.

Маркер optimize.installed: +строка `psi=<0|1>` — только при 1 rollback снимает psi=1
из GRUB (чужой параметр не трогаем). Оба ключа персистятся в optimize.conf.

Тесты: tests/rps-unit.sh (исполняет сгенерированный na-rps-setup против стабов
ip/nproc/sleep — 5 кейсов + юнит + отсутствие escape-кодов) и tests/psi-unit.sh
(секция PSI и блок отката против стаба update-grub). Оба падают на коде v4.0.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#35 #38)

na-fw-status (#32/#36): все счётчики наборов — через nft_set_count из lib/common.sh,
тело функций вшивается в хелпер `declare -f` (на ноде библиотеки рядом может не быть).
`grep -c timeout` считал строку заголовка `flags dynamic,timeout`: пустой набор давал
«1», непустой был завышен, и na-fw-status расходился с na-diagnose. Список банов режем
строго по блоку `elements = { … }`.

Анти-скан-лог (#35): рейт вынесен в PORTSCAN_LOG_RATE (записей в МИНУТУ, дефолт 60
вместо прежних 5/second) + PORTSCAN_LOG_BURST=30; 0 = лог-правило не ставится вовсе,
на бан это не влияет — он работает по meter'ам. Перед применением (и в DRY_RUN)
считаем суточный объём против капа journald (SystemMaxUse из journald.conf и drop-in'ов,
не нашли — 300M как у optimize) и предупреждаем при >30% в сутки. Остальные лог-правила
не тронуты: они срабатывают только сверх лимитов.

WHITELIST (#38, часть 2): add_wl дедупит (как соседний add_npwl), нормализует /32 и /128
к голому адресу и предупреждает о префиксе короче /29 (v4) или /64 (v6) — whitelist это
полный обход защиты, а не «список доверенных». Дедуп доезжает и до CrowdSec-парсера
(строился из сырого WHITELIST); protect.conf по-прежнему хранит список оператора дословно.

tests/protect-unit.sh: исполняет сгенерированный na-fw-status против стаба nft, гоняет
add_wl, сборку PORTSCAN и оценку бюджета журнала. На v4.0.1 падает (25 проверок).
…27 #30 #31 #32 #34 #35 #37 #38 #39)

Урожай аудита боевого флота v4.0.1 (11 нод + панель). Все семь — про то, что
сенсор врал уверенно: цифра была, но мерила не то, что обещала.

#27/#33 docker inspect по несуществующему контейнеру печатает в stdout пустую
  строку ДО ошибки → `|| echo absent` давал "\nabsent": ложный ✘ «node-агент
  лежит» на панель-боксе и НЕВАЛИДНЫЙ --json (сырой \n внутри значения ронял
  парсер на всём документе). Нормализуем `tr -d '[:space:]'` + пусто→absent в
  обеих ветках, ВСЕ строковые значения json — через json_escape. Новая ручка
  NA_NODE_CONTAINER: на панели/CDN-origin контейнер зовётся иначе.
#30 юнит na-rps active при пустом rps_cpus — ▲ «гонка с default route на буте»,
  а не ✔ рядом с «RPS выключен» (две строки в секции утверждали разное).
#31 датчик CONN_LIMIT считал весь `ss state established`: loopback-пары
  nginx↔xray (каждая дважды), исходящие плечи и вайтлист — то, до чего правило
  `ct count` не доходит вовсе. Считаем ТОТ ЖЕ срез: входящие на порты из
  tcp_ports= маркера, без 127/8, ::1 и членов whitelist (v4 — с CIDR).
#32/#36 счётчики autoban/suspect/phantom/blocklist/fleet считали строки вывода
  nft («flags dynamic,timeout» = вечная фантомная 1) → nft_set_count; при
  ненулевом autoban печатаем сами адреса. CrowdSec decisions без строки-
  заголовка CSV (пустой список читался как «один бан есть»).
#34 conf, пиннящий устаревший дефолт (CROWDSEC_STRICT 0→1 в v4.0), виден как ▲.
#35 глубина журнала ВО ВРЕМЕНИ (< 48ч = ▲) и объём лога анти-скана: датчик по
  размеру спокоен, пока [na portscan] вытесняет журнал за сутки.
#37 PSI: «собран, но выключен по умолчанию» (XanMod, CONFIG_PSI_DEFAULT_DISABLED)
  ≠ «старое ядро» — сенсор давления молчал на 100% боксов и выглядел штатно.
#38 whitelist сверяется с ТРЕМЯ источниками: live == .nft → переживёт ребут;
  live ⊆ WHITELIST= из protect.conf → переживёт ре-ран protect. Дрейф называется
  по адресу, IP текущей сессии опознаётся явно.
#39 серты: + /opt/<стек>/certs/*/fullchain.{cer,pem}; снятые с renew acme-серты
  (<domain>.conf.removed) пропускаются; «сертов нет, а :443 слушает» — ▲, а не
  тихое info.

Новые поля --json (только в хвост, старые имена не тронуты): max_conn_per_ip,
journal_span_h, portscan_log_lines_boot, psi, whitelist_drift_conf,
conf_stale_defaults, cert_min_file. −1 = «не измерено», а не «ноль».

na-report: одна info-строка, когда колонка ASN останется «?» из-за отсутствия
dig/whois (на соседних нодах отчёт выглядел иначе без объяснений).

tests/diagnose-unit.sh: ИСПОЛНЯЕТ обе ветки diagnose в песочнице против стабов
docker/nft/ss/cscli/journalctl/systemctl — на коде v4.0.1 падает 38 проверками.
…ряют тот же срез, что и правила; честный --json
@jestivald
jestivald merged commit 427b13f into main Sep 2, 2026
6 checks passed
@jestivald
jestivald deleted the v4.1-fleet-audit branch September 2, 2026 11:30
This was referenced Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

diagnose: счётчики autoban/suspect считают строки вывода nft, а не адреса — вечная «1» на пустом наборе diagnose: датчик CONN_LIMIT считает loopback, вайтлист и исходящие — постоянная ложная тревога optimize/na-rps: юнит рапортует success при неприменённом RPS — гонка с появлением default route optimize: tput civis/cnorm без проверки TTY — escape-коды в логах при запуске под nohup / в файл diagnose: на хосте без контейнера remnanode сенсор даёт ложный ✘ и ломает --json (docker inspect печатает пустую строку перед ошибкой)

1 participant