↓ Перейти к основному содержимому
  1. Posts/
  2. Traefik/

Middlewares в Traefik - что это, зачем нужны и полный список для homelab

Stilicho2011
Автор
Stilicho2011
Пишу о homelab, self-hosting, автоматизации и open-source решениях
Оглавление
Traefik - This article is part of a series.
Part : This Article

Что такое middleware в Traefik
#

Когда запрос долетает до обратного прокси Traefik, он проходит через цепочку обработки, прежде чем попасть к реальному сервису (вашему приложению за прокси) - или вместо этого сразу получить ответ от самого Traefik, не дойдя до бэкенда. Middleware - это как раз тот механизм, который позволяет вмешаться в запрос или ответ на этом пути: изменить заголовки, отклонить запрос, ограничить частоту обращений, потребовать аутентификацию, переписать путь и так далее.

По сути, middleware в Traefik - прямой аналог middleware в любом веб-фреймворке (Express, Django, ASP.NET) - только на уровне реверс-прокси, а не приложения. Разница важная: middleware Traefik работает до того, как запрос вообще доедет до вашего сервиса, а значит может защитить приложение, которое само по себе никакой защиты не имеет (не умеет в аутентификацию, не ограничивает частоту запросов, не проверяет заголовки).

Зачем это нужно, если можно настроить все в самом приложении
#

Формально - многое из того, что делает middleware, можно реализовать и на стороне приложения. Но есть причины, почему делать это на уровне прокси удобнее и более правильно с точки зрения управления и безопасности:

  • Централизация. Ограничение частоты запросов, базовая аутентификация, security-заголовки - если все это настраивается один раз в Traefik, не нужно повторять логику в каждом отдельном приложении, часть из которых вообще не дает такой возможности.
  • Единообразие. Одна и та же политика (например, security headers) применяется ко всем сервисам сразу, без риска, что где-то забыли.
  • Защита того, что само не умеет защищаться. Многие self-hosted приложения (дашборды, простые утилиты) не имеют встроенной аутентификации вообще - middleware вроде forwardAuth или basicAuth перед таким сервисом закрывает эту дыру, не трогая код самого приложения.
  • Разделение ответственности. Приложение занимается своей прямой задачей, вопросы вроде “кто имеет право сюда зайти” и “как часто можно стучаться” - решаются на уровне инфраструктуры.

Как middleware применяется к запросу
#

Middleware не работает сам по себе - он объявляется в dynamic config, а затем подключается к конкретному роутеру по имени:

http:
  routers:
    my-service:
      rule: "Host(`app.example.com`)"
      middlewares:
        - default-headers
        - rate-limit
      service: my-service

  middlewares:
    default-headers:
      headers:
        frameDeny: true
    rate-limit:
      rateLimit:
        average: 100

Порядок в списке имеет значение. Middleware применяются последовательно, сверху вниз - запрос проходит через default-headers, затем через rate-limit, и только после этого (если ни один middleware его не отклонил) доходит до сервиса. Ответ идет в обратном порядке. Это важно, например, для аутентификации - middleware, проверяющий доступ, обычно должен стоять раньше тех, что просто модифицируют заголовки, иначе неавторизованный запрос может успеть что-то затронуть до отказа.

Один и тот же middleware можно переиспользовать в любом количестве роутеров - объявляете один раз, подключаете где угодно по имени. При использовании нескольких providers (file provider, Docker provider) к имени добавляется суффикс провайдера: authentik@file, redirect@docker - это то, как Traefik различает одноименные middleware, объявленные в разных бекэндах.

Определение и применение - не одно и то же
#

Middleware всегда определяется в dynamic config - его тип и параметры (headers, rateLimit, forwardAuth и т.д.) невозможно описать в static config (traefik.yaml) напрямую, такой возможности там просто нет. А вот применяться уже определенный middleware может двумя разными способами.

Точечно, к конкретному роутеру - то, что было в примере выше:

# dynamic config
http:
  routers:
    my-service:
      middlewares:
        - default-headers
        - authentik

В указанном примере мы сначала применяем стандартный заголовки запросов, а потом перенаправляем запрос на Authentik

Глобально, через entryPoint в static config. В traefik.yaml можно сослаться на middleware по имени (с суффиксом провайдера), и тогда он применится ко всем запросам, которые проходят через этот entrypoint, еще до того, как Traefik разберется, к какому роутеру относится запрос:

# traefik.yaml (static config)
entryPoints:
  websecure:
    address: ":443"
    http:
      middlewares:
        - rate-limit@file

