Repository navigation
v4.1 — аудит флота: честные сенсоры, дрейф дефолтов conf, RPS без гонки, рейт лога анти-скана - #42
Merged
Merged
Conversation
…дрейф дефолтов 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 проверок).
…, гигиена whitelist
…LIST; README: строка WHITELIST
…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 проверками.
This was referenced Sep 2, 2026
optimize: tput civis/cnorm без проверки TTY — escape-коды в логах при запуске под nohup / в файл
#28
Closed
optimize/na-rps: юнит рапортует success при неприменённом RPS — гонка с появлением default route
#30
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Релиз по итогам аудита боевого флота на v4.0.1 (11 нод + панель, Debian 12/13, XanMod 6.18). Десять issue, почти все — про то, что тулкит врал о своём состоянии: сенсоры считали не то, что обещали, юнит рапортовал успех при неприменённом тюнинге, сохранённый конфиг молча держал дефолты прошлой версии. Полный разбор — в
CHANGELOG.md(секция v4.1).Что меняется
save_confпишет# explicit: KEYдля ключей из ENV и# na_version=;load_confпредупреждает о замороженном старом дефолте (CROWDSEC_STRICT=0с v3.9) и принимает новые поNA_ADOPT_NEW_DEFAULTS=1; сенсор вna-diagnose+ полеconf_stale_defaults. ТаблицаNA_DEFAULT_CHANGESвlib/common.sh.--jsonна хосте без контейнера.docker inspectна 29.x печатает\nдо ошибки →"\nabsent"→ невалидный JSON и ложный ✘. Нормализация + все строковые поля черезjson_escape. РучкаNA_NODE_CONTAINER.grep -c timeoutсчитал строку заголовка набора: «1» на пустом, +1/+2 на непустом;na-fw-statusиna-diagnoseрасходились. Единыйnft_set_count(адреса вelements = { … }), вna-fw-statusвшиваетсяdeclare -f. CrowdSec: заголовок CSV больше не в счёте. При ненулевом autoban печатаются адреса.CONN_LIMITсчитает тот же срез, что правилоct count: входящие наtcp_ports=, без loopback/whitelist (CIDR учитываются). Полеmax_conn_per_ip.na-rps.serviceполучает NIC аргументом, ждёт default route до 20 с, фолбэк на физические интерфейсы, честныйexit 1+Restart=on-failure.na-diagnoseне пишет «✔ активен» при пустомrps_cpus.PORTSCAN_LOG_RATE=60/мин (PORTSCAN_LOG_BURST=30) вместо5/second(432k строк/сутки → журнал 300M жил <суток);0— не логировать (бан по meter'ам не зависит).protectсчитает бюджет против капа journald;NA_JOURNAL_MAX_USEвoptimize;journal_span_hв diagnose.CONFIG_PSI_DEFAULT_DISABLED=yу XanMod);ENABLE_PSI=1вoptimizeдописываетpsi=1в GRUB (opt-in, ребут),rollbackснимает только свой.protect.conf): «переживёт ребут» vs «переживёт ре-ран protect», адреса по имени;add_wl— дедуп, нормализация/32//128, warn о префиксах шире/29(v6/64); CrowdSec-yaml строится из разобранного списка./opt/*/certs/**/fullchain.{cer,pem}, каталоги acme.sh снятые с renew (*.conf.removed) пропускаются, «сертов нет + :443 слушает» → ▲. Полеcert_min_file.tty_tput/is_tty: никаких escape-кодов курсора не в терминале, прогресс-бар не рисуется.Тесты
Новые:
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 закрыты ранее.)