implant traefik-ratelimit

Escreve um middleware de rate-limit por IP no file-provider do Traefik (v2.60.0+). A requisição acima do limite recebe 429 do próprio Traefik, antes de chegar ao serviço e antes de virar linha de erro no access log. O implant não roda daemon nenhum: quem limita é o Traefik. Opt-in.

Reaproveita a descoberta do traefik-edge: container do Traefik, diretório do file-provider no host e versão (2 ou 3).

Uso

implant traefik-ratelimit <SUBCOMANDO> [OPTIONS]
sudo implant traefik-ratelimit apply [--dry-run]           # padrão: 100/s, burst 200, period 1s, depth 0
sudo implant traefik-ratelimit apply --average 20 --burst 60 --period 1s --in-flight 30
sudo implant traefik-ratelimit apply --no-in-flight
implant traefik-ratelimit status [-o json]
implant traefik-ratelimit print [--major 2|3] [--neutral]  # YAML, sem tocar em nada
sudo implant traefik-ratelimit revert [--dry-run]          # remove (recusa se algum router usa)
sudo implant traefik-ratelimit revert --neutral            # mantém o nome, sem limite

Subcomandos

Subcomando Descrição
apply Escreve/atualiza implant-ratelimit.yml no diretório do file-provider (o Traefik recarrega sozinho). Sem flags e com state, refaz o que estava
revert Remove o arquivo e o state. Recusa se algum router usa o middleware, salvo --force
revert --neutral Não remove: deixa o middleware definido e sem limite (average: 0); o apply sem flags refaz o limite
status Arquivo presente, atual ou divergente, neutro, parâmetros, routers que usam o middleware e avisos
print Só imprime o YAML que o apply escreveria (state + flags), sem tocar em nada. --neutral imprime a versão neutra

Parâmetros

Flag Padrão Descrição
--average <N> 100 Requisições por --period, por IP de origem (média)
--burst <N> 200 Rajada acima da média. Página com muitos assets precisa de folga
--period <DUR> 1s Janela da média: <N>s, <N>m ou <N>h
--depth <N> 0 ipStrategy.depth: 0 = IP da conexão; N = N-ésimo IP do X-Forwarded-For contando da direita (ver "--depth atrás de CDN ou proxy")
--in-flight <N> desligado Também limita requisições simultâneas por IP (inFlightReq.amount)
--no-in-flight Desliga o limite de simultâneas
--major <2|3> detectado Força a versão MAJOR do Traefik (apply, print)
--force apply: substitui arquivo de mesmo nome que não é do implant (fica backup). revert: remove mesmo com router usando

Os parâmetros ficam no state, em /var/lib/implant/traefik-ratelimit.json. Cada apply parte do state e só troca o que vier na linha de comando; apply sem flags e sem state usa os padrões.

Arquivo e middleware

O apply grava implant-ratelimit.yml no diretório do file-provider, definindo o middleware implant-ratelimit. A primeira linha é a marca do implant; editar à mão não adianta, porque o implant reescreve quando o conteúdo diverge.

# Gerado pelo Implant (implant traefik-ratelimit).
# average=100 burst=200 period=1s depth=0 in_flight=off
http:
  middlewares:
    implant-ratelimit:
      rateLimit:
        average: 100
        burst: 200
        period: '1s'
        sourceCriterion:
          ipStrategy:
            depth: 0

Com --in-flight N, implant-ratelimit vira uma chain de implant-ratelimit-rate (o rateLimit) e implant-ratelimit-inflight (o inFlightReq). Anexe sempre o nome público, implant-ratelimit.

rateLimit, inFlightReq, chain e ipStrategy.depth têm a mesma sintaxe no Traefik 2 e no 3. O arquivo não usa ipWhiteList/ipAllowList.

File-provider de arquivo único (filename:) não é suportado: use directory:, ou cole o bloco do print no seu arquivo.

Anexar ao router (é com o operador)

O implant não mexe em router nenhum. Para proteger um serviço, acrescente o middleware ao router dele.

Label do container (se já houver outros middlewares, separe por vírgula):

labels:
  - "traefik.http.routers.app.middlewares=implant-ratelimit@file"
  # com outro middleware já presente:
  # - "traefik.http.routers.app.middlewares=headers-seguros@file,implant-ratelimit@file"

Router de arquivo:

http:
  routers:
    app:
      rule: Host(`app.example.com`)
      service: app
      middlewares: [implant-ratelimit@file]

Sem nenhum router usando o middleware, o apply e o status avisam.

`--depth` atrás de CDN ou proxy

