Pular para o conteúdo

Receitas de integração

Ache a sua infraestrutura abaixo e cole a receita. Se ainda está escolhendo o caminho, comece por Escolha a integração que combina com a sua infraestrutura.

IntegraçãoComo conectaSituação
NGINXauth_request no server {} que você já temDisponível
OpenRestyA receita do NGINX, sem mudançaDisponível
Nginx Proxy ManagerA receita do NGINX em “Custom Nginx Configuration”Disponível
ingress-nginxQuatro annotations no seu IngressDisponível
TraefikMiddleware forwardAuth, num arquivo YAML ou em labels DockerDisponível
DokployA receita do Traefik no editor de Traefik do appDisponível
CoolifyAs labels do Traefik no seu recursoDisponível
Caddyforward_auth dentro de um bloco routeDisponível
Envoyext_authzEm breve
Worker da CloudflareUm Worker nas rotas do seu domínioDisponível
Next.jsMiddleware no seu appDisponível
Hospedado pelo QlyneO seu DNS aponta para o QlyneDisponível
Proxy do QlyneUm container entre o seu servidor web e o seu appDisponível
Tag JavaScriptUma linha de script nas suas páginasEm breve

Use quando o NGINX, o Traefik ou o Caddy já cuida do site: HTTPS, domínios, rotas. Você mantém tudo isso. O proxy do Qlyne roda ao lado em modo decisão, e o seu servidor web pergunta a ele sobre cada pedido, por sub-requisição a /__qlyne/decide.

O Qlyne responde com o veredito das suas regras. Um 200 quer dizer “segue”: o seu servidor web repassa o pedido ao app com os cabeçalhos Qlyne-*. Qualquer outra resposta (a página de desafio, um bloqueio, o redirecionamento para a sala de espera, um limite) vai ao visitante como o Qlyne mandou. Nesse modo o proxy nunca repassa nada ao seu app.

A sub-requisição não leva o corpo do pedido nem a resposta do seu app. É isso que decide o que vale:

FuncionaPrecisa do proxy do Qlyne na frente
Endereços IP bloqueados e confiáveis, países, Tor, reputação compartilhadaWAF no corpo do pedido (caminho e query continuam inspecionados)
Robôs de IA, robôs verificados e impostores, caminhos de armadilhaCarimbo e isca de formulário
Limite de requisições, sala de espera (redirecionamento), trancar um caminho num portão de acesso, as suas regrasConferência de login e de senha vazada enviados por POST, e abrir um portão de acesso pela senha ou pelo e-mail
O desafio, em dois passos no NGINX (a página, depois a solução direto ao proxy)O sensor injetado no HTML, então a nota de robô só vem de um cookie que já esteja lá
Cabeçalhos Qlyne-* para o app, com as cópias forjadas pelo visitante removidasO banner de consentimento, os cabeçalhos de segurança na resposta, o cache de página e o de estáticos

A coluna da direita não quebra o seu site: essas proteções não rodam. Cada regra continua seguindo o seu plano: veja Planos.

Suba o proxy como no passo 1 de Proxy do Qlyne, com o docker-compose.yml de Conector › Configuração. Pule o passo 2 daquela página: aqui o seu servidor web pergunta ao proxy em vez de repassar a ele. Defina estas variáveis:

VariávelValor
QLYNE_TENANT, QLYNE_CORE_URLJá vêm no arquivo que você baixou
QLYNE_SECRETO segredo do conector, no .env
QLYNE_UPSTREAMO endereço do seu app. Continua obrigatório, mesmo sem repasse
QLYNE_DECIDE1 liga /__qlyne/decide e as rotas do desafio em dois passos
QLYNE_TRUSTED_PROXIESO IP ou a faixa CIDR de onde o seu servidor web conecta. Vazio: só o loopback pergunta. Qualquer outro recebe 404
QLYNE_DECIDE_DENY_STATUSDeixe sem definir (403). Use 401 só no ingress-nginx

Em todas as receitas, qlyne:8080 é o proxy do Qlyne e app:3000 é o seu app. Troque os dois pelos endereços que o seu servidor web alcança.

Use quando o NGINX já fica na frente do seu app. Dentro do server {} que você tem:

