BitPage

CrowdSec на Debian 13: пять граблей, которых нет в туториалах

 · 7 мин чтения

Короткий ответ: CrowdSec на Debian 13 (trixie) ставится за вечер, но три вещи ведут себя не так, как в гайдах: OpenSSH 9.8 пишет логи от имени sshd-session, а не sshd; ssh-логи надо забирать из journald, потому что /var/log/auth.log больше не главный источник; бан-таблица nftables создаётся в семействе ip, а не inet — и её легко «не найти». Плюс два организационных правила: whitelist со своими IP — до первого запуска, на боевой прод — сначала режим detect-only без банов.

На хостах у меня много лет жил самодельный слой защиты: nginx-конфиг с блокировкой по User-Agent и крон, который раз в сутки тянул список Tor-exit-нод и вбивал его в nginx через deny. При переезде на Debian 13 я наконец разобрался, чего этот слой стоит, выкинул его и поставил CrowdSec с nftables-bouncer’ом. По дороге собрал пять граблей — ни одной из них нет в туториалах.

Коротко (TL;DR)

Почему крон с Tor-блоклистом — не защита

Ежедневная выгрузка Tor-нод в nginx deny — ритуал, а не защита, и держать его в 2026 году нет смысла. Разбор моего крона показал три проблемы. Во-первых, он закрывает только Tor, а реальные атаки приходят с VPN, облачных провайдеров и ботнетов — по ним списка нет. Во-вторых, после обновления списка крон делал nginx restart вместо reload — раз в сутки рвались все живые соединения. В-третьих, гигантский deny-список nginx проверяет линейно на каждый запрос.

То есть слой создавал видимость работы: что-то качается, что-то блокируется, в логах движение. А фактическую атаку — перебор паролей ssh, сканирование nginx — он не видел вовсе.

CrowdSec или fail2ban

Я выбрал CrowdSec: он закрывает те же сценарии, что fail2ban, плюс даёт краудсорс-репутацию IP, а риск «вендор умрёт» снимается лицензией и локальной архитектурой. Сравнение по пунктам, которые важны были мне:

fail2banCrowdSec
Механизм детектаregex по строкам логапарсеры + сценарии (leaky bucket), многострочная логика
Источники логовфайлыфайлы, journald, docker и другие
Механизм банасам пишет правила в iptables/nftablesотдельный bouncer со своей nft-таблицей
Репутация сообществанеткраудсорс-блоклисты (обмен сигналами)
Работа без вендораполная автономияполная: ядро под MIT, решения принимает локальный LAPI

Последняя строка была для меня решающей. Был страх: CrowdSec — компания, а вдруг она закроется. Проверил: ядро CrowdSec распространяется под MIT, детект и баны работают полностью локально через Local API. Если компания исчезнет, потеряется только краудсорс-репутация — сам IPS продолжит работать.

Как поставить: агент, коллекции, bouncer

Порядок такой: сначала агент с коллекциями и whitelist’ом, обкатка без банов, и только потом bouncer.

  1. Подключить репозиторий CrowdSec и поставить агент: apt install crowdsec.
  2. Поставить коллекции под свой стек: cscli collections install crowdsecurity/sshd crowdsecurity/nginx crowdsecurity/linux.
  3. Настроить acquisition: ssh — через journald, nginx — по glob лог-файлов (ниже).
  4. Добавить whitelist со своими IP и внутренними сетями (ниже, шаг обязательный).
  5. Перезапустить агент и проверить cscli metrics — логи должны читаться.
  6. Обкатать detect-only: агент детектит и пишет решения в cscli decisions list, но банить некому.
  7. Когда ложных срабатываний нет — поставить bouncer: apt install crowdsec-firewall-bouncer-nftables.

Acquisition для ssh на Debian 13 надёжнее делать через journald, а не через файл (документация CrowdSec по journald-источнику):

1
2
3
4
5
6
# /etc/crowdsec/acquis.d/ssh.yaml
source: journalctl
journalctl_filter:
  - "_SYSTEMD_UNIT=ssh.service"
labels:
  type: syslog

Грабли 1: cscli metrics показывает ssh-логи как unparsed

Строки «unparsed» в cscli metrics по ssh — не признак поломки: парсер намеренно не разбирает записи об успешных логинах. Я на это попался: увидел ssh-логи в колонке unparsed и решил, что парсер не работает на свежем стеке.

