IPScanner

Integraciones

Forward auth para nginx, Apache y HAProxy

Un contenedor pequeño al que nginx, Nginx Proxy Manager, Apache, HAProxy, Traefik o Caddy consulta en cada petición.

Instalación

  1. 1Añade un sitio forward auth en el panel y copia su Site ID.
  2. 2Arranca el gate en la red Docker de tu proxy con tu clave de API y el Site ID. No publiques su puerto.
  3. 3Añade el snippet de tu proxy.

ghcr.io/ipscanner/forward-auth es un binario estático para linux/amd64 y linux/arm64 que se ejecuta sin root y sin shell. Tienes binarios para Linux y macOS en la página de releases.

Un sitio nuevo empieza en modo monitor: añade cabeceras y no bloquea nada hasta que lo pases a enforce.

Resumen en la página de nginx, Apache y HAProxy, código en GitHub.

docker-compose.yml
services:
  ipscanner:
    image: ghcr.io/ipscanner/forward-auth:0.1
    restart: unless-stopped
    environment:
      IPSCANNER_API_KEY: ${IPSCANNER_API_KEY}
      SITE_ID: site_...

Snippets por proxy

  • nginxauth_request

    auth_request consulta /check en cada petición. Un visitante bloqueado ve la página 403 del gate, y si el gate no responde el tráfico pasa. nginx resuelve el gate al arrancar, así que arranca primero el gate.

  • Nginx Proxy Managerauth_request

    Lo mismo, pegado en la pestaña Advanced del proxy host, con el gate en la red Docker de NPM.

  • TraefikForwardAuth

    Un middleware ForwardAuth en los labels de tu app. El plugin de Traefik hace lo mismo sin el contenedor.

  • Caddyforward_auth

    forward_auth hacia /check, copiando las cabeceras X-IPScanner-*. El módulo de Caddy hace lo mismo sin el contenedor.

  • HAProxyUPSTREAM_URL

    Modo proxy con UPSTREAM_URL=http://app:80. La app es el servidor de respaldo, así que el tráfico sigue si el gate cae.

  • ApacheUPSTREAM_URL

    Modo proxy como en HAProxy, con la app como hot standby.

location / {
    auth_request /_ipscanner;
    auth_request_set $ipscanner_status     $upstream_http_x_ipscanner_status;
    auth_request_set $ipscanner_class      $upstream_http_x_ipscanner_class;
    auth_request_set $ipscanner_action     $upstream_http_x_ipscanner_action;
    auth_request_set $ipscanner_network    $upstream_http_x_ipscanner_network_class;
    auth_request_set $ipscanner_anonymized $upstream_http_x_ipscanner_anonymized;
    auth_request_set $ipscanner_risk       $upstream_http_x_ipscanner_risk;
    auth_request_set $ipscanner_country    $upstream_http_x_ipscanner_country;
    auth_request_set $ipscanner_site       $upstream_http_x_ipscanner_site;
    auth_request_set $ipscanner_request_id $upstream_http_x_ipscanner_request_id;
    error_page 403 = @ipscanner_blocked;

    proxy_set_header X-IPScanner-Status        $ipscanner_status;
    proxy_set_header X-IPScanner-Class         $ipscanner_class;
    proxy_set_header X-IPScanner-Action        $ipscanner_action;
    proxy_set_header X-IPScanner-Network-Class $ipscanner_network;
    proxy_set_header X-IPScanner-Anonymized    $ipscanner_anonymized;
    proxy_set_header X-IPScanner-Risk          $ipscanner_risk;
    proxy_set_header X-IPScanner-Country       $ipscanner_country;
    proxy_set_header X-IPScanner-Site          $ipscanner_site;
    proxy_set_header Host                      $host;
    proxy_set_header X-Forwarded-For           $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto         $scheme;
    proxy_pass http://app:80;
}

location = /_ipscanner {
    internal;
    proxy_pass http://ipscanner:8080/check;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-Original-Method $request_method;
    proxy_connect_timeout 1s;
    proxy_read_timeout 5s;
    error_page 500 502 503 504 = @ipscanner_down;
}

