implant traefik-edge

Guarda de borda para o Traefik (v2.29.0+): arquivos sensíveis (.git, .env, chaves, certificados, compose…) recebem 403 no próprio Traefik, antes de a requisição chegar a qualquer serviço. É uma segunda linha de defesa para o caso de uma aplicação servir, por engano, um arquivo que não devia.

A guarda é o arquivo ccs-edge-sensitive.yml no diretório do file-provider do Traefik. Com watch: true, o Traefik recarrega sozinho: não há restart.

Uso

implant traefik-edge [OPTIONS]

Sem flag, o comando audita (somente leitura).

Modos

`--audit` (padrão)

Acha o container do Traefik em execução, descobre de onde ele lê a config estática (arquivo, flags ou variáveis de ambiente), localiza o diretório do file-provider no host (mapeando o bind mount) e responde:

  • se a guarda de paths sensíveis existe;
  • se o catch-all de hosts sem router (--with-404) existe;
  • o resultado de um probe: o implant pede /.git/HEAD ao Traefik local (Host fictício, certificado ignorado). 403 prova que a guarda vale; a simples presença do arquivo não prova nada.

O campo risk resume o veredito:

risk Significado
ok Guarda presente e efetiva
sensitive_paths_unguarded Há file-provider, mas a guarda não existe — rode --configure
guard_not_effective O arquivo existe, mas o probe não deu 403 (veja recent_errors)
no_file_provider O Traefik não tem file-provider — ver --enable-file-provider
file_provider_single_file O file-provider usa filename: (arquivo único): o Traefik ignora arquivos vizinhos

`--configure`

Escreve a guarda no diretório dinâmico do Traefik (hot-reload, sem restart) e prova o efeito com o probe. Idempotente: se o arquivo já está idêntico, nada muda. Se existia uma versão diferente, um backup é gravado ao lado (caminho no campo backup da saída).

Se o probe não der 403 e o log do Traefik mostrar que o file-provider não subiu (YAML inválido anterior), o --configure reinicia o container do Traefik uma vez e prova de novo (restarted: true).

Com file-provider de arquivo único (filename:), o --configure recusa e indica as duas saídas: trocar por directory: ou colar o bloco do --print dentro desse arquivo (mesclando sob http:).

`--print`

Só imprime o YAML que seria escrito, sem tocar em nada. Combine com --major e --with-404 para gerar o bloco de uma versão específica.

`--enable-file-provider` (v2.35.0+)

Para Traefik que nasceu só com o provider do Docker: sem file-provider, a guarda não tem onde entrar. Funciona em duas etapas, com o dry-run como pré-condição verificada pelo próprio binário:

  1. Sem --apply (dry-run): acha o docker-compose.yml e o serviço pelas labels do container, gera o patch e mostra o diff. Se existe traefik.yml no host, o file-provider entra nele (providers.file.directory + watch: true) e o compose só ganha o bind do diretório dinâmico; sem arquivo estático, entram duas flags no command: do compose. O plano é gravado com o hash dos arquivos.
  2. --apply: só roda com um plano de até 1 hora para o mesmo compose (ninguém editou no meio). Faz backup, escreve, recria o serviço (docker compose up -d --force-recreate), confere que o Traefik subiu com o file-provider — senão restaura o backup e recria de novo (rollback) — e então escreve a guarda e prova com o probe.

O patch é textual (preserva comentários e formatação) e validado por parse YAML. command: em forma de string/inline, TOML ou config dentro do container sem mount são recusados com instrução manual.

Flags

Flag Descrição
--audit Só auditar (padrão)
--configure Escrever a guarda no diretório dinâmico do Traefik
--with-404 Também instalar o catch-all para hosts sem router. Host com serviço continua respondendo o 404 do próprio serviço; o catch-all só pega host que não tem router (e, portanto, nenhum 404 próprio) e responde 403 com o mesmo bloqueio da guarda
--bloquear <ENTRADA> Acrescenta uma entrada à lista de bloqueio própria (repetível; exige --configure)
--desbloquear <ENTRADA> Remove uma entrada da lista de bloqueio própria (repetível; exige --configure)
--dry-run Mostra o que escreveria, sem escrever
--print Só imprime o YAML (não toca em nada)
--major <2|3> Força a versão MAJOR do Traefik em vez de detectar
--enable-file-provider Liga o file-provider editando o compose/traefik.yml. Sem --apply é dry-run
--apply Aplica o plano do dry-run (exige dry-run recente com o compose inalterado)

O que a guarda bloqueia

Além desta lista embutida, entram as entradas da lista de bloqueio própria.

Na raiz e em qualquer subdiretório:

  • .git, .env*, .secrets*, .deploy.yml, .gitops.yml, CLAUDE*.md
  • docker-compose*.yml, Dockerfile*, .npmrc, .yarnrc
  • *.pem, *.key, *.crt, *.cer, *.p12, *.pfx, *.jks, *.keystore, *.enc, *.log
  • arquivos de credencial: .test_credentials, .credentials (arquivo ou diretório), .htpasswd, .pgpass, .netrc, chaves SSH privadas sem extensão (id_rsa, id_dsa, id_ecdsa, id_ed25519 — o .pub passa)
  • iscas comuns de scanner (sem diferenciar maiúsculas): phpinfo.php, info.php, wp-config.php, wp-login.php, xmlrpc.php, aws-config.js/.json, credentials.json, web.config, .htaccess, .aws/…

