Edge Inline
Deployment inline: reverse proxy, block fast-path, TLS, challenge PoW, uploads e tuning
Edge Inline
No modo [deployment] mode = "inline" o Sentry deixa de ser um observador
e vira o front da aplicação: um reverse proxy axum
(sentry-edge/src/proxy.rs)
que inspeciona cada request antes do upstream e aplica o verdict na
hora. O proxy roda o mesmo Arc<Pipeline> das sources passivas (trackers
não duplo-contam) e entrega eventos prontos (Incoming::Processed) ao
daemon.
Health check, grupos de upstream e failover (F14)
O startup exige ≥1 servidor saudável por grupo de upstream
(health_path, default /, timeout health_timeout_secs = 5 s) —
secundário morto só gera warn (o failover existe exatamente para isso),
mas grupo sem nenhum servidor saudável impede o subir: a edge nunca sobe
"protegendo" um backend morto. O hop edge→upstream é tunado (F12.6):
connect_timeout 5 s, pool de keepalive com upstream_pool_idle = 32
conexões por host (idle 90 s, TCP keepalive 60 s) e tcp_nodelay —
zero handshake TCP por request no hop interno.
As requisições são roteadas por Host header (h1 Host / h2
:authority) para grupos de upstream por site — nginx server_name +
proxy_pass. Cada grupo balanceia (round_robin, least_conn,
ip_hash), roda health checks passivos (max_fails/fail_timeout:
falhas de conexão suficientes na janela ejectam o servidor pela janela) e
faz failover: erro de conexão/timeout tenta o próximo servidor saudável
uma vez por request (5xx não é retryable) — sentry_edge_upstream_failovers_total{vhost}
conta as retentativas; grupo todo morto responde 502. Upstreams podem ser
hostnames (nomes de container Docker, services k8s) — resolvidos pelo
resolver do sistema na conexão.
Requests de upgrade h1 (WebSocket) seguem por pass-through cru: o pipeline
decide primeiro, a edge repassa a requisição com um client h1, responde
101 e pipeia as duas conexões upgraded bidirecionalmente.
response_buffering = false (ou text/event-stream) faz stream das
respostas em vez de bufferizar, pulando a compressão.
Fast-path de bloqueio (BlockTable)
O BlockTable
é um HashMap<IpAddr, Option<Instant>> compartilhado. A edge (proxy,
middleware, edge-tcp e o aceitor TLS) consulta is_blocked antes de
qualquer processamento: IP bloqueado leva 403 imediato, sem evento e sem
passar pelo pipeline — sem spam de webhook e com custo O(1).
- Verdicts Block do pipeline são espelhados no
ip_state(Postgres) comNOTIFY sentry_blocks_changed; pre-warm no startup e LISTEN por nó fazem um bloco aplicado em qualquer nó valer na edge de todos (daemon.rs). - Guard never-ban (
[real_ip] trusted_ips) vale também no fast-path. - Métricas:
sentry_edge_block_hits_total,sentry_block_table_size.
Verdict → HTTP
| Verdict | Resposta |
|---|---|
| Block | 403 (página customizável) |
| RateLimit | 429 com Retry-After |
| Challenge | interstício PoW (backend sentry) ou regra Cloudflare (backend cloudflare) |
| Allow | proxy para o upstream |
Todas as páginas geradas pela edge (403/429/challenge/413/502/301) passam
pelo mapa ErrorPages
(F12.1): [edge.error_pages] dir com <status>.html + default.html,
UTF-8, cap 256 KiB/arquivo, placeholders {{SENTRY_STATUS}},
{{SENTRY_TITLE}}, {{SENTRY_MESSAGE}}, {{SENTRY_TRACE_ID}},
{{SENTRY_ICON}} com escape de HTML. Trace ID sempre (header
x-sentry-trace-id + log).
JS challenge com proof-of-work (F7.11)
[edge.challenge] enabled = true troca a resposta de Challenge por um
interstício PoW SHA-256 stateless
(challenge.rs):
challenge_id = SHA-256(secret || ip || bucket); buckets debucket_secs(default 3600) com graça de 1 bucket — o cookiesentry_chsobrevive à rotação.- Secret via env (
secret_env, defaultSENTRY_EDGE_CHALLENGE_SECRET) — obrigatório quando enabled; compartilhe entre nós da edge. difficultydefault 16; página sem CDN,template_pathopcional com hot-read.- PoW nunca destrava Block; respostas 503 com
Retry-After. - Crawlers verificados por rDNS (F7.7) passam sem resolver.
TLS inline com telemetria ClientHello (F8)
Feature edge-tls (tls.rs):
o aceitor faz peek do ClientHello antes do handshake rustls, parseia
SNI/ciphers/extensions (clienthello.rs)
e alimenta o pipeline com um evento TlsHandshake (SNI + JA3 + JA4) por
conexão. O fast-path do BlockTable nega antes do handshake.
- ALPN
h2+http/1.1com HTTP/2 servido no mesmo router ([edge] http2, default true); métricasentry_edge_tls_alpn_total{alpn}. tls_allowed_hosts— SNI ausente/desconhecido (443 exposto) vira o sinalTlsSniMismatch(peso 20); Block/Quarantine pós-handshake derrubam a conexão.tls_redirect_httpsresponde 301 na porta 80 depois do pipeline — o tráfego HTTP continua monitorado e bloqueado.- Gauge
sentry_edge_tls_cert_not_aftercom warn quando o certificado expira em menos de 14 dias.
Inspeção de uploads e corpo (F10)
[uploads] enabled = true (opt-in, requer inline) liga a análise do corpo
antes do proxy: multipart RFC 7578, urlencoded e JSON
(multipart.rs,
uploads.rs).
inspect_kb(default 4096 = 4 MiB) limita o buffer por request; o teto de memória é ~concorrência ×inspect_kb— por isso existe o cap de concorrência.- Corpo maior que o cap segue a política
oversize:reject(413),skip(encaminha sem parse) ouflag(encaminha + sinalUploadOversizepeso 20 + conta na janela de flood). - Sinais:
UploadTypeMismatch(30),UploadPolyglot(60),UploadExecutable(50),UploadFlood(25),UploadDisallowed(30, allowlistallowed_extensions).mode = "shadow"zera os pesos — detecta sem bloquear. - SQLi/XSS em filenames e campos de formulário reemitem os sinais HTTP conhecidos, então dashboards e regras existentes funcionam de graça.
Postura de segurança web (F11)
posture.rs
avalia os headers da resposta do upstream (nunca de páginas geradas
pela edge) e anexa findings PostureAdvisory com peso 0 travado:
csp, hsts, coop, clickjacking, trusted_types, nosniff,
referrer_policy — todos desligáveis em [posture] checks. Dedupe por
(host, check) com TTL 1 h, cap de 64 hosts. Métrica
sentry_posture_findings_total{check, host}; relatório via
sentry posture. mode = "enforce" é rejeitado no load (injeção de
headers é roadmap).
Compressão de resposta (F12.5)
compression.rs —
um middleware cobre upstream e páginas da edge nos dois listeners. Corpos
já bufferizados → re-encode síncrono com content-length exato.
- Negociação de
Accept-Encodingcom preferência zstd > brotli > gzip (zstd atrás da featureedge-zstd; brotli/gzip são pure-Rust e sempre presentes). Níveis:level_br4,level_gzip6,level_zstd3. - Nunca comprimido:
content-encodingpresente, 206/content-range,no-transform, corpo menor quemin_length(256), content-type fora da allowlist (semânticagzip_types;text/event-streamexcluído) e payload que não encolhe.Vary: Accept-Encodingsempre em candidatos. - Sob overload (flag compartilhada
OverloadState) a compressão se desliga sozinha. Métricasentry_edge_compressed_total{algo}.
Forwarded headers e hop de upstream (F12.6/7)
[edge] forwarded_headers = true (default): a edge remove
X-Forwarded-*/X-Real-IP do cliente e seta a visão resolvida —
X-Forwarded-For/X-Real-IP = IP do cliente (precedência §8.1),
X-Forwarded-Proto = scheme real (via extensão TlsTerminated, nunca um
header spoofável), X-Forwarded-Host = Host original. O backend nginx
mantém real_ip/vhost funcionando sem spoof. Advisories de topologia
(edge_advisories()): TLS duplo com upstream https:// e hop sem
criptografia para upstream fora do loopback.
Cap de concorrência
[edge] max_concurrent_requests (default 1024, 0 = ilimitado): cada
request em voo bufferiza até o cap de inspeção/forward em memória, então
uma enchente de conexões multiplica esse custo. Além do cap, o request
recebe 503 imediato (com error pages customizáveis) em vez de ser
bufferizado — memória bounded por design.