Роками периметр моєї інфраструктури захищав Fail2Ban. Він працював добре: SSH був прикритий, поштові сервіси захищені, а кілька критичних додатків мали власні ізольовані «в'язниці» (jails).

У мене не було жодних причин шукати йому заміну.

Проблема виникла поступово, у міру того як розвивалася сама інфраструктура.

Сьогодні один шлюз Nginx маршрутизує трафік до приблизно двадцяти публічних сервісів: GLPI, систем моніторингу, API, бізнес-додатків та кількох внутрішніх служб, опублікованих через реверс-проксі.

У цей момент я зрозумів, що більше не керую окремим веб-додатком. Я захищаю цілу платформу.

Завданням стало не просто блокування окремих IP-адрес, а розуміння того, що саме відбувається на всьому HTTP-рівні шлюзу.

Саме це змусило мене шукати інструмент, який би доповнив Fail2Ban, а не замінив його.

Чому Fail2Ban стало важко масштабувати

Fail2Ban чудовий для служб із передбачуваними логами автентифікації: SSH, Postfix, Dovecot, OpenVPN. Ці сервіси генерують чітко структуровані записи про помилки входу, які легко ловити регулярними виразами.

З HTTP-додатками все інакше. Кожен додаток має власні ендпоінти, фреймворки повертають різні HTTP-коди, логіка входу відрізняється, а плагіни додають унікальні URL-адреси. Деякі сервіси активно використовують AJAX, інші — REST API. Хтось повертає HTTP 403, а хтось завжди віддає HTTP 200 із повідомленням про помилку всередині тіла відповіді.

У міру зростання кількості сервісів за одним реверс-проксі кількість кастомних правил (jails) та регулярних виразів почала стрімко збільшуватися. Їх підтримка почала вимагати більше зусиль, ніж я очікував.

Що важливіше, кожне правило Fail2Ban представляло собою ізольоване правило виявлення. Не існувало спільного поведінкового контексту безпеки на рівні всього HTTP-шлюзу.

Дивитися глибше за простий збіг рядків логів

Я не шукав черговий двигун для регулярних виразів. Мені потрібне було рішення, здатне оцінювати патерни поведінки HTTP-трафіку.

Різниця тут принципова. Fail2Ban відповідає на просте запитання: «Чи маю я забанити цей IP?»

CrowdSec намагається відповісти на інше: «Чи відповідає ця послідовність запитів сценарію атаки?»

Замість аналізу окремих ізольованих рядків логів, він корелює множинні запити в часі та оцинює їх на основі поведінкових сценаріїв. Ця невелика архітектурна відмінність кардинально змінює підхід до розслідування атак.

Архітектура CrowdSec

Поширена помилка — думати, що CrowdSec блокує трафік самостійно. Це не так. Він побудований із кількох незалежних компонентів:

                 Nginx Access Logs
                         │
                         ▼
                  CrowdSec Agent
      Parsers → Enrichment → Detection Scenarios
                         │
                         ▼
                 Local API (LAPI)
                         │
               Firewall Bouncer
              (iptables / nftables)
                         │
                         ▼
                 Incoming traffic blocked

Кожен компонент має свою зону відповідальності. Агент читає логи, парсить та збагачує дані. Сценарії виявляють підозрілу активність. Отримані рішення (decisions) записуються в Local API (LAPI). Нарешті, Bouncer (елемент блокування) застосовує ці рішення.

У моєму випадку використання Firewall Bouncer означає, що блокування відбувається на мережевому рівні (iptables/nftables) ще до того, як шкідливий запит вдруге досягне Nginx. Інші інсталяції можуть використовувати Nginx Lua bouncers для блокування безпосередньо на рівні проксі. Таке розділення робить архітектуру гнучкою: двигуну виявлення байдуже, як саме блокується трафік.

Колективна безпека (Community Blocklist)

Поведінковий аналіз — це лише частина історії. Найбільше мене залучила функція Community Blocklist.

Кожна інсталяція CrowdSec може анонімно передавати інформацію про заблоковані IP-адреси до центральної системи. Після перевірки ці дані стають частиною глобальної системи колективної репутації та аналізу загроз (threat intelligence).

Результат вражає: частина атакуючих IP-адрес блокується на моєму шлюзі ще до того, як вони зроблять перший запит до моєї інфраструктури. Мій сервер виграє від атак, які вже були зафіксовані десь в іншій частині світу.

Fail2Ban є суто локальним захисником. CrowdSec додає шар колективної безпеки поверх локальних правил. Для невеликих інфраструктур це дає значну цінність з дуже невеликими операційними витратами порівняно з повноцінними системами threat intelligence.

Побудова Observability навколо безпеки

Встановлення CrowdSec — це лише половина справи. Друга половина — це видимість.