Причин запутаться сразу две. Первая — в release notes OpenSSH 9.8 сказано прямо: «the server has been split into a listener binary, sshd(8), and a per-session binary “sshd-session”». То есть строки о сессиях в логе теперь идут от процесса sshd-session, и любой фильтр, который ждёт sshd[, молча перестаёт находить их. Вторая — на Debian 13 auth-события живут в journald, и привычки смотреть /var/log/auth.log больше недостаточно.

Проверить, что детект жив, можно за минуту — скормить парсеру строку неудачного логина:

1
cscli explain --type syslog --log "Jul 23 10:00:00 host sshd-session[1234]: Failed password for root from 198.51.100.7 port 44444 ssh2"

В выводе видно всю цепочку: строка парсится и попадает во все ssh-bruteforce-сценарии. Детект работал с самого начала — вводила в заблуждение метрика.

Грабли 2: nft list table inet crowdsec пустой

Bouncer создаёт таблицы в семействах ip и ip6, а не inet — проверять бан надо командой nft list table ip crowdsec. Я после первого бана полез в inet, увидел пустоту и решил, что баны не работают.

Документация firewall-bouncer это подтверждает: для IPv4 создаётся table ip crowdsec с сетом crowdsec-blacklists, для IPv6 — table ip6 crowdsec6. Рабочие команды диагностики:

1
2
3
cscli decisions list            # что забанено с точки зрения CrowdSec
nft list table ip crowdsec      # что реально в firewall (IPv4)
nft list table ip6 crowdsec6    # то же для IPv6

Отдельная таблица — это и ответ на вопрос «не подерётся ли bouncer с моим кастомным firewall». На моих нодах уже был свой набор nftables-правил, и я боялся, что новый инструмент его сломает. Не подрался: у bouncer’а своя таблица, оба хука input отрабатывают независимо, и DROP в любой из таблиц финален. Существующие правила я не трогал вообще.

Грабли 3: смена порта в sshd_config не работает

Если ssh запущен через socket-активацию systemd, параметр Port в sshd_config игнорируется — слушающий сокет описан в юните ssh.socket, и менять порт надо там либо возвращать классический service-режим. Я в рамках той же чистки решил увести ssh со стандартного порта, поправил конфиг, сделал рестарт — порт не изменился.

Диагноз ставится одной командой: systemctl status ssh.socket. Если сокет активен — правки Port в конфиге бесполезны. Я вернул службу в обычный режим:

1
2
systemctl disable --now ssh.socket
systemctl enable --now ssh.service

После этого sshd_config снова источник правды. Прямого отношения к CrowdSec эти грабли не имеют, но наступил я на них в той же сессии, и в паре с «логи теперь от sshd-session» они складываются в общий вывод: на свежем Debian старые привычки вокруг ssh стоит перепроверять по одной.

Почему whitelist нужен до первого запуска

Без whitelist CrowdSec забанит ваш собственный IP при первой же проверке типа «а задетектит ли он брутфорс» — и вы останетесь без доступа к хосту. Документация CrowdSec определяет их так: «Whitelists are special parsers that allow you to “discard” events» — событие отбрасывается на этапе парсинга и до сценариев не доходит.

Минимальный parser-whitelist:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/crowdsec/parsers/s02-enrich/01-my-whitelist.yaml
name: my/whitelist
description: "Не банить себя и внутренние сети"
whitelist:
  reason: "admin IPs and internal networks"
  ip:
    - "127.0.0.1"
  cidr:
    - "10.0.0.0/8"
    - "198.51.100.0/24"   # сюда — свои админские сети

Меня этот файл однажды спас. На одном из хостов я забыл про план «сначала без банов» и поставил bouncer сразу — CrowdSec встал в боевой режим на работающем проде. Обошлось: whitelist не дал системе залочить меня самого, а забанены оказались только реальные сканеры. Но полагаться на такое везение на хосте с клиентским трафиком нельзя.

Как раскатывать на прод: сначала detect-only

На критичный прод CrowdSec ставится в два этапа: сначала агент без bouncer’а — детект есть, банов нет; и только после недели чистых логов — bouncer с реальными банами. «Режим» здесь — просто отсутствие пакета crowdsec-firewall-bouncer-nftables: агент пишет решения в cscli decisions list, а исполнять их некому.

Логика простая. Ложный бан на внутреннем хосте — неприятность для админа. Ложный бан на edge-ноде — заблокированный клиент, то есть деньги. Поэтому раскатку по парку я в Ansible-роли развёл переключателем: некритичные хосты получают агент и bouncer сразу, боевые — сначала только агент. Список хостов под IPS — явным перечнем в плейбуке: попытка вывести «умные» группы под каждый инструмент — лишняя инженерия, которая на десятке хостов ничего не даёт.

Итог

Если ставите CrowdSec на Debian 13 — закладывайте вечер и держите под рукой три команды: cscli explain (проверить, что детект жив, не доверяя колонке unparsed), nft list table ip crowdsec (смотреть баны в правильном семействе) и systemctl status ssh.socket (понять, кто на самом деле слушает порт). Whitelist со своими IP — до первого запуска, bouncer на боевом проде — только после обкатки без банов. А самодельные блоклисты типа «крон с Tor-нодами» спокойно выкидывайте: после разбора оказалось, что мой не защищал ничего — только рестартовал nginx раз в сутки.

#CrowdSec #Debian #nftables #безопасность #self-hosted

<< Previous Post

|

Next Post >>