Middleware по-прежнему определен в dynamic config - в static config лежит только ссылка на его имя. Такой способ удобен для того, что должно действовать на весь трафик одинаково (rate limit, CrowdSec bouncer) - не нужно перечислять их в middlewares: каждого отдельного сервисного роутера, они и так применяются глобально через привязку к entrypoint.

Что такое Chain
#

Chain - это middleware, который сам состоит из других middleware:

http:
  middlewares:
    secured:
      chain:
        middlewares:
          - default-whitelist
          - default-headers

Подключаете secured цепочку (chain) к роутеру - и получаете применение сразу обоих (default-whitelist, затем default-headers) в заданном порядке, одной строкой. Удобно, когда одна и та же комбинация из нескольких middleware повторяется на многих роутерах - вместо того чтобы каждый раз перечислять весь список, ссылаетесь на один chain.

Открытая версия vs Traefik Hub
#

Часть middleware из документации Traefik помечены как доступные только в Traefik Hub (платная надстройка/Enterprise) - например, JWT, OIDC, OAuth2, OPA, WAF, LDAP, HMAC, APIKey, Distributed RateLimit. Ниже - только те middleware, что доступны в open-source версии, которую использует большинство homelab-инсталляций (включая мою).


Полный список middleware
#

AddPrefix
#

Добавляет префикс к пути запроса перед тем, как он уйдет к бэкенду. Полезно, когда бэкенд ожидает путь вида /api/..., а снаружи хочется без префикса.

http:
  middlewares:
    add-prefix-example:
      addPrefix:
        prefix: "/api"

BasicAuth
#

Классическая HTTP Basic Authentication - логин/пароль в диалоговом окне браузера. Хеш пароля генерируется через htpasswd (пакет apache2-utils):

htpasswd -nB username
http:
  middlewares:
    basic-auth-example:
      basicAuth:
        users:
          - "username:$2y$05$..."
        realm: "Restricted"

Использую для защиты dashboard самого Traefik - простой и надежный fallback, не зависящий от доступности внешнего identity-провайдера.

Buffering
#

Читает тело запроса (и при необходимости ответа) целиком, прежде чем передать его дальше, и отклоняет запросы больше заданного размера с кодом 413. Полезен в двух случаях: чтобы ограничить максимальный размер запроса к сервису, который в принципе не должен принимать большие файлы (API, формы входа), и чтобы Traefik мог повторить запрос при сетевой ошибке - для повтора тело запроса должно быть сохранено целиком.

http:
  middlewares:
    middlewares-buffering:
      buffering:
        maxRequestBodyBytes: 10485760
        memRequestBodyBytes: 2097152
        maxResponseBodyBytes: 10485760
        memResponseBodyBytes: 2097152
        retryExpression: "IsNetworkError() && Attempts() <= 2"

А вот для сервисов с большими загрузками (Nextcloud, Immich, OnlyOffice) этот middleware скорее вреден. С таким maxRequestBodyBytes любой файл больше 10 МБ получит 413, а без лимита Traefik будет сначала целиком складывать каждую многогигабайтную загрузку в память и на диск и только потом отдавать ее сервису. Сам Traefik размер тела запроса по умолчанию не ограничивает, поэтому большим загрузкам нужен не buffering, а увеличенный readTimeout на entrypoint: с дефолтными 60 секундами длинная загрузка видео просто обрывается (у меня стоит 600 секунд, подробнее - во второй части про Traefik в LXC). Так что buffering у меня лежит как заготовка и ни к одному сервису не подключен.

Chain
#

Уже разобрали выше - объединяет несколько middleware под одним именем.

http:
  middlewares:
    secured:
      chain:
        middlewares:
          - default-whitelist
          - default-headers

CircuitBreaker
#

Автоматически отключает трафик от сервиса, если тот начинает массово отдавать ошибки или тормозить - защищает остальную систему от каскадного эффекта одного упавшего бэкенда.

http:
  middlewares:
    circuit-breaker-example:
      circuitBreaker:
        expression: "NetworkErrorRatio() > 0.30 || ResponseCodeRatio(500, 600, 0, 600) > 0.25"
        checkPeriod: "10s"
        fallbackDuration: "30s"
        recoveryDuration: "30s"

Compress
#

Сжимает тело ответа (gzip) перед отправкой клиенту - экономит трафик. Стоит исключать уже сжатые форматы (изображения, видео), сжимать их повторно бессмысленно, там уже все сжато до нас, и тратит мощность CPU впустую.

http:
  middlewares:
    compress:
      compress:
        excludedContentTypes:
          - "image/png"
          - "image/jpeg"
          - "image/webp"
          - "video/mp4"