location / {
auth_request /__qlyne/decide;
auth_request_set $qlyne_cookie $upstream_http_set_cookie;
auth_request_set $qlyne_challenge_passed $upstream_http_qlyne_challenge_passed; # one per Qlyne-* your app uses
add_header Set-Cookie $qlyne_cookie always;
error_page 403 500 = @qlyne_page;
proxy_set_header Qlyne-Challenge-Passed $qlyne_challenge_passed;
proxy_pass http://app:3000; # what you already had
}
location = /__qlyne/decide {
internal;
proxy_pass http://qlyne:8080/__qlyne/decide;
proxy_pass_request_body off; proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host;
}
location @qlyne_page {
rewrite ^ /__qlyne/page? break; proxy_pass http://qlyne:8080;
proxy_pass_request_body off; proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host;
proxy_set_header Cookie "$http_cookie; $qlyne_cookie";
}
location /__qlyne/ { proxy_pass http://qlyne:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; }

O que cada parte faz:

  • auth_request /__qlyne/decide manda a sub-requisição, sem corpo, com o pedido original descrito em X-Original-URI e X-Original-Method. internal esconde essa rota de fora.
  • auth_request_set guarda um cabeçalho do veredito por variável. $upstream_http_set_cookie leva o passe da sala de espera de volta ao visitante. Acrescente uma linha por cabeçalho Qlyne-* que o seu app lê, e passe adiante com proxy_set_header: Qlyne-Bot-Score, Qlyne-Bot-Reasons, Qlyne-Challenge-Passed, Qlyne-Trusted-Ip, Qlyne-Verified-Bot, Qlyne-Reputation, Qlyne-Reputation-Score, Qlyne-Rule, Qlyne-Gate-User, Qlyne-Leaked-Password, Qlyne-Decoy. O proxy_set_header substitui o que o visitante mandou com o mesmo nome, então ninguém forja um Qlyne-*.
  • error_page 403 500 = @qlyne_page é o primeiro dos dois passos. O auth_request descarta o corpo da resposta e só entende 2xx, 401 e 403; qualquer outro status vira 500. O @qlyne_page pede ao proxy a /__qlyne/page, que manda a resposta de verdade para o endereço original: a página de desafio ou de bloqueio com 403, ou o 302, o 429 ou o 303 reais.
  • location /__qlyne/ sem auth_request é o segundo passo. A página de desafio manda a solução a /__qlyne/verify com corpo, e a sub-requisição não leva corpo.
  • O X-Forwarded-For é igual nas três rotas, porque o desafio fica amarrado ao IP do visitante.

Os cookies do Qlyne saem com Secure, então o site precisa estar em HTTPS para o navegador guardá-los.

Use a receita do NGINX como está. O auth_request vem com o NGINX de dentro do OpenResty; não precisa de Lua.

Abra o host, aba Advanced, e cole em “Custom Nginx Configuration”. O Nginx Proxy Manager escreve o location / sozinho, então o veredito vai no nível do server e o location / herda:

auth_request /__qlyne/decide;
auth_request_set $qlyne_cookie $upstream_http_set_cookie;
auth_request_set $qlyne_challenge_passed $upstream_http_qlyne_challenge_passed;
add_header Set-Cookie $qlyne_cookie always;
error_page 403 500 = @qlyne_page;

Abaixo dessas linhas, no mesmo campo, acrescente as três rotas do Qlyne da receita do NGINX (= /__qlyne/decide, @qlyne_page e /__qlyne/), com auth_request off; dentro de /__qlyne/. Troque qlyne:8080 por um endereço que o container do Nginx Proxy Manager alcança: o IP do host, ou o nome do container na mesma rede Docker.

O location / que o Nginx Proxy Manager escreve tem proxy_set_header próprios, que anulam os do nível do server. Para mandar os Qlyne-* ao app, crie a localização / em Custom Locations, apontando para o app, e ponha as linhas proxy_set_header Qlyne-... no campo de configuração dela.

Acrescente estas annotations ao Ingress do seu app:

nginx.ingress.kubernetes.io/auth-url: "http://qlyne.<namespace>.svc.cluster.local:8080/__qlyne/decide"
nginx.ingress.kubernetes.io/auth-method: "GET"
nginx.ingress.kubernetes.io/auth-response-headers: "Qlyne-Challenge-Passed, Qlyne-Bot-Score, Qlyne-Trusted-Ip"
nginx.ingress.kubernetes.io/auth-signin: "https://$host/__qlyne/page?to=$escaped_request_uri"

Mais três coisas:

  • Suba o proxy com QLYNE_DECIDE_DENY_STATUS=401. O ingress-nginx só manda o visitante ao auth-signin num 401, então todo veredito que não segue sai de /__qlyne/decide como 401. O auth-signin leva o navegador a /__qlyne/page, que responde com o status de verdade: a página de desafio ou de bloqueio, o 302 da sala de espera, o 429.
  • Ponha em QLYNE_TRUSTED_PROXIES o CIDR dos pods do controller.
  • Crie um segundo Ingress para o caminho /__qlyne/, apontando ao Service do proxy do Qlyne, sem as annotations de auth.

O ingress-nginx descreve o pedido original em X-Original-URL, e o proxy tira dele o caminho, a query, o host e o esquema. Não precisa de auth-snippet.

Use com o Traefik v3, pelo provider de arquivo ou pelo provider Docker. Duas peças fazem funcionar: o middleware forwardAuth no router do seu app, e um router para /__qlyne/ sem o middleware, com prioridade maior. O segundo leva a solução do desafio, que tem corpo.

Num arquivo de configuração dinâmica (troque o host, a porta do app e os cookies de passe):

http:
middlewares:
qlyne:
forwardAuth:
address: "http://qlyne:8080/__qlyne/decide"
trustForwardHeader: true
authResponseHeadersRegex: "^Qlyne-"
addAuthCookiesToResponse: [qlyne_pass_cap] # one qlyne_pass_<event_key> per queue event
routers:
qlyne-direct:
rule: "Host(`loja.exemplo.com`) && PathPrefix(`/__qlyne/`)"
priority: 1000
service: qlyne
app:
rule: "Host(`loja.exemplo.com`)"
middlewares: [qlyne]
service: app
services:
qlyne: { loadBalancer: { servers: [{ url: "http://qlyne:8080" }] } }
app: { loadBalancer: { servers: [{ url: "http://app:3000" }] } }

Os seus routers de verdade já têm entryPoints e tls; dê ao qlyne-direct os mesmos do app. O addAuthCookiesToResponse lista os cookies que o 200 precisa entregar ao visitante: um qlyne_pass_<event_key> por fila da sala de espera. Os cookies do desafio e da liberação não precisam de nada, porque o Traefik repassa essas respostas inteiras.

Em labels Docker, no serviço do seu app:

- traefik.http.routers.app.middlewares=qlyne
- traefik.http.middlewares.qlyne.forwardauth.address=http://qlyne:8080/__qlyne/decide
- traefik.http.middlewares.qlyne.forwardauth.trustforwardheader=true
- traefik.http.middlewares.qlyne.forwardauth.authresponseheadersregex=^Qlyne-
- traefik.http.middlewares.qlyne.forwardauth.addauthcookiestoresponse=qlyne_pass_cap

E no serviço qlyne, que roda com QLYNE_DECIDE=1 e QLYNE_TRUSTED_PROXIES com a faixa da rede Docker do Traefik:

- traefik.http.routers.qlyne-direct.rule=Host(`loja.exemplo.com`) && PathPrefix(`/__qlyne/`)
- traefik.http.routers.qlyne-direct.priority=1000
- traefik.http.services.qlyne.loadbalancer.server.port=8080

O Traefik, o seu app e o qlyne precisam estar na mesma rede Docker, porque o middleware e o serviço são achados pelo nome. Ponha no qlyne-direct também as labels de entrypoints e tls.certresolver que os seus routers já usam.

O domínio que você cadastra num app vira um arquivo de Traefik próprio, que aparece em Advanced, Traefik. Acrescente ali o middleware qlyne, ponha middlewares: [qlyne] no router que o Dokploy escreveu e crie o router qlyne-direct com os mesmos entryPoints e tls desse router.

Suba o qlyne como um serviço Compose no mesmo projeto, ou como app Docker, na rede dokploy-network. O QLYNE_TRUSTED_PROXIES é a faixa dessa rede (docker network inspect dokploy-network). Num projeto Compose, as labels acima vão direto no arquivo.

As labels que o Coolify escreve ficam no editor de labels do recurso. Acrescente as do middleware no serviço do seu app. Suba o qlyne como um recurso Docker Compose na rede coolify, com as labels do router qlyne-direct.

Use com o Caddy 2. A receita inteira:

loja.exemplo.com {
route {
reverse_proxy /__qlyne/* qlyne:8080
forward_auth qlyne:8080 {
uri /__qlyne/decide
copy_headers Qlyne-Bot-Score Qlyne-Bot-Reasons Qlyne-Challenge-Passed Qlyne-Trusted-Ip Qlyne-Verified-Bot Qlyne-Reputation Qlyne-Reputation-Score Qlyne-Rule Qlyne-Gate-User Qlyne-Leaked-Password Qlyne-Decoy
}
@pass expression `{http.reverse_proxy.header.Set-Cookie} != ""`
header @pass +Set-Cookie {http.reverse_proxy.header.Set-Cookie}
reverse_proxy app:3000
}
}

Por que cada linha está ali:

  • route mantém a ordem escrita. Fora dele, o Caddy reordena as diretivas e o forward_auth rodaria antes da rota direta.
  • reverse_proxy /__qlyne/* vem antes do forward_auth, porque a solução do desafio chega a /__qlyne/verify com corpo.
  • copy_headers nomeia cada cabeçalho. O curinga Qlyne-* não copia nada. Com os nomes, cada um chega ao app com o valor do veredito, e a cópia mandada pelo visitante é removida.
  • header @pass +Set-Cookie entrega o passe da sala de espera ao visitante. O forward_auth sozinho só o poria no pedido que o seu app recebe.

No proxy, ponha em QLYNE_TRUSTED_PROXIES o IP ou a faixa do Caddy. Com domínio, o Caddy serve HTTPS sozinho, que os cookies Secure do Qlyne exigem.

Em breve, pelo filtro ext_authz do Envoy.

CaminhoQuando usarO que cobre
Worker da CloudflareO seu domínio está na CloudflareQuase tudo (detalhes na página do Worker); roda na borda da Cloudflare
Next.jsUm app Next.js, na Vercel ou em outro lugarO que não depende da resposta do app
Hospedado pelo QlyneHospedagem compartilhada, ou nenhum lugar para rodar algoTudo, com HTTPS; o tráfego passa pelo Qlyne
Proxy do QlyneO seu próprio servidor, com o proxy entre o servidor web e o appTudo, a coluna da direita da tabela acima inclusive

Em breve: uma linha de script nas suas páginas que dá nota a cada visitante. Com o modo decisão, ela traz de volta a nota de robô que a sub-requisição não consegue injetar.

Antes, mude o site para “Aplicar” em Conector › Configuração. Em “Só observando”, o Qlyne conta o que faria e deixa todo mundo passar. Depois, trocando loja.exemplo.com pelo seu site:

Janela do terminal
# 1. De um endereço na sua lista de bloqueados: 403
curl -s -o /dev/null -w '%{http_code}\n' https://loja.exemplo.com/produtos
# 2. Uma rota com desafio, sem cookie: a página de desafio
curl -s https://loja.exemplo.com/checkout | grep -o '<title>[^<]*'
# 3. Uma página sem regra: 200 do seu app
curl -s -o /dev/null -w '%{http_code}\n' https://loja.exemplo.com/produtos

O primeiro precisa de uma máquina cujo IP esteja em Regras de IP, na lista de bloqueados. O segundo imprime <title>Checking your browser para uma rota em “Rotas com desafio” (Pro; veja Bot Management); o curl não manda idioma, então a página sai em inglês. Abra essa rota no navegador também: a página se resolve sozinha e abre a sua.

O limite de requisições pega todo mundo de uma vez, ou a lista de bloqueados nunca casa. Um CDN ou balanceador fica na frente do seu servidor web, então o último item do X-Forwarded-For é o endereço dele.

Ponha as faixas dele em QLYNE_TRUSTED_PROXIES, junto com a do seu servidor web. O proxy pula esses itens da direita para a esquerda e julga o primeiro endereço fora da lista. O Traefik e o Caddy também precisam confiar nesse CDN, senão descartam o cabeçalho: forwardedHeaders.trustedIPs no entry point do Traefik, trusted_proxies no Caddy. No NGINX, set_real_ip_from com real_ip_header continua valendo; nunca com 0.0.0.0/0.

A solução não chega ao proxy. O /__qlyne/ precisa ir direto ao proxy do Qlyne, fora da sub-requisição: no NGINX, location /__qlyne/ sem auth_request (auth_request off; no Nginx Proxy Manager); no Traefik, o router qlyne-direct com prioridade 1000 e sem middleware; no Caddy, o reverse_proxy /__qlyne/* antes do forward_auth; no ingress-nginx, o segundo Ingress. Sem isso, a solução recebe 403 com DIRECT_ROUTE_REQUIRED.

Outras duas causas: no NGINX, um X-Forwarded-For diferente numa das três rotas (o desafio fica amarrado ao IP); e um site em HTTP puro, em que o navegador descarta os cookies Secure do Qlyne.

A sala de espera ou um limite responde 500 no NGINX

Seção intitulada “A sala de espera ou um limite responde 500 no NGINX”

O auth_request só repassa 2xx, 401 e 403. O 302 da sala de espera, o 429 e o 303 do portão viram 500, e o 403 do desafio chega vazio. Acrescente error_page 403 500 = @qlyne_page; e a rota @qlyne_page da receita. No ingress-nginx, a correção é QLYNE_DECIDE_DENY_STATUS=401 com o auth-signin.

O proxy só responde a endereços em QLYNE_TRUSTED_PROXIES, ou ao loopback quando a lista está vazia. Confira se o endereço do seu servidor web está na lista e se QLYNE_DECIDE=1 está definido.