Cuidado: errar o depth custa caro nos dois sentidos.

  • --depth 0 (padrão) limita pelo IP da conexão. Atrás de CDN ou proxy reverso, esse é o IP do CDN: todos os usuários que chegam pelo mesmo nó dividem o limite.
  • --depth N usa o N-ésimo IP do X-Forwarded-For, contando da direita.

Os dois erros possíveis:

  • Balde único. Depth alto demais, ou depth > 0 sem forwardedHeaders.trustedIPs no entrypoint (o Traefik descarta o X-Forwarded-For de origem não confiável), deixa o IP vazio. Todas as requisições caem no mesmo balde e o limite vira global: 429 para todo mundo.
  • IP falsificável. Depth > 0 sem proxy na frente deixa o cliente escolher o próprio IP pelo cabeçalho e contornar o limite.

O apply e o status avisam quando o depth é maior que zero e nenhum entrypoint confia em X-Forwarded-For.

Idempotência e backup

  • Conteúdo e state iguais não reescrevem nada.
  • Mudança grava backup do arquivo anterior em implant-ratelimit.yml.implant-bak, no mesmo diretório.
  • Arquivo de mesmo nome sem a marca do implant na 1ª linha não é tocado sem --force.

Simular (`--dry-run`)

O apply mostra o YAML que escreveria; o revert, o que removeria. Nada é gravado, nem o state.

`revert`

Apagar um middleware que um router cita derruba esse router (o Traefik desliga router com middleware inexistente). Por isso o revert procura quem usa o middleware:

  • labels de todos os containers, inclusive parados;
  • os outros arquivos do diretório dinâmico;
  • o usedBy da API do Traefik, se ela responder.

Achou uso: recusa e lista os routers. As saídas são:

  1. tirar a referência dos routers antes;
  2. revert --neutral — mantém o nome com average: 0 (sem limite) e guarda o state; um apply sem flags refaz o limite;
  3. --force — remove mesmo assim (os routers que o citam caem).

O revert completo remove o arquivo e o state, mas deixa o backup implant-ratelimit.yml.implant-bak no diretório do file-provider. O Traefik ignora esse arquivo (a extensão não é .yml/.toml); apague-o à mão se quiser.

`status`

Mostra: arquivo presente; atual (igual ao que o state gera) ou divergente; neutro; parâmetros; routers que usam o middleware; avisos (nenhum router usando, depth sem trustedIPs).

"Quem usa" vem sempre de labels e arquivos, e também da API do Traefik (/api/http/middlewares/implant-ratelimit@file, consultada de dentro do container, só com api.insecure). Sem a API, middleware_api.motivo diz que não dá para saber por ela; routers de outros providers (Consul, Kubernetes) ficam de fora nesse caso.

Emergency-mode

traefik-ratelimit é uma feature opt-in do emergency-mode. Sai por revert --neutral — o middleware continua definido, sem limite, e os routers que o usam não caem — e volta por apply sem flags, com os parâmetros guardados no state.

Limitações

  • Sem isenção por IP da whitelist. O rateLimit do Traefik não tem isenção por IP, e chain não é condicional. Isentar exigiria duplicar cada router com um matcher ClientIP(...), o que o implant não faz em router de terceiro. A whitelist do rate-limit não vale aqui: dimensione os limites para o tráfego legítimo mais pesado (monitoramento, CI) ou deixe sem o middleware os routers que esse tráfego usa.
  • Anexo por entrypoint não implementado (próximo passo). Anexar a todos os routers de uma vez por entryPoints.<ep>.http.middlewares exige editar a config estática e recriar o Traefik. Além disso, um middleware de entrypoint que deixa de existir (file-provider que não sobe, arquivo removido) derruba todos os routers do entrypoint. Quando vier, seguirá o molde do traefik-edge --enable-file-provider: plano com hash, --apply explícito, backup e restart verificado.

Saída JSON

Envelope 1.0, comandos traefik.ratelimit.apply, traefik.ratelimit.revert e traefik.ratelimit.status.

Exemplos

# Ver o YAML antes de gravar
implant traefik-ratelimit print --average 20 --burst 60

# Aplicar: 20 req/s por IP, rajada de 60, até 30 simultâneas
sudo implant traefik-ratelimit apply --average 20 --burst 60 --in-flight 30

# Atrás de um CDN que acrescenta 1 IP no X-Forwarded-For
# (exige forwardedHeaders.trustedIPs no entrypoint)
sudo implant traefik-ratelimit apply --depth 1

# Conferir quem usa e os avisos
implant traefik-ratelimit status

# Desligar o limite sem derrubar routers
sudo implant traefik-ratelimit revert --neutral

Veja também