ContentType
#

В Traefik v3 автоопределение Content-Type включено по умолчанию - этот middleware нужен, только если хотите явно отключить автоопределение для конкретного сервиса.

http:
  middlewares:
    content-type-example:
      contentType: {}

DigestAuth
#

Похож на BasicAuth, но пароль не передается в открытом виде даже в рамках HTTP-схемы (используется хеш-based challenge-response).

http:
  middlewares:
    digest-auth-example:
      digestAuth:
        users:
          - "username:Restricted:ha1_hash"
        realm: "Restricted"

Errors (Custom Error Pages)
#

Подменяет стандартную страницу ошибки Traefik/бэкенда на свою - отдает ее с отдельного сервиса (например, статическая страница).

http:
  middlewares:
    error-pages-example:
      errors:
        status:
          - "500-599"
        service: error-pages-service
        query: "/{status}.html"

ForwardAuth
#

Один из самых полезных middleware для homelab с SSO. Перенаправляет каждый входящий запрос на внешний сервис аутентификации (в моем случае - Authentik) перед тем, как пропустить его дальше. Если внешний сервис отвечает успехом - запрос идет к реальному бэкенду, с добавленными заголовками из ответа (имя пользователя, группы и т.д.); если нет - Traefik сам отдает ответ аутентификации (редирект на логин), не трогая бэкенд вообще.

http:
  middlewares:
    authentik:
      forwardAuth:
        address: "http://10.10.10.10:9000/outpost.goauthentik.io/auth/traefik"
        trustForwardHeader: true
        maxResponseBodySize: 1048576
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-groups
          - X-authentik-email
          - X-authentik-name
          - X-authentik-uid

Важный нюанс: нужен только для приложений, которые сами не умеют говорить с identity-провайдером напрямую (по OIDC). Если приложение само поддерживает OIDC-клиент (как многие современные self-hosted сервисы) - forwardAuth не нужен, приложение настраивается напрямую на Authentik как Identity Provider, без участия Traefik в цепочке аутентификации.

Необходимо отметить, что не все сервисы аутентификации могут работать с forwardAuth напрямую. Authentik и Authelia могут, Keycloak таким коробочным функционалом на дату написания статьи не обладает.

GrpcWeb
#

Актуально только если проксируете grpc-web клиента к обычному gRPC-бэкенду - конвертирует протокол на лету.

http:
  middlewares:
    grpc-web-example:
      grpcWeb:
        allowOrigins:
          - "*"

Headers
#

Один из самых часто используемых middleware - управляет HTTP-заголовками запроса и ответа: security headers (HSTS, X-Frame-Options, CSP), кастомные заголовки, CORS.

http:
  middlewares:
    default-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https

Держу два варианта baseline - облегченный для большинства сервисов и усиленный (со скрытием версии сервера, более длинным HSTS для preload-листа) для более чувствительных случаев - плюс отдельные варианты под конкретные приложения, где нужен нестандартный CSP (например, для встраивания редактора документов в iframe с другого домена).

InFlightReq
#

Ограничивает число одновременных запросов в обработке (в отличие от rate limit, который ограничивает частоту запросов в секунду). Когда лимит достигнут, новые запросы получают 429. Важная деталь: если не задать sourceCriterion, лимит считается по хосту запроса, то есть он один общий на всех клиентов сервиса сразу, а не на каждого клиента в отдельности.

Полезно для тяжелых синхронных эндпоинтов, где каждый запрос сам по себе нагружает сервер, - например, генерация отчетов или экспорт. А вот для фоновой обработки он бесполезен. Например, Immich перекодирует видео и запускает распознавание лиц фоновыми заданиями после загрузки, а не во время запроса, и их нагрузку ограничивают настройки параллельности заданий в самом Immich. Ограничение соединений на прокси там приведет только к ошибкам 429 у мобильного приложения при массовой загрузке фото. Медиасерверам вроде Plex и Jellyfin, которые транскодируют на лету, число одновременных потоков тоже правильнее ограничивать их собственными настройками, а не подсчетом HTTP-запросов на прокси.

http:
  middlewares:
    in-flight-req-example:
      inFlightReq:
        amount: 20

IPAllowList
#

Ограничивает доступ по списку разрешенных IP/подсетей.

http:
  middlewares:
    default-whitelist:
      IPAllowList:
        sourceRange:
          - "10.0.0.0/8"
          - "192.168.0.0/16"
          - "172.16.0.0/12"

У меня все сервисы закрыты аутентификацией, поэтому этот middleware сейчас нигде не подключен - держу как готовый инструмент про запас.