У моїй інфраструктурі вже працював стек Prometheus, Grafana, Loki та Node Exporter. Замість впровадження окремого важкого сервісу моніторингу я написав легкий скрипт, який опитує CrowdSec Local API (LAPI) та формує метрики в Prometheus-форматі.

Для запуска скрипта я налаштував systemd-сервіс, який тригериться через `systemd.timer` кожні 60 секунд. На відміну від cron, це дає ідеальну простежуваність: кожен запуск, час роботи та статус виходу скрипта логуються безпосередньо в journald.

Скрипт записує дані у `.prom` файл, який зчитується Node Exporter через textfile collector:

    [ systemd.timer (60s) ]
               │
               │ (triggers)
               ▼
     [ Exporter Script ] ◄─── (queries) ─── [ CrowdSec LAPI ]
               │
               │ (writes metrics)
               ▼
   [ crowdsec_decisions.prom ]
               │
               │ (reads file)
               ▼
  [ Node Exporter (textfile) ]
               │
               │ (scrapes port 9100)
               ▼
     [ Prometheus Server ]

Тепер я можу моніторити активні рішення, сценарії атак, країни та автономні системи (ASN) атакуючих, конкретні заблоковані IP-адреси та історичні тренди. Дашборди більше не показують лише «здоров'я» серверів — вони демонструють реальне вороже середовище, в якому щодня працює інфраструктура.

CrowdSec Grafana Dashboard

Ось приклад того, як виглядають реальні заблоковані загрози в реальному часі через інтерфейс командного рядка:

──┬────────────┬──────────╮
│   ID  │  Source  │     Scope:Value    │                 Reason                │ Action │ Country │                              AS                              │ Events │ expiration │ Alert ID │
├───────┼──────────┼────────────────────┼───────────────────────────────────────┼────────┼─────────┼──────────────────────────────────────────────────────────────┼────────┼────────────┼──────────┤
│ 75138 │ crowdsec │ Ip:129.153.93.55   │ crowdsecurity/http-bad-user-agent     │ ban    │ US      │ 31898 ORACLE-BMC-31898                                       │ 2      │ 11h56m48s  │ 151      │
│ 75137 │ crowdsec │ Ip:20.205.229.184  │ crowdsecurity/http-probing            │ ban    │ SG      │ 8075 MICROSOFT-CORP-MSN-AS-BLOCK                             │ 11     │ 3h27m7s    │ 150      │
│ 75136 │ crowdsec │ Ip:23.161.169.127  │ crowdsecurity/http-crawl-non_statics  │ ban    │ GB      │ 400529 INFRALY-LLC                                           │ 49     │ 11h25m15s  │ 149      │
│ 75133 │ crowdsec │ Ip:158.158.41.78   │ crowdsecurity/http-crawl-non_statics  │ ban    │ ES      │ 8075 MICROSOFT-CORP-MSN-AS-BLOCK                             │ 48     │ 19h19m49s  │ 146      │
│ 75128 │ crowdsec │ Ip:52.165.196.84   │ crowdsecurity/http-probing            │ ban    │ US      │ 8075 MICROSOFT-CORP-MSN-AS-BLOCK                             │ 11     │ 2h37m33s   │ 141      │

Несподіваний урок із практики

Одного дня CrowdSec заблокував легітимного користувача GLPI. На перший погляд, це виглядало як помилкове спрацьовування (false positive).

Замість того, щоб просто вимкнути правило, я вирішив розібратися. Логи Loki містили повну історію HTTP-запитів. Grafana чітко показала час створення блокування, а Nginx розкрив послідовність дій: користувач натрапив на баг у фронтенді додатку, який почав циклічно тригерити один і той самий AJAX-ендпоінт, що повертав помилку. Це згенерувало десятки HTTP 403 відповідей за короткий проміжок часу.

З точки зору CrowdSec, це виглядало як класичний підбір паролів (brute force). Двигун виявлення не помилився — поведінка додатку дійсно збіглася зі сценарієм атаки.

Рішенням було не вимкнення захисту, а коригування порогів чутливості (thresholds) сценарію під особливості моїх додатків. Цей інцидент довів важливе правило: інструментам безпеки набагато легше довіряти, коли ти маєш хорошу видимість (observability) їхньої роботи.

Що насправді показали мої дашборди

Цікаве спостереження з'явилося після кількох днів детального моніторингу.

Спочатку мені здавалося, що більшість атак відбувається вночі. Але це виявилося неправдою. Автоматичне сканування не зупиняється ні на секунду. Боти цілодобово шукають у мережі вразливі додатки, відкриті адміністративні панелі та свіжі CVE.

Змінюється лише одне — пропорція корисного навантаження. Вночі активність легітимних користувачів падає майже до нуля, і на цьому фоні «білий шум» інтернету стає максимально помітним на графіках. Без детальних дашбордів я б ніколи не побачив цієї різниці.