location @ipscanner_down {
    return 204;
}

location @ipscanner_blocked {
    internal;
    proxy_method GET;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-IPScanner-Request-Id $ipscanner_request_id;
    rewrite ^ /blocked break;
    proxy_pass http://ipscanner:8080;
}

Endpoints

  • /checkforward auth

    200 con las cabeceras X-IPScanner-*, o 403 con la página de bloqueo y X-IPScanner-Request-Id.

  • /blockedforward auth

    La página 403 para el ID de petición de X-IPScanner-Request-Id, para el error_page de nginx.

  • /healthzambos modos

    200 en JSON: versión, modo, sitio, backoff, último error y la política en caché.

/check lee la petición original de las cabeceras del proxy: X-Forwarded-For (o IP_HEADERS), User-Agent, X-Forwarded-Uri o X-Original-URI, y X-Forwarded-Method o X-Original-Method.

Configuración

  • IPSCANNER_API_KEY

    Tu clave de API. Sin ella, todas las peticiones pasan sin comprobar.

  • IPSCANNER_API_KEY_FILE

    Archivo con la clave, para secrets de Docker. Tiene prioridad sobre IPSCANNER_API_KEY.

  • SITE_ID

    Site ID del panel. El modo y la política vienen entonces del panel.

  • MODEpor defecto monitor

    monitor o enforce. Con un sitio, solo monitor tiene efecto: mantiene el sitio en modo monitor.

  • BLOCK_CLASSESpor defecto malicious_automation

    Sin sitio: clases que enforce bloquea, separadas por comas, entre malicious_automation, ai_agent, tor, vpn, proxy, hosting, relay o none.

  • TRUSTED_PROXIESpor defecto private

    Direcciones y rangos CIDR cuyas cabeceras de IP y URI se aceptan. private, cloudflare, o none para usar solo la dirección del par.

  • IP_HEADERSpor defecto X-Forwarded-For

    Cabeceras que se leen en orden desde un proxy de confianza. Detrás de Cloudflare: CF-Connecting-IP.

  • TIMEOUT_MSpor defecto 1500

    Presupuesto de una comprobación, en milisegundos. Si se agota, la petición pasa.

  • CACHE_TTLpor defecto 600

    Segundos que se reutiliza un veredicto por sitio, IP y user agent. También admite duraciones como 10m.

  • CACHE_SIZEpor defecto 10000

    Veredictos en memoria.

  • POLICY_TTLpor defecto 30

    Segundos que se reutiliza la política del sitio. También admite duraciones.

  • SKIP_PATHSpor defecto archivos estáticos

    Regex sobre la ruta, sin distinguir mayúsculas. Las peticiones que coinciden no se comprueban; vacío comprueba todas las rutas.

  • UPSTREAM_URL

    Activa el modo proxy: las peticiones permitidas van a esta URL.

  • LISTENpor defecto :8080

    Dirección de escucha.

  • HEALTH_PATHpor defecto /healthz

    Endpoint de salud.

  • BLOCK_MESSAGEpor defecto Blocked by IPScanner edge guard.

    Texto de la página 403.

  • IPSCANNER_API_URLpor defecto https://ipscanner.io

    URL base de la API.

  • DEBUGpor defecto false

    Registra cada decisión en una línea. En modo proxy también añade las cabeceras de estado, clase y acción a las respuestas.

Un valor no válido detiene el gate al arrancar con una línea de log. La clave de API nunca se registra.

IP del cliente

  • TRUSTED_PROXIES

    X-Forwarded-For solo se lee si el par está en esta lista, recorriendo la cadena hasta la primera dirección que no es un proxy de confianza. Para cualquier otro se usa la dirección del par.

  • private

    El valor por defecto. Encaja con un proxy que llega al gate por una red Docker o privada. Si otros en esa red pueden llegar al gate, pon la dirección de tu proxy.

  • :8080

    No publiques el puerto del gate: solo tu proxy debería llegar a él.

Cabeceras que añade