PassTLSClientCert
#

Актуально для mTLS-сценариев, где бэкенд сам хочет видеть клиентский сертификат в заголовке.

http:
  middlewares:
    pass-tls-client-cert-example:
      passTLSClientCert:
        pem: true

RateLimit
#

Ограничивает частоту запросов от одного клиента (по умолчанию - по source IP), защита от флуда/DDoS.

http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 100
        burst: 50

Подключен у меня глобально на оба entrypoint (web/websecure), а не точечно к отдельным сервисам - общая линия защиты для всего трафика.

RedirectScheme
#

Редиректит запрос с одной схемы на другую - чаще всего HTTP → HTTPS.

http:
  middlewares:
    https-redirect:
      redirectScheme:
        scheme: https
        permanent: true

RedirectRegex
#

То же самое, но с произвольным regex-паттерном для более сложных редиректов, не только смены схемы.

http:
  middlewares:
    redirect-regex-example:
      redirectRegex:
        regex: "^https://(.*)/old-path"
        replacement: "https://${1}/new-path"
        permanent: true

ReplacePath
#

Полностью заменяет путь запроса перед передачей бэкенду.

http:
  middlewares:
    replace-path-example:
      replacePath:
        path: "/new/path"

ReplacePathRegex
#

То же самое через regex, с возможностью захватывать и переиспользовать части исходного пути.

http:
  middlewares:
    replace-path-regex-example:
      replacePathRegex:
        regex: "^/old/(.*)"
        replacement: "/new/$${1}"

Retry
#

Повторяет запрос при сетевой ошибке до заданного числа попыток - в отличие от retryExpression внутри buffering, это отдельный, более общий механизм.

http:
  middlewares:
    retry-example:
      retry:
        attempts: 3
        initialInterval: "100ms"

StripPrefix
#

Убирает указанный префикс из пути перед передачей бэкенду - обратная операция к AddPrefix.

http:
  middlewares:
    strip-prefix-example:
      stripPrefix:
        prefixes:
          - "/api"

StripPrefixRegex
#

То же самое, но по regex-паттерну.

http:
  middlewares:
    strip-prefix-regex-example:
      stripPrefixRegex:
        regex:
          - "^/api/v[0-9]+"

Как я организовал middleware у себя
#

Эта конфигурация касается всей серии статей про Traefik в LXC, а не только части про финальную настройку прокси - от первой части с базовой установкой, через CrowdSec в третьей части, до сетевой изоляции в части про firewall-правила.

Каждый middleware - отдельный .yaml файл по пути /etc/traefik/dynamic/middlewares/, имя файла соответствует его назначению. Часть middleware реально подключена к сервисам (default-headers, rate-limit, https-redirect, authentik forwardAuth), часть держу как готовую библиотеку про запас - на случай, когда понадобится конкретный сценарий (whitelist для админок, buffering для ограничения размера запросов, in-flight limit для тяжелых эндпоинтов), не изобретая конфиг с нуля в моменте.

Ключевой плюс такого подхода - при добавлении нового сервиса не нужно думать, “а как вообще пишется этот middleware” - просто беру готовый файл, копирую нужные строки в services/<новый-сервис>.yaml, подключаю по имени.

Ссылки
#

Traefik - This article is part of a series.
Part : This Article

Related

Установка Traefik в LXC-контейнер Proxmox как systemd-сервиса | Часть 3 - CrowdSec

··2519 слов·12 минут· loading · loading
Третья часть серии про Traefik в LXC - установка CrowdSec в тот же контейнер, что и сам Traefik. Коллекции сценариев, профиль реагирования с уведомлениями в Gotify, источники логов (Traefik, syslog, AppSec), и подключение bouncer-плагина обратно к Traefik.

Установка Traefik в LXC-контейнер Proxmox как systemd-сервиса | Часть 2

··3449 слов·17 минут· loading · loading
Продолжение серии про Traefik в LXC - от тестового бинарника к рабочей конфигурации. Токен Cloudflare, финальный static config, wildcard-сертификат, защита dashboard через Basic Auth, ротация логов, структура dynamic config и перенос десятка сервисов с Docker labels на file provider. CrowdSec - в планах, отдельным заходом.

Установка Traefik в LXC-контейнер Proxmox как systemd-сервиса | Часть 1

··1441 слово·7 минут· loading · loading
Подробная инструкция по установке и настройке Traefik в LXC-контейнере для организации обратного прокси. Рассмотрены настройка Docker, конфигурация маршрутизации, интеграция с SSL и рекомендации по управлению веб-трафиком.