Введение#
В части 2 в разделе “что осталось” я отложил установку CrowdSec на отдельный заход . Вот тот самый заход. CrowdSec ставится в тот же LXC, что и сам Traefik, а не отдельным контейнером в Docker, как было раньше. Это упрощает связность (никаких Docker DNS-имен, все общается через localhost) и позволяет CrowdSec читать access.log напрямую с диска, без пробрасывания файла между разными окружениями.
Обновлено 9 октября 2026. С выходом CrowdSec 1.8 в WAF появилась защита от ботов через проверочную страницу (bot challenge) - ей посвящен новый раздел «Защита от ботов» ближе к концу статьи. Заодно исправлен источник AppSec: коллекция appsec-crs в прежней версии статьи устанавливалась, но ее правила не подключались.
Установка#
CrowdSec ставится через официальный скрипт добавления репозитория, дальше - обычным apt:
curl -s https://install.crowdsec.net | sudo sh
apt update
apt list crowdsec
apt install crowdsecapt list crowdsec - необязательный шаг, просто чтобы убедиться, что пакет реально появился в списке после добавления репозитория, прежде чем ставить.
Коллекции#
Коллекция - это готовый набор сценариев обнаружения под конкретный тип нагрузки. Перед установкой обновляю индекс хаба, чтобы точно ставить актуальные версии, а не то, что было зашито в пакет на момент сборки:
cscli hub updateСтавлю сразу пакет под HTTP-периметр (Traefik), плюс базовые сценарии для самой ОС:
cscli collections install crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/base-http-scenarios crowdsecurity/sshd crowdsecurity/linux crowdsecurity/appsec-generic-rules crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crscrowdsecurity/traefik- парсер access-логов конкретно под формат Traefik;crowdsecurity/http-cve+base-http-scenarios- обнаружение попыток эксплуатации известных CVE и типовых HTTP-атак;crowdsecurity/sshd+linux- раз CrowdSec теперь живет в том же LXC, что и Traefik, заодно прикрываю и сам SSH-доступ к контейнеру;appsec-generic-rules,appsec-virtual-patching,appsec-crs- под AppSec-компонент (WAF-подобная защита на уровне запроса, до того как он дойдет до бэкенда) - про него отдельно ниже. Первые две коллекции подключаются общим конфигомcrowdsecurity/appsec-default, а правила OWASP CRS изappsec-crs- отдельным конфигомcrowdsecurity/crs, который нужно явно перечислить в источнике AppSec (об этом тоже ниже).
systemctl reload crowdsecАвтообновление hub по расписанию#
Сценарии и парсеры в хабе периодически обновляются (новые CVE, поправки в существующих детекциях), а вручную про cscli hub update вспоминаешь редко. Выношу в cron-файл, чтобы обновление индекса и апгрейд установленных коллекций происходили сами раз в сутки, в полночь:
nano /etc/cron.d/crowdsec-hub-update0 0 * * * root /usr/bin/cscli hub update && /usr/bin/cscli hub upgradeФайл в /etc/cron.d/ не требует crontab -e и не привязан к пользовательскому crontab - подхватывается системным cron автоматически после сохранения, без дополнительной перезагрузки демона. hub upgrade подтягивает новые версии уже установленных коллекций/парсеров/сценариев (не ставит новые - только обновляет то, что уже включено). Если апгрейд что-то реально поменял, cscli сам попросит systemctl reload crowdsec - это можно либо добавить третьей командой в ту же строку cron, либо, как я предпочитаю, оставить на ручной контроль, чтобы не перезагружать конфигурацию боевого сервиса без просмотра, что именно обновилось.
Профиль реагирования#
Профиль определяет, что конкретно делать, когда CrowdSec принимает решение о блокировке - не просто “забанить”, а по какой логике и куда уведомить.
nano /etc/crowdsec/profiles.yaml:
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
notifications:
- http_default
on_success: break
---
name: default_range_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
notifications:
- http_default
on_success: breakДва профиля - один на бан конкретного IP, другой на бан целого диапазона (если атака идет распределенно с одной подсети). Оба банят на 4 часа и шлют уведомление через плагин http_default, который настраивается отдельно.
Уведомления через Gotify#
Раз у меня в стеке уже есть Gotify (пуш-уведомления на телефон/десктоп) - логично заводить туда и алерты CrowdSec, вместо того чтобы заново поднимать email-интеграцию (другие варианты в России на дату написания статьи официально не работают).
nano /etc/crowdsec/notifications/http.yaml:
type: http
name: http_default
log_level: info
format: |
{{ range . -}}
{{ $alert := . -}}
{
"extras": {
"client::display": {
"contentType": "text/markdown"
}
},
"priority": 3,
{{range .Decisions -}}
"title": "{{.Type }} {{ .Value }} for {{.Duration}}",
"message": "{{.Scenario}} \n\n[crowdsec cti](https://app.crowdsec.net/cti/{{.Value -}}) \n\n[shodan](https://shodan.io/host/{{.Value -}})"
{{end -}}
}
{{ end -}}
url: https://gotify.domain.ru/message
method: POST
headers:
X-Gotify-Key: ваш_application_token
Content-Type: application/jsonШаблон формирует Gotify-совместимый JSON: заголовок с типом и значением решения (IP/диапазон, длительность бана), тело сообщения со сценарием, который сработал, плюс сразу готовые ссылки на CrowdSec CTI и Shodan для быстрой проверки адреса вручную.
X-Gotify-Key - это application token конкретного приложения в Gotify (создается в его собственном UI), не токен пользователя.
Источники логов (acquisition)#
CrowdSec с версии, которую мы ставим, использует директорию /etc/crowdsec/acquis.d/ - отдельный файл на каждый источник, вместо одного монолитного acquis.yaml. Логика та же, что и с разбивкой dynamic/ у самого Traefik: читать и поддерживать проще, когда источники не свалены в одну кучу.
Access-логи Traefik#
touch /etc/crowdsec/acquis.d/traefik.yamlpoll_without_inotify: false
filenames:
- /var/log/traefik/*.log
labels:
type: traefikpoll_without_inotify: false - использовать inotify (эффективнее, чем поллинг файла по таймеру) для отслеживания новых строк в логе.
Через маску *.log в этот источник попадает не только access.log, но и traefik.log (операционный лог самого Traefik - запуск, обновление конфигурации, выпуск TLS-сертификатов). Парсер crowdsecurity/traefik-logs рассчитан на формат access-лога, поэтому строки из traefik.log в cscli metrics будут честно показываться как “unparsed” - это ожидаемо, не ошибка конфигурации, и убирать traefik.log из источника необязательно.
Системные логи#
touch /etc/crowdsec/acquis.d/syslog.yamlfilenames:
- /var/log/auth.log
- /var/log/syslog
labels:
type: syslogЭто то, ради чего ставили коллекции sshd/linux - раз CrowdSec теперь в том же LXC, что принимает внешний трафик, разумно заодно приглядывать за попытками подбора SSH и подозрительной активностью на уровне ОС.
Debian 13 (trixie) - rsyslog не ставится по умолчанию#
На Debian 13 этот источник сам по себе работать не будет. Начиная с bookworm rsyslog больше не ставится по умолчанию - /var/log/auth.log и /var/log/syslog просто не существуют или не пишутся, если пакет не установлен явно. Проверить:
ls -la /var/log/auth.log /var/log/syslogЕсли auth.log отсутствует, а syslog есть, но нулевого размера - rsyslog не установлен:
apt install rsyslog
systemctl enable --now rsyslogДальше - ловушка конкретно для trixie: даже после установки пакет не создает файл с правилами маршрутизации /etc/rsyslog.d/50-default.conf, который раньше шел “из коробки”. Каталог /etc/rsyslog.d/ после установки пуст (кроме, возможно, postfix.conf, если Postfix тоже стоит). Без этих правил rsyslogd слушает журнал, но не знает, куда писать auth/authpriv-сообщения - файлы остаются пустыми. Создаем вручную:
nano /etc/rsyslog.d/50-default.confМинимум, нужный CrowdSec:
auth,authpriv.* /var/log/auth.log
*.*;auth,authpriv.none -/var/log/syslogsystemctl restart rsyslogПроверка - любое действие через sudo (например sudo -k && sudo whoami, чтобы сбросить кэш и заново пройти PAM) должно тут же появиться в auth.log:
ls -la /var/log/auth.log /var/log/syslogОба файла должны существовать и расти. После этого systemctl restart crowdsec и cscli metrics - в Acquisition Metrics должны появиться строки file:/var/log/auth.log и file:/var/log/syslog с ненулевым числом прочитанных строк.
systemctl reload crowdsec
cscli metricscscli metrics показывает, видит ли CrowdSec вообще какие-то строки из подключенных источников - если счетчики нулевые, то AppSec можно не начинать настраивать, сначала надо разобраться с acquisition.
AppSec-компонент#
AppSec - это WAF-подобная защита на уровне самого запроса (сигнатуры атак, virtual patching под известные CVE), работающая отдельно от классического anti-bruteforce анализа логов. Traefik обращается к нему напрямую через bouncer-плагин, до того как запрос уйдет к реальному бэкенду.
touch /etc/crowdsec/acquis.d/appsec.yamllisten_addr: 127.0.0.1:7422
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/crs
name: myAppSecComponent
source: appsec
labels:
type: appsecКонфигов здесь два, и это важно. crowdsecurity/appsec-default подключает правила из коллекций appsec-virtual-patching и appsec-generic-rules - они работают в режиме in-band, то есть проверяют запрос и блокируют его до того, как он уйдет к бэкенду. А правила OWASP CRS из коллекции appsec-crs подключаются только отдельным конфигом crowdsecurity/crs: без него коллекция установлена, но ее правила просто не загружаются. Причем CRS работает в режиме out-of-band - запрос не блокирует, а только поднимает алерт, по которому дальше срабатывают обычные сценарии CrowdSec. И это разумный вариант по умолчанию: правила CRS широкие, на реальных приложениях они нередко дают ложные срабатывания, и блокировать запросы сразу по ним было бы рискованно.
127.0.0.1, не 127.0.0.0 - последний является адресом самой сети 127.0.0.0/8, а не хостом, и не гарантированно забиндится корректно. В bouncer-плагине Traefik ниже используется localhost:7422, что резолвится в 127.0.0.1 - оба адреса должны совпадать буквально, иначе AppSec будет недоступен, а crowdsecAppsecUnreachableBlock: true в конфиге плагина заблокирует вообще весь трафик через Traefik, если компонент не отвечает.systemctl reload crowdsec
systemctl reload traefik
cscli metrics show acquisitionРегистрация bouncer’а и подключение к Traefik#
cscli bouncers add traefik-bouncerКоманда выведет API-ключ один раз - сохраните сразу, повторно посмотреть не получится (та же логика, что с токеном Cloudflare в части 2), только пересоздать заново.
Middleware в dynamic/middlewares/crowdsec.yaml:
http:
middlewares:
crowdsec:
plugin:
bouncer:
enabled: true
logLevel: INFO
updateIntervalSeconds: 15
updateMaxFailure: 0
defaultDecisionSeconds: 15
httpTimeoutSeconds: 10
crowdsecMode: stream
crowdsecAppsecEnabled: true
crowdsecAppsecHost: localhost:7422
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecLapiKey: ваш_ключ_из_cscli_bouncers_add
crowdsecLapiHost: localhost:8080
crowdsecLapiScheme: http
forwardedHeadersTrustedIPs:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
clientTrustedIPs:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16localhost вместо Docker-имени вроде crowdsec:8080, которое было бы в старой Docker-based установке - оба сервиса теперь в одном LXC, дополнительная сетевая связность не нужна вообще.
Последний шаг - раскомментировать middleware в static config (в части 2 он был закомментирован):
entryPoints:
web:
http:
middlewares:
- crowdsec@file # теперь можно раскомментировать
- rate-limit@file
websecure:
http:
middlewares:
- crowdsec@file # и здесь
- rate-limit@filesystemctl restart traefikЗащита от ботов - AppSec bot challenge#
Раздел добавлен 9 октября 2026.
В CrowdSec 1.8 у WAF появился новый режим - проверка на бота. Вместо того чтобы сразу пропустить или заблокировать запрос, AppSec отдает клиенту легкую проверочную страницу: браузер решает небольшую вычислительную задачу (proof-of-work) и собирает “отпечаток” окружения. Дальше CrowdSec оценивает отпечаток по набору признаков автоматизации - headless-браузер, следы Selenium, Playwright или WebDriver, нестыковки между тем, что браузер о себе сообщает, и тем, что он умеет на самом деле, - и складывает их в общий балл. Набрал балл выше порога - бот, не набрал - получает cookie и дальше ходит по сайту без проверок. По сути это собственный аналог проверки Cloudflare, только целиком на своем сервере.
Под эту функцию в хабе появились коллекции, и ставить нужно ровно одну:
| Коллекция | Порог отказа | Когда выбирать |
|---|---|---|
crowdsecurity/appsec-bot-challenge | 75 баллов | Рекомендуемый вариант по умолчанию |
crowdsecurity/appsec-bot-challenge-strict | 45 баллов | Ловит больше ботов, но и чаще ошибается на живых людях |
crowdsecurity/appsec-bot-challenge-permissive | 100 баллов | Отсекает только почти гарантированную автоматизацию |
Во все три уже входят исключения: проверенные “хорошие” боты (поисковики, ИИ-краулеры, рекламные и соцсетевые краулеры, мониторинг) и служебные пути, где проверочная страница точно все сломает, - robots.txt и подобные файлы для краулеров, статика, API, RSS-ленты и вебхуки. Две коллекции сразу ставить не нужно: документация прямо предупреждает, что пороги в таком случае складываются.
Что нужно, чтобы это заработало#
- CrowdSec не ниже 1.8.1. Сама функция появилась в 1.8.0, а в 1.8.1 уже исправлено ложное срабатывание на браузер Brave с включенной защитой Shields. Если ставили CrowdSec из официального репозитория, как в начале статьи, достаточно обычного
apt upgrade, проверить версию можно командойcscli version. - Bouncer, который умеет отдавать проверочную страницу. Плагин для Traefik научился этому только в версии v1.8.0, а во второй части серии у меня закреплена
v1.7.1. На момент обновления статьи v1.8.0 существует только как предварительный выпускv1.8.0-alpha.
Не включайте бот-челлендж со старым плагином. Документация CrowdSec предупреждает, что за bouncer’ом без поддержки этой функции она, скорее всего, начнет молча отклонять всех клиентов подряд. А с crowdsecAppsecFailureBlock: true из нашего конфига это означает, что лежать будут все сервисы за Traefik разом.
Кроме того, сам CrowdSec помечает бот-детекцию как функцию в статусе Alpha. Поэтому на момент обновления я бы относился к ней как к эксперименту: попробовать на одном некритичном сервисе можно, включать для всего homelab - рано.
Почему не стоит просто включить ее для всего сразу#
В моей схеме middleware crowdsec@file висит на entrypoint, то есть через AppSec проходят запросы ко всем сервисам за Traefik. А бот-челлендж - это проверочная страница с JavaScript, и пройти ее может только браузер. Мобильные приложения и другие программные клиенты получат вместо ответа сервера HTML-страницу, которую не смогут обработать. Встроенные исключения покрывают только типовые пути вроде /api/ и /webhooks/, а у многих self-hosted сервисов клиенты ходят и по другим адресам - например, мобильный Vaultwarden авторизуется через /identity/, а клиенты Gotify работают с /message и /stream. Есть и ограничения на стороне браузера: решению задачи нужна поддержка SSE4.1 и исполняемой памяти, так что очень старые или специально “закаленные” браузеры проверку не пройдут.
Поэтому сервисы, к которым ходят приложения, нужно исключить из проверки своим конфигом. Править файлы из хаба не нужно, это прямо оговорено в них самих, - создаем свой, например исключая целиком поддомены таких сервисов:
nano /etc/crowdsec/appsec-configs/my-bot-exclusions.yamlname: custom/my-bot-exclusions
inband:
pre_eval:
- filter: "req.Host in ['vault.domain.ru', 'gotify.domain.ru']"
apply:
- ExemptFromChallenge("my-apps")Какие еще адреса нужно исключить, лучше всего видно по access-логу Traefik: на что ходят ваши приложения, то и добавляйте.
Включение#
Когда плагин и CrowdSec нужных версий, порядок такой. Ставим коллекцию:
cscli collections install crowdsecurity/appsec-bot-challengeДобавляем конфиги бот-детекции и свои исключения в источник AppSec, /etc/crowdsec/acquis.d/appsec.yaml:
listen_addr: 127.0.0.1:7422
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/crs
- crowdsecurity/appsec-bot-*
- custom/my-bot-exclusions
name: myAppSecComponent
source: appsec
labels:
type: appsecМаска crowdsecurity/appsec-bot-* подхватывает и движок проверки (appsec-bot-challenge-scoring), и все исключения из коллекции. Это важно: конфиг -balanced сам по себе несет только порог отказа, а проверочную страницу выдает именно appsec-bot-challenge-scoring - без него AppSec будет обрабатывать запросы, но никого не проверять.
В static config Traefik меняем версию плагина:
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.8.0-alphaИ перезапускаем оба сервиса:
systemctl reload crowdsec
systemctl restart traefikПара мест, где все может сломаться без единой ошибки в логах:
- Роутер должен пропускать путь
/crowdsec-internal. Проверочная страница грузит свой скрипт и обработчик задачи по абсолютному пути/crowdsec-internal/challenge/.... Роутеры с правиломHost(...)пропускают любой путь, и с ними все в порядке. А если у роутера в правиле естьPathPrefix, то путь нужно добавить явно, иначе скрипт не загрузится и клиент так и останется на проверочной странице. crowdsecAppsecBodyLimitдолжен быть больше нуля - иначе решенная задача не дойдет до AppSec, и браузер будет крутиться на проверке по кругу. По умолчанию в плагине там 10 МБ, и в нашем конфиге он не переопределен, так что все в порядке, пока вы сами не поставите 0.- Заголовок
Content-Security-Policy. Если в ваших middleware заголовков задана строгая CSP, она может заблокировать работу проверочной страницы. Если проверка не проходит, начните с этого.
Проверка бот-детекции#
В логах CrowdSec после перезапуска должна появиться строка WAF challenge runtime initialized - значит, конфигурация подхватилась. Дальше:
cscli metrics show appsecСчетчик Ch. Requested должен расти вместе с запросами. Если Processed растет, а Ch. Requested стоит на нуле - запросы до AppSec доходят, но никого не проверяют, и ошибка в списке appsec_configs, а не в bouncer’е. Самая простая проверка снаружи - открыть сайт обычным браузером (проверочная страница мелькнет и пропустит дальше) и запросить его через curl (он так и останется на проверочной странице, потому что решить задачу не может - ровно это и нужно).
Проверка#
cscli decisions list # текущие активные баны
cscli metrics # общая статистика по сценариям и bouncer'ам
cscli bouncers list # подтверждение, что traefik-bouncer подключился и опрашивает LAPIЕсли cscli bouncers list показывает bouncer, но с давним временем последнего запроса - проверьте связность между Traefik и localhost:8080 (сам факт, что оба в одном LXC, еще не гарантирует, что плагин смотрит на правильный порт/схему).
Дальше речь уже не о самом Traefik, а о сети вокруг него - в следующей части переношу Traefik в отдельный VLAN и настраиваю firewall-правила в OPNsense.





