Установка Traefik и Crowdsec

Предисловие

В этой статье я бы хотел рассказать, как установить и настроить обратный прокси Traefik с Let’s Encrypt и fail2ban систему Croudsec. Настройка Traefik может показаться сложной на первый взгляд, однако разобравшись вы сэкономите уйму сил при добавлении и администрировании новых сервисов. Ранее я использовал Nginx Proxy Manager, но перешел на Traefik, из-за возможности прописать все сетевые параметры для сервиса прямо в docker-compose.yml.

Статья получилась длинная, но я надеюсь ей закрыть вопросы людей, которые собираются пройти тот же путь, что и я.

Traefik

Подготовка директорий и создание конфигураций

Создадим основной каталог traefik, а в нем каталоги log и custom для логов и конфигураций сервисов.

А также 2 файла acme.json и traefik.yml, для сертификатов и основной конфигурации. Файл acme.json оставляем пустым однако ему нужно сменить права доступа на 600, иначе Traefik выдаст ошибку.

1
2
3
4
5
6
7
cd $path_your_docker_configs$

mkdir traefik
cd traefik
mkdir log custom
touch acme.json traefik.yml
chmod 600 acme.json

В traefik.yml перечислены глобальные конфигурации. Их можно переопределить и дополнить, для отдельных сервисов, используя labels в docker-compose.yml или файлы в директории custom. Пример содержимого файла:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
api:
  dashboard: true
  insecure: false
  debug: true

entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"
    http:
      tls:
        certResolver: letsEncrypt

serversTransport:
  insecureSkipVerify: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy-net
  file:
    directory: /custom
    watch: true

certificatesResolvers:
  letsEncrypt:
    acme:
      email: example@gmail.com # Указать почту для letsEncrypt
      storage: /acme.json
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      httpChallenge:
        entryPoint: http
log:
  level: "INFO"
  filePath: "/var/log/traefik/traefik.log"
accessLog:
  filePath: "/var/log/traefik/access.log"

Docker compose

Ниже представлен пример docker-compose.yml. Исправьте путь до конфигов и доменное имя. Traefik запустится с дашбордом и базовой аутентификацией. Но для этого нужно прописать в настройках контейнера имя пользователя и хеш пароля. Получить хеш можно с помощью следующей команды:

1
echo $(htpasswd -nb "user" "password") | sed -e s/\\$/\\$\\$/g

Если htpasswd нет в системе, установите пакет apache2-utils