Detalhes técnicos da guarda:

  • Router de prioridade 10000, em HTTP e em TLS, sem fixar entryPoints (vale em todos), com o serviço noop@internal.
  • O 403 vem de um middleware de allowlist com origem impossível. No Traefik v3 o middleware é ipAllowList; no v2 é ipWhiteList (anterior à 2.11 só conhece esse nome, e um campo desconhecido derruba o file-provider inteiro).
  • A regra usa PathRegexp no v3 e Path(/{p:…}) no v2.
  • O arquivo é gerenciado: editar à mão não adianta, o implant reescreve quando o conteúdo diverge.

Lista de bloqueio própria (v2.56.0+)

Além da lista embutida, o operador pode bloquear caminhos próprios. As entradas ficam em [traefik_edge] do config.toml e entram na mesma guarda (mesmo 403), no Traefik 2 e no 3:

[traefik_edge]
bloquear = ["/backup.sql", "/config/prod.ini", "/privado/", "dump.sql", "exports/"]

Cada entrada é literal (sem curingas nem regex) e diferencia maiúsculas de minúsculas. Barra inicial: a partir da raiz. Sem barra inicial: a partir de qualquer pasta. Barra final: a pasta e tudo dentro dela.

Entrada Bloqueia Não bloqueia
/backup.sql /backup.sql /x/backup.sql, /backup.sql.gz
/config/prod.ini /config/prod.ini /a/config/prod.ini
/privado/ /privado, /privado/, /privado/qualquer/coisa /privados, /a/privado/x
dump.sql /dump.sql, /a/b/dump.sql /meudump.sql
exports/ /exports, /exports/x, /a/exports/x /myexports/x
a/b.txt /a/b.txt, /x/a/b.txt /xa/b.txt

Pelas flags (repetíveis, exigem --configure), que gravam no config.toml (preservando comentários e as outras seções; cria o arquivo, 0600, se não existir) e regravam a guarda:

sudo implant traefik-edge --configure --bloquear /backup.sql --bloquear exports/
sudo implant traefik-edge --configure --desbloquear exports/
sudo implant traefik-edge --configure --bloquear dump.sql --dry-run   # mostra o plano, não grava nada
  • Caracteres aceitos: letras e dígitos ASCII e . _ ~ - @ + /. Recusados: entrada vazia, / sozinho (bloquearia o site inteiro), //, segmentos . e .., mais de 200 caracteres. % não é aceito: o Traefik compara o caminho já decodificado, então %20 nunca casaria.
  • Entrada inválida dá erro e nada é gravado. Duplicatas são ignoradas.
  • --desbloquear compara o texto exato e não valida, então também remove uma entrada inválida escrita à mão no arquivo.
  • Uma entrada acrescentada direto no config.toml vale no próximo --configure.
  • O audit lista as entradas próprias e, com a guarda escrita, testa uma delas (tem que dar 403). No json: bloquear_extra, bloquear_erro e probe_extra. Esse probe não entra no risk.

Editar a lista (--bloquear/--desbloquear) não mexe no resto da guarda: o catch-all de hosts sem router que já estiver instalado continua. Só o --configure puro segue o --with-404.

Exemplos

# Auditar (padrão)
sudo implant traefik-edge
sudo implant traefik-edge -o json

# Ver o que seria escrito e aplicar
sudo implant traefik-edge --configure --dry-run
sudo implant traefik-edge --configure

# Com catch-all para hosts sem router (responde 403)
sudo implant traefik-edge --configure --with-404

# Só gerar o YAML para Traefik v2
implant traefik-edge --print --major 2

# Traefik sem file-provider: revisar o diff e aplicar
sudo implant traefik-edge --enable-file-provider
sudo implant traefik-edge --enable-file-provider --apply

# Conferir de fora
curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/.env   # 403

Saída JSON

Envelopes traefik.edge.audit, traefik.edge.configure e traefik.edge.enable_provider.

  • audit: traefik_found, container, traefik_major, static_config, config_source (file|flags|env), provider_kind (directory|file), dynamic_dir/dynamic_file, file_provider_watch, sensitive_guard, catch_all_404, probe_status, recent_errors, risk.
  • configure: path, changed, dry_run, with_404, traefik_major, backup, requires_restart, probe_status, restarted.

Como reverter

  • Guarda: apague o ccs-edge-sensitive.yml do diretório do file-provider (o caminho exato sai no campo path do --configure). O Traefik recarrega sozinho.
  • --enable-file-provider: o apply guarda backup do compose (e do traefik.yml, quando editado) antes de escrever; restaure-os e recrie o serviço.

Notas

  • O probe precisa alcançar a porta 443 local; sem isso, probe_status: null e o veredito fica por arquivo, sem prova.
  • Para o auto-ban pontuar scanners que recebem esse 403, o access log do Traefik precisa registrar a resposta — ver traefik-accesslog.

Veja também