En un 200 de /check, para que tu proxy las pase a la app, y en la petición a tu app en modo proxy. El modo proxy quita las cabeceras X-IPScanner-* entrantes; los snippets hacen lo mismo en el proxy.

  • X-IPScanner-Statusok, error, timeout, backoff, skipped

    Si la comprobación se hizo y, si no, por qué.

  • X-IPScanner-Classhuman, verified_bot, ai_agent, …

    Clase de tráfico del visitante.

  • X-IPScanner-Actionallow, flag, block, would_flag, would_block

    Qué pasó con la petición. En modo monitor: would_flag y would_block.

  • X-IPScanner-Network-Classresidential_clean, hosting, vpn, tor, …

    Clase de red de la dirección.

  • X-IPScanner-Anonymizedtrue, false

    La red oculta dónde está el visitante.

  • X-IPScanner-Risk0-100

    Puntuación de riesgo de la dirección.

  • X-IPScanner-CountryDE

    País de la dirección.

  • X-IPScanner-Sitesite_...

    El Site ID, si hay uno.

Si una comprobación falla o se omite, solo se fija X-IPScanner-Status.

Petición a tu app
X-IPScanner-Status: ok
X-IPScanner-Class: ai_agent
X-IPScanner-Action: would_block
X-IPScanner-Network-Class: hosting
X-IPScanner-Anonymized: true
X-IPScanner-Risk: 60
X-IPScanner-Country: US
X-IPScanner-Site: site_4fQ8nZ2kLm7xR1vT9cBw

Caché y fail open

  • Política del sitioGET /v1/sites/{id}/policy

    Modo y política por clase, en caché 30 s. Si una actualización falla, la última política válida sigue 24 horas. Los cambios se aplican sin recargar.

  • Caché de veredictos

    Una comprobación por visitante (sitio, IP, user agent) cada 10 minutos, en memoria, por instancia del gate.

  • Fail open

    1,5 s para veredicto y política juntos. Ante un timeout o un error de la API la petición pasa y las comprobaciones paran 30 s, o 5 minutos tras un 401, 402, 403 o 429.

  • Gate caído

    nginx y NPM dejan pasar tras el timeout de conexión de 1 s, HAProxy manda el tráfico a la app al momento, Apache falla una petición y luego usa la app. Traefik responde 500 y Caddy 502, así que arranca el gate con restart: unless-stopped.

  • Siempre pasan

    Crawlers verificados, peticiones OPTIONS, direcciones privadas y de loopback, y rutas de la lista de exclusión.

  • Bloqueado

    Una página 403 con el ID de la petición. El modo monitor nunca bloquea.

  • Plan Free

    Los sitios del plan Free se quedan en modo monitor.

  • Coste

    2 unidades de petición por visitante fuera de caché. Las peticiones repetidas dentro de la ventana de caché son gratis.

Qué envía

  • POST /v1/edge/checkCuenta como 2 solicitudes

    Una llamada por visitante fuera de caché.

  • La política del sitio, si hay un Site ID.

  • X-IPScanner-Source: forward_auth

    Marca las llamadas como de esta integración en tu tráfico y tu uso.

  • User-Agent: ipscanner-forward-auth/<version>

    La versión, que se muestra en la página del sitio en el panel.

Cuerpo de la petición

  • sitestring

    El Site ID, si hay uno.

  • ipstring

    La dirección del visitante.

  • user_agentstring

    El User-Agent del visitante.

  • headersobject

    Accept, Accept-Language, Accept-Encoding, Sec-CH-UA* y Sec-Fetch-*, cada una recortada a 512 bytes.

  • request_idstring

    El X-Request-Id de la petición o un ID nuevo, que también aparece en la página de bloqueo.

La comprobación en el edge que hace
curl https://ipscanner.io/v1/edge/check \
  -H "Authorization: Bearer $IPSCANNER_API_KEY" \
  -H "X-IPScanner-Source: forward_auth" \
  -H "User-Agent: ipscanner-forward-auth/0.1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "site": "site_4fQ8nZ2kLm7xR1vT9cBw",
    "ip": "203.0.113.7",
    "user_agent": "Mozilla/5.0",
    "headers": {
      "accept-language": "en-US"
    },
    "request_id": "9f2c41b7d03e77f2"
  }'