Ir al contenido
Q
QuoteNode

Wiki

GeoIP y control de acceso geográfico

Cómo QuoteNode usa una base de datos GeoIP local para el control de acceso por país y el seguimiento de ofertas.

GeoIP y control de acceso geográfico

QuoteNode incluye un control de acceso geográfico opcional y seguimiento por país mediante una base de datos GeoIP local por país (DB-IP Country Lite sin clave por defecto, o MaxMind GeoLite2).

Dos usos de GeoIP

1. Control de acceso a la aplicación

Cuando está activado, el filtro GeoIP puede restringir el acceso a la aplicación a determinados países. Las solicitudes desde países no permitidos reciben HTTP 403 (Forbidden).

Esto es útil para organizaciones que:

  • operan únicamente en jurisdicciones concretas,
  • quieren reducir la exposición a ataques automatizados desde ciertas regiones,
  • deben cumplir normativas de residencia de datos o control de acceso.

2. Seguimiento de interacciones con la oferta

Cuando un cliente abre un enlace público de oferta, el sistema registra el código de país del cliente (ISO 3166-1 alpha-2) junto con el evento de interacción. Esto aporta contexto geográfico a la analítica de ofertas:

  • «Tu oferta se abrió desde Alemania» — confirma alcance internacional,
  • «Múltiples aperturas desde distintos países» — puede indicar que la oferta se reenvió.

Cómo funciona

Base de datos

La resolución GeoIP usa una base de datos local por país en formato MaxMind DB (.mmdb) — no una API externa. Todas las búsquedas ocurren en memoria, sin llamadas de red.

Por defecto es la edición DB-IP Country Lite sin clave (licencia CC-BY-4.0, sin cuenta). El contenedor la descarga automáticamente al arrancar (y según una programación diaria) si falta o está obsoleta — independientemente de GEOIP_ENABLED, gobernado por GEOIP_AUTO_UPDATE (por defecto true). Por tanto, la base puede estar lista mientras la aplicación por país está desactivada. Sin descarga manual ni montaje de archivo. Para usar MaxMind GeoLite2 en su lugar, define MAXMIND_ACCOUNT_ID / MAXMIND_LICENSE_KEY (una cuenta gratuita de MaxMind); es el mismo formato .mmdb. También puedes rellenar el archivo manualmente con scripts/update-geoip.sh.

Detección de IP

La IP del cliente la resuelve íntegramente la aplicación — no se toma a ciegas de las cabeceras de la solicitud. Un X-Forwarded-For, X-Real-IP o CF-Connecting-IP falsificado desde un cliente directo nunca puede suplantar la comprobación de país.

  1. La aplicación toma el par de transporte (la dirección que realmente abrió la conexión).
  2. Si ese par no es interno, todas las cabeceras de reenvío se ignoran y se usa el par directamente. Un par es interno cuando es un proxy de confianza explícito o cualquier dirección no enrutable globalmente (privada/RFC 1918, loopback, link-local, CGNAT, IPv6 ULA) — que tras Docker/Coolify siempre es interna del contenedor.
  3. Si el par es interno, el cliente se deriva de sus metadatos de reenvío en orden: CF-Connecting-IP (de modo que un CDN como Cloudflare que reescribe X-Forwarded-For siga dando el cliente real) → X-Forwarded-For (recorrido de derecha a izquierda, omitiendo saltos internos) → X-Real-IP. Solo se aceptan IP literales (sin DNS).

Como los pares privados y los CDN se gestionan automáticamente, la mayoría de los despliegues no necesitan configuración — el Caddy integrado, un borde de Coolify o un CDN por delante funcionan de inmediato. La variable opcional SECURITY_TRUSTED_PROXIES (IP/CIDR literales separados por comas y/o nombres de host de servicio) solo añade confianza para un proxy inusual cuyo par de transporte sea una dirección pública.

La IP del cliente es un filtro aproximado de mejor esfuerzo, no autenticación. Cualquiera que pueda alcanzar el origen directamente (saltándose el CDN/proxy) podría presentar un CF-Connecting-IP elegido; el endurecimiento recomendado es restringir el origen con un firewall para que solo tu CDN/proxy pueda alcanzarlo.