Далее открываем docker-compose.yml и вставляем получившуюся строку после traefik.http.middlewares.traefik-auth.basicauth.users=

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      # Исправить путь к директории traefik
      - $path_your_docker_configs$/traefik/traefik.yml:/traefik.yml:ro
      - $path_your_docker_configs$/traefik/acme.json:/acme.json
      - $path_your_docker_configs$/traefik/custom.conf/:/custom.conf/:ro
      - $path_your_docker_configs$/traefik/log:/var/log/traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      # # http
      # - "traefik.http.routers.traefik.entrypoints=http"
      # - "traefik.http.routers.traefik.rule=Host(`traefik.dotgs.ru`)" # Заменить домен
      # https
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.dotgs.ru`)" # Заменить домен
      # basic auth
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=Login:PasswordHash" # Указать логин и хеш пароля (htpasswd)

    ports:
      - 80:80
      - 443:443
    networks:
      proxy-net:
    security_opt:
      - no-new-privileges=true
    restart: unless-stopped

networks:
  proxy-net:
    name: proxy-net
    driver: bridge

Блок http закомментирован, поскольку в файле traefik.yml указан автоматический редирект на https.

Запускаем контейнер:

1
docker compose up -d

На данный момент Traefik должен был запустить свой дашборд на https://traefik.ваш-домен.com, а также получить на него сертификат. Убедитесь что все работает корректно, прежде чем продолжить.

Подключаем сервис используя Docker

Для того чтобы Traefik пробросил подключение к новому докер контейнеру нужно:

  • Убедится, что docker выбран в качестве провайдера в файле traefik.yml

    В конфигурации, что я приводил выше, эта функция включена.

  • Подключить контейнер к docker сети proxy-net.

    Или той, что вы выбрали основной в файле traefik.yml

  • Добавить labels:

    - "traefik.enable=true"
    - "traefik.http.routers.example-secure.entrypoints=https"
    - "traefik.http.routers.example-secure.rule=Host(`example.domain.com`)"
    - "traefik.http.routers.example-secure.service=example"
    - "traefik.http.services.example.loadbalancer.server.port=80"

    Исправив имя приложения (example), domain, subdomain, а также порт приложения. Если нужен http:

    - "traefik.http.routers.example.entrypoints=http"
    - "traefik.http.routers.example.rule=Host(`example.domain.com`)"
  • По желанию, отключить проброс портов.

    Если к приложению не нужен доступ в обход Traefik, лучше закрыть все лишнее.

Вот пример docker-compose.yml моего приложение для блога (Hugo):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: "3.8"
services:
  hugo:
    image: klakegg/hugo:ext-debian
    container_name: hugo
    hostname: https://dotgs.ru
    command: server --baseURL=https://dotgs.ru --disableFastRender --appendPort=false
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Europe/Moscow
    volumes:
      - $path_your_docker_configs$/hugo:/src
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.hugo-secure.entrypoints=https"
      - "traefik.http.routers.hugo-secure.rule=Host(`dotgs.ru`)"
      - "traefik.http.routers.hugo-secure.service=hugo"
      - "traefik.http.services.hugo.loadbalancer.server.port=1313"
    networks:
      proxy-net:
    security_opt:
      - no-new-privileges=true
    restart: unless-stopped

networks:
  proxy-net:
    external: true

Подключаем сервис через файл

Напоминаю, что в файле traefik.yml в качестве провайдера выбран еще и file. Указана директория для этих файлов и проброшена в контейнер traefik’a. А так же установлен флаг watch, что заставляет Trafik следить за изменениями в этой директории

Теперь, все что нам нужно, это создать файл:

1
nano custom/example.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
http:
  routers:
    example:
      entryPoints:
        - https
      service: example
      rule: Host(`example.domain.com`)
  services:
    example:
      loadBalancer:
        servers:
          - url: http://ip_address:port/
        passHostHeader: true

И заменить в нем название приложения (example) и адрес. Конфигурация схожа с предыдущим методом, отличается лишь синтаксис.

Дополнительную информацию о провайдерах, как и весь их список читайте в документации.

Crowdsec

Установка

Обратный прокси подготовлен, теперь переходим к защите. Необходимо установить еще 2 docker контейнера, самого Crowdsec и traefik-crowdsec-bouncer. Но прежде предлагаю разобраться в терминах:

  • cscli - Интерфейс командной строки для Crowdsec
  • collection - Набор парсеров для анализа логов определенного сервиса и правил для блокировки ip адресов.
  • acquis.yaml - Файл с путями до логов и их типом.
  • bouncer - Программа, которая фактически блокирует доступ согласно черному списку от Crowdsec. Подбирается под сервис, например в нашем случае traefik-crowdsec-bouncer bouncer для Traefik.

Создаем директории и файл acquis.yaml:

1
2
3
4
5
6
cd $path_your_docker_configs$

mkdir crowdsec
cd crowdsec
mkdir db config
touch acquis.yaml

Содержимое acquis.yaml:

1
2
3
4
filenames:
  - /var/log/traefik/*
labels:
  type: traefik

Теперь исправьте под себя docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: "3.8"
services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    environment:
      COLLECTIONS: "crowdsecurity/traefik"
    volumes:
      - $path_your_docker_configs$/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
      - $path_your_docker_configs$/crowdsec/db:/var/lib/crowdsec/data/
      - $path_your_docker_configs$/crowdsec/config:/etc/crowdsec/
      - $path_your_docker_configs$/traefik/log:/var/log/traefik/:ro
    networks:
      proxy-net:
    restart: unless-stopped

  bouncer-traefik:
    image: docker.io/fbonalair/traefik-crowdsec-bouncer:latest
    container_name: bouncer-traefik
    depends_on:
      - crowdsec
    environment:
      CROWDSEC_BOUNCER_API_KEY: API_KEY
      CROWDSEC_AGENT_HOST: crowdsec:8080
    networks:
      proxy-net:
    restart: unless-stopped

networks:
  proxy-net:
    external: true

Переменную CROWDSEC_BOUNCER_API_KEY укажем позже, пока оставьте как тут.

Запускаем контейнер:

1
docker compose up -d

За статусом Crowdsec вы можете наблюдать введя cscli metrics внутри контейнера:

1
docker exec crowdsec cscli metrics

На данном этапе Crowdsec уже должен читать лог файлы от Traefik, но bouncer еще не подключен. Исправим это, запросив api ключ:

1
docker exec crowdsec cscli bouncers add bouncer-traefik

Ключ вставляем docker-compose.yml в переменной CROWDSEC_BOUNCER_API_KEY.

Помимо ключа, необходимо настроить Traefik, чтобы он использовал этот bouncer как middlewares. Для этого создадим файл crowdsec.yml в директории custom у Traefik:

1
2
cd ../traefik/custom
nano crowdsec.yml

Содержимое файла:

1
2
3
4
5
6
http:
  middlewares:
    crowdsec-bouncer:
      forwardauth:
        address: http://bouncer-traefik:8080/api/v1/forwardAuth
        trustForwardHeader: true

И напоследок, в файле traefik.yml необходимо добавить этот файл в entryPoints.http.http и entryPoints.https.http:

middlewares:
  - crowdsec-bouncer@file

Получившийся файл:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
api:
  dashboard: true
  insecure: false
  debug: true

entryPoints:
  http:
    address: ":80"
    http:
      # Добавлен crowdsec bouncer
      middlewares:
        - crowdsec-bouncer@file
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"
    http:
      # Добавлен crowdsec bouncer
      middlewares:
        - crowdsec-bouncer@file
      tls:
        certResolver: letsEncrypt

serversTransport:
  insecureSkipVerify: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy-net
  file:
    directory: /custom
    watch: true

certificatesResolvers:
  letsEncrypt:
    acme:
      email: example@gmail.com # Указать почту для letsEncrypt
      storage: /acme.json
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      httpChallenge:
        entryPoint: http
log:
  level: "INFO"
  filePath: "/var/log/traefik/traefik.log"
accessLog:
  filePath: "/var/log/traefik/access.log"

Теперь пересоздаем контейнеры traefik crowdsec и bouncer-traefik:

1
docker compose up -d --force-recreate

Проверка

В качестве проверки вы можете заблокировать самого себя вручную. Но будьте внимательны, при подобных проверках, НЕ потеряйте доступ к системе, на которой запущен Crowdsec!

1
docker exec crowdsec cscli decisions add --ip 192.168.0.5

Ip адрес 192.168.0.5 будет заблокирован на 4 часа (по умолчанию). При попытке доступа к сервисам за Traefik, вы должны получить ошибку 403 с сообщением Forbidden. Если хотите поменять, это настраивается через environment в docker-compose.yml.

Получить список всех заблокированных на данный момент адресов можно следующей командой:

1
docker exec crowdsec cscli decisions list

Посмотреть всех заблокированных, в том числе и тех, у кого истек таймаут: cscli alerts list. А получить расширенную информацию по блокировке: cscli alerts inspect -d <ID>.

Если все работает как нужно, разблокируйте себя следующим образом:

1
docker exec crowdsec cscli decisions delete --ip 192.168.0.5

Если нет, проверьте логи bounser’а, traefik и crowdsec:

1
docker logs bouncer-traefik

Использование Crowdsec Security Engines

Это веб приложение, которое может помочь вам наблюдать за блокировками на нескольких экземплярах сразу. Так же там можно подписаться на дополнительные черные списки составленные самой Crowdsec. Приложение имеет вариант премиум подписки, а в бесплатной версии можно подписаться на 3 списка.

Для того чтобы им воспользоваться, зарегистрируйтесь на сайте app.crowdsec.net. После чего, в главном меню выберете Add Security Engine. Укажите ключ доступа в следующей команде:

1
docker exec crowdsec cscli console enroll <KEY>

После нужно будет подтвердить добавление нового Security Engine на сайте.

Добавление нового типа блокировки

Обобщая, для добавления блокировки под новый сервис, вам необходимо выполнить следующее:

  • Найти подходящую коллекцию в этом списке.
  • В описании коллекции зачастую перечислены логи, которые она обрабатывает. Добавляем пути к логам и название коллекции в docker compose crowdsec’a.
  • Добавляем пути к логам в файл acquis.yaml.
  • Если нужен другой bouncer, ищем его в этом списке.
  • Получаем ключ для нового bouncer’а командой cscli bouncers add <bounser-name>
  • Конфигурируем bouncer, и перезапускаем все контейнеры.

Iptables + Crowdsec

Источник - Secure Docker Compose stacks with CrowdSec

В качестве примера вышеописанных действий, предлагаю разобрать защиту всего сервера, а не только сервисов находящихся за Traefik. Возможно с этого следовало бы начать, но в названии статьи указан Traefik, не могу же я изменить название.

В качестве bouncer’а выбран crowdsec-firewall-bouncer, а коллекция называется crowdsecurity/iptables.

Необходимые логи в моей системе (Ubuntu) хранятся в файлах /var/log/syslog /var/log/auth.log. И в файл acquis.yaml нужно добавить следующее:

1
2
3
4
5
6
---
filenames:
 - /var/log/auth.log
 - /var/log/syslog
labels:
  type: syslog

--- - разделитель конфигураций, который необходим для файла yaml.

Если же в вашей системе используется Journald вам необходимо обрабатывать его. Вот ссылки по синтаксису acquis.yaml и по монтированию journal в контейнер. Обратите внимание, что они используют контейнер crowdsec основанный на debian.

Добавим репозитории Crowdsec:

1
curl -s https://install.crowdsec.net | sudo sh

Установим пакет bouncer’a:

1
sudo apt install crowdsec-firewall-bouncer-iptables

Исправим docker-compose.yml. Добавим название коллекции crowdsecurity/iptables, смонтируем пути к логам, а так же установим ip вручную для контейнера, так как bouncer’у нужно связаться с api crowdsec, а порт 8080 мы не пробрасывали в систему.

Я выложу весь docker-compose.yml под спойлер
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $path_your_docker_configs$/traefik/traefik.yml:/traefik.yml:ro
      - $path_your_docker_configs$/traefik/acme.json:/acme.json
      - $path_your_docker_configs$/traefik/custom/:/custom/:ro
      - $path_your_docker_configs$/traefik/log:/var/log/traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      # https
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.dotgs.ru`)"
      # basic auth
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=Login:PasswordHash" # Указать логин и хеш пароля (htpasswd)

    ports:
      - 80:80
      - 443:443
      - 853:853
    networks:
      proxy-net:
        ipv4_address: 172.25.0.2
    security_opt:
      - no-new-privileges=true
    restart: unless-stopped

  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    depends_on:
      - traefik
    environment:
      # Добавлена коллекция
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik"
    volumes:
      - $path_your_docker_configs$/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
      - $path_your_docker_configs$/crowdsec/db:/var/lib/crowdsec/data/
      - $path_your_docker_configs$/crowdsec/config:/etc/crowdsec/
      - $path_your_docker_configs$/traefik/log:/var/log/traefik/:ro
      # Добавлены системные логи
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog:/var/log/syslog:ro
    networks:
      proxy-net:
        # Добавлен ip для контейнера
        ipv4_address: 172.25.0.3
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped

  bouncer-traefik:
    image: docker.io/fbonalair/traefik-crowdsec-bouncer:latest
    container_name: bouncer-traefik
    depends_on:
      - traefik
      - crowdsec
    environment:
      CROWDSEC_BOUNCER_API_KEY: API_KEY #Указать ключ для api crowdsec
      CROWDSEC_AGENT_HOST: crowdsec:8080
    networks:
      proxy-net:
        ipv4_address: 172.25.0.4
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped

networks:
  proxy-net:
    name: proxy-net
    driver: bridge
    ipam:
      config:
        # Указана подсеть для proxy-net
        - subnet: 172.25.0.0/16
          gateway: 172.25.0.1

Перезапускаем контейнеры:

1
docker compose up -d --force-recreate

И получаем ключ:

1
docker exec crowdsec cscli bouncers add firewall-bouncer

Конфигурация bounser’а, находится по следующему пути:

1
nano /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml

В ней мы меняем api_url на ip контейнера - http://172.25.0.3:8080/ и api_key на ключ полученный выше.

Запускаем службу:

1
sudo systemctl start crowdsec-firewall-bouncer

Если ошибок не возникло, вы можете убедиться, что в iptables были внесены изменения. Посмотреть ветку INPUT (по умолчанию только ее застрагивает bouncer) можно следующей командой:

1
iptables -L INPUT -n -v
 pkts bytes target     prot opt in     out     source               destination
 4790  249K DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set crowdsec-blacklists src

Содержимое блок листа можно посмотреть следующим образом:

1
ipset list crowdsec-blacklists | head -n 50

Вы можете вернуться к проверке и забанить самого себя. Однако повторюсь еще раз, НЕ потеряйте доступ к серверу. Этот bouncer заблокирует, в том числе, доступ к ssh для выбранного вами ip. Подобную проверку проводите, например, на телефоне, вне вашей wifi сети.