Інша зміна стосується того, хто саме сканує мережу. Роками раніше фоновий шум інтернету асоціювався з «підозрілими» домашніми IP-адресами з конкретних країн. Сьогодні ж сканери працюють у самому серці сучасного вебу: AWS, Azure, Oracle Cloud та DigitalOcean.

Як видно з наведеного вище консольного виводу, заблоковані хости належать не сумнівним провайдерам, а Oracle BMC та Microsoft Corp. Зловмисники зламують хмарні сервери або орендують дешеві короткоживучі віртуальні машини. Це робить класичний гео-бан (GeoIP blocking) неефективним — заблокувавши певні хмари чи країни повністю, ви ризикуєте зламати власні легітимні інтеграції.

Що я налаштував після базового встановлення

Дефолтне налаштування CrowdSec працює «з коробки», але в продакшн-середовищі я вніс кілька критичних змін:

1. Прогресивне блокування (Ban Progression)

Базові налаштування дають фіксований час бану. Я налаштував прогресивну тривалість блокування: перше порушення — короткий тимчасовий бан (наприклад, 4 години), повторні інциденти з тієї ж адреси — експоненціально довші терміни бану. Мета — не витрачати ресурси ядра та фаєрволу на постійну обробку одних і тих самих ботів.

2. Небінарне реагування (тести CAPTCHA)

CrowdSec підтримує різні методи протидії. Замість миттєвого повного бану для підозрілих джерел першою відповіддю системи може бути перевірка (challenge), наприклад CAPTCHA.

Можливий сценарій безпеки:

  • — Звичайний трафік ➔ дозволити;
  • — Підозріла активність ➔ вимагати додаткову верифікацію (CAPTCHA);
  • — Повторна шкідлива поведінка ➔ тимчасовий бан;
  • — Персистентні атаки ➔ довгострокове блокування.

Такий підхід суттєво зменшує вплив помилкових спрацьовувань, зберігаючи захист активним. Для публічних додатків, де працюють зовнішні користувачі, це набагато практичніше за миттєвий бан: реальний користувач, який випадково тригернув правило, отримає шанс довести, що він людина, тоді як боти та сканери просто зламають свої скрипти на цій перевірці. Важлива ідея полягає в тому, що рішення безпеки не обов'язково мають бути бінарними: "дозволити або заблокувати".

3. Адаптація сценаріїв під додатки

Стандартні сценарії захисту не знають логіки вашого софту. Як показав приклад із GLPI, будь-який сервіс з активним AJAX або частими автоматизованими API-запитами рано чи пізно викличе помилкові спрацьовування. Я відкоригував ліміти спрацьовування сценаріїв для специфічних URL-адрес.

4. Розділення виявлення та блокування (Режим симуляції)

Я не вмикав блокування для всіх правил одразу. При додаванні нових сценаріїв безпеки корисно спочатку запускати їх у режимі симуляції (simulation mode / dry-run). Це дозволяє зібрати статистику помилкових спрацьовувань для легітимних інтеграцій до того, як вони будуть реально заблоковані bouncer-ом.

Чому я все ще використовую Fail2Ban

CrowdSec не замінив Fail2Ban в моїй інфраструктурі, він просто змінив його роль. Сьогодні їхні сфери відповідальності чітко розділені:

  • Fail2Ban захищає класичні системні служби, де автентифікація відбувається в межах чітких протоколів: SSH, Postfix, Dovecot.
  • CrowdSec захищає HTTP-рівень за реверс-проксі Nginx.

Кожен інструмент вирішує ту проблему, для якої він створювався. Спроба змусити один із них робити все лише збільшила б складність системи.

Де CrowdSec доречний (а де ні)

Не кожній інфраструктурі потрібен CrowdSec. Великі корпорації будують складніші системи: Enterprise WAF, комерційні SIEM-платформи, керовані SOC-центри та аналітику загроз. Ці рішення пропонують можливості іншого масштабу, але вимагають великих бюджетів та команд для підтримки.

Для невеликих та середніх проектів компроміс виглядає інакше. CrowdSec займає ідеальну нішу: він дає поведінковий аналіз, колективний інтелект та централізований моніторинг без необхідності розгортати складні корпоративні монстри.

Замість висновків

Коли я починав цей проект, я думав, що покращую свій фаєрвол. Оглядаючись назад, я розумію, що покращив свою обсервабіліті (observability).

Найціннішим результатом стало не блокування більшої кількості IP-адрес. Ним стало розуміння того, як поводиться моя інфраструктура під постійним фоновим скануванням, як додатки взаємодіють із фільтрами безпеки, і де мої власні припущення про «нормальну поведінку систем» виявилися хибними.

CrowdSec не просто додав ще один рівень захисту. Він дав мені значно чіткішу картину інфраструктури, яку я вже побудував.