Las direcciones de cliente no enrutables globalmente (loopback, privadas/RFC 1918, link-local, CGNAT, IPv6 ULA) nunca se geo-bloquean — no se pueden geolocalizar — de modo que los health checks de localhost y los despliegues LAN/same-host siguen funcionando mientras GeoIP está activo. La lista blanca de IP y la autenticación siguen aplicándoseles.

Rutas exentas

Las siguientes rutas están siempre exentas de las restricciones geográficas para que sigan siendo accesibles en todo el mundo:

  • enlaces públicos de oferta (/offer/public/*) y branding público (/api/v1/public/branding/*),
  • acciones de enlaces de correo: baja de suscripción (/api/v1/notifications/unsubscribe-token/*) y preferencias públicas (/api/v1/notifications/preferences-public/*),
  • endpoints de infraestructura/salud (/health*, /actuator/health, /actuator/info).

La autenticación de administrador (/api/v1/auth/login, 2FA) y los recursos autenticados siguen sujetos al control geográfico.

Configuración

Variable Por defecto Descripción
GEOIP_ENABLED false Solo interruptor de aplicación por país — también el interruptor de emergencia. No afecta a la carga de la base de datos: la BD se sigue cargando y actualizando (ver GEOIP_AUTO_UPDATE), por lo que puede estar lista mientras la aplicación está desactivada. false desactiva de forma fiable toda la aplicación por país (véase la recuperación más abajo).
GEOIP_DB_PATH /app/data/geoip/GeoLite2-Country.mmdb Ruta al archivo de base de datos .mmdb por país (se rellena automáticamente cuando GeoIP está activado).
GEOIP_AUTO_UPDATE true Descargar la base automáticamente al arrancar (DB-IP sin clave por defecto; MaxMind cuando MAXMIND_* están definidos) cuando falta o es más antigua que GEOIP_AUTO_UPDATE_MAX_AGE_DAYS (25 por defecto). Pon false para gestionar el archivo tú mismo.
SECURITY_GEOIP_ALLOWED_COUNTRIES (vacío) Códigos de país ISO 3166-1 alpha-2 separados por comas (p. ej. PL,DE,FR). Los códigos se normalizan a mayúsculas y se validan; los inválidos se rechazan. Vacío = sin restricción. Cuando se establece, este valor del operador prevalece sobre el ajuste del tenant en la aplicación.

Cuando no hay ninguna lista de países configurada, el filtro GeoIP es pasivo — resuelve códigos de país con fines de seguimiento pero no bloquea ninguna solicitud. Activar el filtrado por país en la interfaz de administración usa un selector de países validado (ISO 3166-1 alpha-2) y requiere al menos un país y una base de datos cargada.

Actualizar la base de datos: la descarga automática al arrancar la mantiene razonablemente al día, pero tras reemplazar manualmente el archivo .mmdb puedes recargarlo sin reiniciar desde Administración → Seguridad → estado efectivo → Recargar base de datos (una recarga protegida — un candidato corrupto o ausente conserva la base de datos cargada actualmente). Un reinicio del backend también toma el archivo nuevo. El panel de estado efectivo muestra si la base de datos está cargada y sus marcas de tiempo de compilación/carga.

Recuperación tras un bloqueo de GeoIP

Si una lista de países permitidos excluye accidentalmente tu propio país, recibes HTTP 403 con la pista de recuperación GEO_BLOCKED. Para recuperar el acceso:

  1. Establece GEOIP_ENABLED=false en el entorno del backend.
  2. Reinicia el backend (esto desactiva la aplicación de GeoIP antes de evaluar cualquier política).
  3. Corrige o vacía la lista de países permitidos, luego establece GEOIP_ENABLED=true y reinicia de nuevo.

Esto es independiente del interruptor de recuperación de la lista blanca de IP — recuperar uno nunca desactiva el otro de forma silenciosa.

Consideraciones de privacidad

  • La resolución GeoIP es sin estado — no se almacena de forma permanente ninguna correspondencia IP-país.
  • Para el seguimiento de ofertas solo se registra el código de país (p. ej. «ES»), no la dirección IP en sí.
  • Las direcciones IP registradas en los eventos web de ofertas están sujetas al trabajo configurable de anonimización de IP, que las hashea tras un número configurable de días para el cumplimiento del RGPD.

Last reviewed: Recently