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.
- La aplicación toma el par de transporte (la dirección que realmente abrió la conexión).
- El par solo se considera de confianza cuando es un proxy declarado explícitamente — una entrada en
SECURITY_TRUSTED_PROXIES(IP/CIDR literales separados por comas y/o nombres de servicio de Compose). El servicio integradofrontend/caddyse declara automáticamente, por lo que los stacks estándar de Docker/Coolify funcionan sin configuración. Una dirección privada/loopback/CGNAT no es de confianza solo por no ser enrutable globalmente. - Si el par no es un proxy de confianza, todas las cabeceras de reenvío se ignoran y el propio par es el cliente. Desde un proxy de confianza, el cliente es el único y autoritativo
X-Real-IP; en caso contrario, la cadenaX-Forwarded-Forrecorrida de derecha a izquierda omitiendo solo los saltos de confianza (la primera dirección no confiable desde la derecha es el cliente, aunque sea privada). Solo se aceptan IP literales (sin DNS). El backend nunca leeCF-Connecting-IP— Cloudflare se gestiona en el borde (más abajo).
Tras Cloudflare (automático). Cuando el despliegue está tras Cloudflare, el par de transporte estricto es una dirección de borde de Cloudflare, no el visitante. El borde Caddy integrado (el Caddy del frontend en Coolify y el Caddy autónomo) lo normaliza automáticamente: una solicitud se trata como Cloudflare solo cuando la IP que conecta está dentro de los rangos oficiales publicados de Cloudflare y CF-Connecting-IP es una única IP bien formada; entonces X-Real-IP se establece a ese visitante real y CF-Connecting-IP se elimina antes del backend. Un valor duplicado, en lista o mal formado falla de forma segura (fail-closed) a la dirección de borde. No añades los rangos de Cloudflare a SECURITY_TRUSTED_PROXIES, y el backend sigue sin confiar nunca en un CF-Connecting-IP proporcionado por el cliente. La lista de rangos se versiona y se actualiza con scripts/sync-cloudflare-ips.sh, protegida por una comprobación semanal de deriva.
Tras otro CDN. Un CDN que no sea Cloudflare no se reconoce automáticamente. Normaliza la IP del cliente en tu propio borde de la misma manera — establece X-Real-IP al visitante real y descarta las cabeceras de reenvío no confiables — antes de que el tráfico llegue al backend.
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 una cabecera de reenvío elegida; 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 (valor por defecto de la aplicación 35; la plantilla de producción fija 25 explícitamente). Pon false para gestionar el archivo tú mismo. |
GEOIP_AUTO_UPDATE_MAX_AGE_DAYS |
35 |
Antigüedad a partir de la cual la base se considera obsoleta y se vuelve a descargar. Es el valor por defecto de la aplicación; infra/.env.prod.example lo sobrescribe con un 25 explícito, así que un despliegue que omita la clave obtiene 35. |
GEOIP_UPDATE_SCHEDULER_ENABLED |
true |
Interruptor de la comprobación periódica de obsolescencia. Viene activado porque una base de países obsoleta es el estado inseguro. Desactivarlo no afecta al refresco de arranque ni a las actualizaciones manuales. |
GEOIP_UPDATE_CRON |
0 15 3 * * * |
Cuándo se ejecuta la comprobación periódica, en la zona horaria del servidor (UTC salvo que definas TZ). Una programación diaria es una comprobación diaria, no una descarga diaria: el fichero solo se descarga si falta o está obsoleto. El modelo soportado es un actualizador por ruta de base. |
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
.mmdbpuedes 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:
- Establece
GEOIP_ENABLED=falseen el entorno del backend. - Reinicia el backend (esto desactiva la aplicación de GeoIP antes de evaluar cualquier política).
- Corrige o vacía la lista de países permitidos, luego establece
GEOIP_ENABLED=truey 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.