Введение #
В [части 2Title Unavailable | Site Unreachable в разделе “что осталось” я отложил установку CrowdSec на отдельный заход . Вот тот самый заход. CrowdSec ставится в тот же LXC, что и сам Traefik, а не отдельным контейнером в Docker, как было раньше. Это упрощает связность (никаких Docker DNS-имён, всё общается через localhost) и позволяет CrowdSec читать access.log напрямую с диска, без пробрасывания файла между разными окружениями.
Установка #
CrowdSec ставится через официальный скрипт добавления репозитория, дальше — обычным apt:
curl -s https://install.crowdsec.net | sudo sh
apt update
apt list crowdsec
apt install crowdsecapt list crowdsec - необязательный шаг, просто чтобы убедиться, что пакет реально появился в списке после добавления репозитория, прежде чем ставить.
Коллекции #
Коллекция - это готовый набор сценариев обнаружения под конкретный тип нагрузки. Ставлю сразу пакет под 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-подобная защита на уровне запроса, до того как он дойдёт до бэкенда) - про него отдельно ниже.
systemctl reload crowdsecПрофиль реагирования #
Профиль определяет, что конкретно делать, когда CrowdSec принимает решение о блокировке - не просто “забанить”, а по какой логике и куда уведомить.
/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-интеграцию (другие варианты в России на дату написания статьи официально не работают).
/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 (эффективнее, чем поллинг файла по таймеру) для отслеживания новых строк в логе.
Системные логи #
touch /etc/crowdsec/acquis.d/syslog.yamlfilenames:
- /var/log/auth.log
- /var/log/syslog
labels:
type: syslogЭто то, ради чего ставили коллекции sshd/linux - раз CrowdSec теперь в том же LXC, что принимает внешний трафик, разумно заодно приглядывать за попытками подбора SSH и подозрительной активностью на уровне ОС.
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_config: crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
type: appsec127.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Проверка #
cscli decisions list # текущие активные баны
cscli metrics # общая статистика по сценариям и bouncer'ам
cscli bouncers list # подтверждение, что traefik-bouncer подключился и опрашивает LAPIЕсли cscli bouncers list показывает bouncer, но с давним временем последнего запроса - проверьте связность между Traefik и localhost:8080 (сам факт, что оба в одном LXC, ещё не гарантирует, что плагин смотрит на правильный порт/схему).