Challenge Providers
Edge actions provider-agnostic — o trait ChallengeProvider e os providers embutidos
Edge actions e ChallengeProvider
Edge actions (block / challenge / rate-limit em CDN/WAF) são
provider-agnostic via trait
ChallengeProvider,
espelhando o padrão do LlmProvider. Isso permite trocar a Cloudflare por
AWS WAF, Fastly, Bunny… sem mudar o ActionKind, as regras ou o filtro de
verdict.
/// A provider that can apply an edge action (block / challenge / rate-limit).
#[async_trait]
pub trait ChallengeProvider: Send + Sync {
fn name(&self) -> &'static str;
async fn apply(&self, ip: IpAddr, verdict: Verdict, opts: &EdgeOptions) -> Result<()>;
}O provider recebe um EdgeOptions
(ttl + mode, onde mode é um
EdgeMode:
block | js_challenge | managed_challenge | rate_limit) e pode derivar o
modo efetivo do verdict. Implementações devem ser idempotentes e manter
cache de dedupe próprio — o daemon pode repetir decisões depois de um
restart.
Filtro de verdict — ChallengeAction
A lógica de "qual veredito vira action" fica uma vez, no core: o tipo
ChallengeAction
embrulha o provider e seu applies_to aceita apenas Block, Challenge
e RateLimit. Allow (benigno) e Quarantine (retido para análise LLM)
são filtrados centralmente — o provider nunca precisa repetir a checagem.
O provider só executa no edge.
Providers embutidos
Os providers conhecidos por
daemon::build_challenge_action:
| Provider | Crate | Enforcement | Observações |
|---|---|---|---|
cloudflare | sentry-action-cloudflare | IP Access Rules + IP Lists (IPv6) | API; TTL via notes |
firewall | sentry-action-firewall | nftables / ipset / firewalld | kernel; Linux-only; roda inline |
opnsense | sentry-action-opnsense | alias table via REST alias_util | drop-only |
pfsense | sentry-action-opnsense | tabela pfctl | drop-only |
nginx | sentry-action-nginx | includes deny + geo map de JS challenge | mesmo host do daemon |
Cloudflare
Aplica block/challenge/rate-limit via IP Access Rules
(POST /zones/{zone}/firewall/access_rules/rules; o endpoint legado
/firewall/rules foi descontinuado). A API não tem TTL por regra, então
cada regra criada carrega em notes o stamp
sentry:<created_unix>:<ttl_secs> — expiry recuperável entre restarts.
[[action]]
type = "challenge"
provider = "cloudflare"
[action.options]
mode = "managed_challenge" # block | js_challenge | managed_challenge | rate_limit
ttl_secs = 86400
# max_failures = 3 # circuit breaker (default 3)
# ipv6_prefix = 64 # block/rate-limit IPv6 via IP List
# list_name = "sentry_blocks"- Env:
SENTRY_CF_TOKEN+SENTRY_CF_ZONEobrigatórios (sem eles a action é pulada com warning);SENTRY_CF_ACCOUNTopcional (o account id é derivado do zone lookup quando ausente). - Reconcile no startup: verifica o token, readota regras vivas da edge no cache local (evita duplicar inserts) e deleta as expiradas.
- Reaper: task de fundo (300 s) deleta regras cujo TTL no
notesvenceu. - Idempotência:
duplicate-rule(CF 10009) é tratado como sucesso. - Circuit breaker: após
max_failuresinserts falhos consecutivos o provider se desativa até restart (falha de config não gera stream infinito de requests condenados). - IPv6 /64 (F2.14): IP Access Rules aceitam só endereço exato; com
ipv6_prefix, vereditosBlock/RateLimitpara clientes IPv6 viram itens de prefixo em uma IP List account-level (sentry_blocks) + uma custom ruleip.src in $sentry_blocks(action block).
firewall
Bans de kernel (mesma crate descrita em Ações). Único provider que roda inline no hot path por default — contenção local, latência de fila zero.
[[action]]
type = "challenge"
provider = "firewall"
[action.options]
backend = "auto" # auto | nftables | ipset | firewalld
ttl_secs = 86400opnsense / pfsense
Bans em appliance — mesma crate, dois protocolos (detalhes em
Ações). Plataformas
drop-only: Challenge e RateLimit são logados como unenforced.
[[action]]
type = "challenge"
provider = "opnsense" # ou "pfsense"
[action.options]
base_url = "https://192.0.2.10" # obrigatório para opnsense
table = "sentry_blocks"nginx
Geração de includes + reload (detalhes em
Ações). Para o challenge JS,
inclua o snippet gerado dentro do seu server {}:
include /etc/nginx/conf.d/sentry/sentry-challenge-if.conf;[[action]]
type = "challenge"
provider = "nginx"
[action.options]
conf_dir = "/etc/nginx/conf.d/sentry"
reload_cmd = "nginx -s reload"
validate = trueGuarda never-ban
IPs em [real_ip] trusted_ips (e trusted_lists/whitelist, que entram
no mesmo conjunto) nunca são banidos: o pipeline faz short-circuit
para Allow e marca o IP como ReputationTier::Authorized — logo o
filtro de verdict do ChallengeAction nem dispara. Os providers de
containment local (firewall, opnsense/pfsense, nginx) recusam o IP
de novo como guarda final, antes de qualquer comando ou chamada de API; o
fast-path da edge inline também respeita o conjunto.
Alias de compatibilidade
type = "cloudflare" sem provider é equivalente à forma canonical
type = "challenge" + provider = "cloudflare" e mantido por
compatibilidade. Configs novas devem preferir a forma canonical.
Adicionar um novo provider de edge (AWS WAF, Fastly, Bunny…)
- Crie a crate
sentry-action-<nome>implementandoChallengeProvider. - Adicione-a a
sentry-cli/Cargo.toml. - Adicione um braço no
matchdedaemon::build_challenge_action.
Sem mudar ActionKind, regras, ou filtro de verdict.
Sem provider: challenge PoW na edge inline
Em [deployment] mode = "inline" existe uma opção que não fala com
nenhuma API: o challenge JavaScript interativo com proof-of-work embutido
(JsChallenge,
opt-in em [edge.challenge]). Um veredito Challenge vira um
interstitial 503 com um puzzle SHA-256; a solução vira o cookie
sentry_ch=<bucket>:<nonce>, válido pela graça de um bucket.
[edge.challenge]
enabled = true
# secret_env = "SENTRY_EDGE_CHALLENGE_SECRET" # obrigatório quando enabled
# bucket_secs = 3600 # challenge e cookies rotam por bucket
# difficulty = 16 # bits zero iniciais (clamp 8..=28)
# template_path = "/etc/sentry/challenge.html" # página custom (hot-read)- O segredo (
SENTRY_EDGE_CHALLENGE_SECRET, 32+ bytes aleatórios) é obrigatório quandoenabled = true— o startup falha sem ele. - Quem executa o veredito
Challengena edge é ochallenge_backend(ChallengeBackend):sentry(default, PoW embutido) oucloudflare(o veredito vira regra no CF; exige uma action comprovider = "cloudflare"). - O PoW nunca destrava um
Block; bots verificados via rDNS ([bot_verification]) passam sem resolver; o cookie ganhaSecurequando a própria edge termina TLS.