lunes, 31 de marzo de 2025

Nginx (IX) Configurar Openresty para Filtrado de paises. Direcciones internas de intranet

0. Introducción

Veamos como podemos detectar las IP de una request:

0.1 Conexión directa sin proxy

En "lua" se puede hacer:

local client_ip = ngx.var.remote_addr
ngx.say("Client IP: ", client_ip)

0.2 Conexión del cliente tras un proxy

Se usa en el proxy X-Forwarded-For o X-Real-IP y en lua se puede obtener:

local client_ip = ngx.var.http_x_real_ip or ngx.var.http_x_forwarded_for or ngx.var.remote_addr
ngx.say("Client IP: ", client_ip)

Siendo:

  • ngx.var.http_x_real_ip → La mayoría de proxys especifican este encabezado (header)
  • ngx.var.http_x_forwarded_for → Puede devolver múltiples IPs (usese la primera).
  • ngx.var.remote_addr → Hace un Fallback si no se decta ningún proxy.

0.3 Extraer la primera IP de X-Forwarded-For

Para ello con lua se hace:

local forwarded_for = ngx.var.http_x_forwarded_for
local client_ip = forwarded_for and forwarded_for:match("([^,]+)") or ngx.var.remote_addr
ngx.say("Client IP: ", client_ip)

0.4 Indicar a nginx.conf que se encuentra tras "Trusted proxies" si és el caso

Para ello en el fichero de configuración de nginx , se le indica que que confíe en dichos proxys. La variable ngx.var.remote_addr nos devolverá la dirección IP correcta del cliente:

set_real_ip_from 192.168.1.0/24;  # Your proxys IP range
real_ip_header X-Forwarded-For;
real_ip_recursive on;

0.5 Resumiendo

  • Usar ngx.var.remote_addr si OpenResty iestá directamente expuesto.
  • Usar ngx.var.http_x_forwarded_for o ngx.var.http_x_real_ip si estamos tras un proxy
  • Actualizar  Nginx set_real_ip_from si usamos un "trusted proxy".

1. Prerequisitos

Hay que instalar estas dependencias:

sudo apt-get install libmaxminddb-dev
sudo luarocks install lua-resty-maxminddb
sudo luarocks install lua-resty-iputils

2. Descarga de GeoLite desde https://lite.ip2location.com/

Nos aparece esta pantalla:


y elegimos "Sign Up Free Account" y aparece esta panta que en la parte de abajo aparece:


Y elegimos "Use Google Account"

Ahora nos pide otra vez el login y le decimos que utilice la cuenta de google y ya entramos dentro y elegimos descargar la base datos DB1.LITE IP-COUNTRY en formato MMDB


Ahora se crea una carpeta, en mi caso /usr/local/openresty/ip2location y descargamos la base datos ahí y descomprimimos el fichero zip y luego copiamos el contenido a la carpeta padre /usr/local/openresty/ip2location para no tener tantas subcarpetas.

3. Descargar la BD cada mes. 

En este caso de la pantalla anterior tenemos el token, y utilizamos esta sentencia

cd /usr/local/openresty/ip2location
curl -O "https://www.ip2location.com/download/?token=WLi6PORLxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCsEPf&file=DB1LITEMMDB"

Pero puede que no vaya bien la sentencia pues solo admite 5 descargas al día. Es mejor descargarla directamente des de el navegador.

En principio la IP version 6 no vamos a utilizarla

Se podría generar un cron-tab para ello.

Si nos hubieramos registrado sin la cuenta de google, nos proporcianaría una "LICENSE KEY" y podríamos descargar el fichero así (según Claude AI)

cd /usr/local/openresty/ip2location
sudo wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz -O GeoLite2-Country.tar.gz
sudo tar -xzf GeoLite2-Country.tar.gz
sudo cp GeoLite2-Country_*/GeoLite2-Country.mmdb .
sudo rm -rf GeoLite2-Country_* GeoLite2-Country.tar.gz

y según Claude AI podríamos crear una entrada al crontab así:

# Add to crontab
(crontab -l 2>/dev/null; echo "0 0 1 * * wget -q https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz -O /tmp/GeoLite2-Country.tar.gz && tar -xzf /tmp/GeoLite2-Country.tar.gz -C /tmp && cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /usr/local/openresty/ip2location/ && rm -rf /tmp/GeoLite2-Country_* /tmp/GeoLite2-Country.tar.gz") | crontab -


4. Crear el módulo lua para validar IPs


Según "Claude" podemos crear este módulo "eu_filter.lua" dentro de /usr/local/openresty/nginx/conf

-- EU IP Filtering for OpenResty with internal network detection
-- Save this as /usr/local/openresty/nginx/conf/eu_filter.lua

local _M = {}

-- Define EU country codes
local eu_countries = {
    AT = true, BE = true, BG = true, HR = true, CY = true, CZ = true, 
    DK = true, EE = true, FI = true, FR = true, DE = true, GR = true, 
    HU = true, IE = true, IT = true, LV = true, LT = true, LU = true, 
    MT = true, NL = true, PL = true, PT = true, RO = true, SK = true, 
    SI = true, ES = true, SE = true
}

-- Define your internal network ranges
local internal_networks = {
    "10.0.0.0/8",     -- RFC1918 private IPv4 range
    "172.16.0.0/12",  -- RFC1918 private IPv4 range
    "192.168.0.0/16", -- RFC1918 private IPv4 range
    "127.0.0.0/8"     -- Localhost
}

-- Precompile internal networks using iputils
local iputils = require("resty.iputils")
local internal_cidrs = iputils.parse_cidrs(internal_networks)

-- Function to get real client IP when behind proxies
function _M.get_client_ip()
    -- Check for X-Forwarded-For header (common in proxy setups)
    local xff = ngx.req.get_headers()["X-Forwarded-For"]
    if xff then
        -- Extract the original client IP (first in the chain)
        local ip = xff:match("^([^,]+)")
        if ip then
            return ip
        end
    end
    
    -- Check for other common proxy headers
    local real_ip = ngx.req.get_headers()["X-Real-IP"]
    if real_ip then
        return real_ip
    end
    
    -- Fall back to direct connection IP
    return ngx.var.remote_addr
end

-- Function to check if IP is in internal networks
function _M.is_internal_ip(ip)
    -- ngx.log(ngx.ERR, "is_internal_ip: " .. ip)
    -- ngx.log(ngx.ERR, "iputils.ip_in_cidrs: " .. tostring(iputils.ip_in_cidrs(ip, internal_cidrs)))
    return iputils.ip_in_cidrs(ip, internal_cidrs)
end

-- Function to check if IP is from EU using MaxMind GeoIP
function _M.is_eu_ip(ip)
    local geoip = require("resty.maxminddb")
    local mmdb, err = geoip.open("/usr/local/openresty/geoip/GeoLite2-Country.mmdb")
    
    if not mmdb then
        ngx.log(ngx.ERR, "Failed to open GeoIP database: ", err)
        return false
    end
    
    local res, err = mmdb:lookup(ip)
    if not res then
        ngx.log(ngx.ERR, "IP lookup failed: ", err)
        return false
    end
    
    local country_code = res:get("country", "iso_code")
    if not country_code then
        return false
    end
    
    return eu_countries[country_code] or false
end

-- Main filter function
function _M.filter()
    local client_ip = _M.get_client_ip()
    
    -- Allow internal network IPs
    if _M.is_internal_ip(client_ip) then
        return true
    end
    
    -- Allow EU IPs
    if _M.is_eu_ip(client_ip) then
        return true
    end
    
    -- Reject all other IPs
    return false
end

return _M

Aquí hacemos 2 verifiaciones, o que sea una dirección interna de la intranet o que sea de un país de la Union Europea. En caso que no se cumpla ninguna de las condiciones entonces denegará el acceso.

Ahora veamos coo queda el fichero nginx.conf dentro de la misma carpeta. Pero debemos comprobar que la ruta del fichero "lua" anterior esté definido dentro del fichero "nginx.conf"

worker_processes  auto;

#error_log  logs/error.log;
error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    error_log /usr/local/openresty/nginx/logs/error.log info;

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    
    #--- BEGIN EDU
    # Enable Lua shared dictionary for sessions
    lua_shared_dict sessions 10m;
    
    # Load the resty modules we need
    lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/share/lua/5.1/resty/?.lua;/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/conf/?.lua;/usr/local/openresty/site/lualib/?.lua;/usr/local/openresty/site/lualib/resty/?.lua;;';
    lua_package_cpath './?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;/home/eduard/.luarocks/lib/lua/5.1/?.so;;';
        
    # Initialize the IP filter module at the http level
    init_by_lua_block {
        require("resty.core")
        require("resty.iputils").enable_lrucache()
    }
    # Enable cache
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=100m inactive=60m use_temp_path=off;

    # Creamos una bateria de servidores para softprop con un solo servidor
    upstream softpropsrv {
        server 0.0.0.0:5001;
        #server 127.0.0.1:5001;
        #server el_que_sea.com;
    }     
    # Hacemos un "rate limiting" para evitar un número de peticiones excesivas
    # Define rate limit (10 requests per second, with a burst of 20) por IP
    limit_req_zone $binary_remote_addr zone=my_api_limit:10m rate=10r/s;
    #--- END EDU
    
    

    server {
        #listen       8080;
        listen       8449 ssl; #->New
        
        server_name  localhost;
        #server_name  edu.tavernes.es;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        #-> Begin new
        # SSL certificate configuration
        ssl_certificate     /usr/local/openresty/nginx/conf/wildcard2023Nginx.crt;
        ssl_certificate_key /usr/local/openresty/nginx/conf/wildcard2023Nginx.rsa;
    
        # Optional SSL settings for security and performance
        ssl_protocols       TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;

        # Common proxy settings (optional)
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header Host             $host;
        proxy_set_header X-Forwarded-Proto https;
    

        location / {
            # Aplicamos el rate limiting a esta ruta	
            limit_req zone=my_api_limit burst=30 delay=10;
            
            content_by_lua_block{
                local filter = require("eu_filter")
                local client_ip = ngx.var.remote_addr
                if not filter.filter() then
					ngx.status = ngx.HTTP_FORBIDDEN
					ngx.say("Access denied: Your location is not permitted.")
					return ngx.exit(ngx.HTTP_FORBIDDEN)
				end
				
                
            }
            #root   html;
            #index  index.html index.htm;
        }
        # Location of static files
        location /static/ {
	    
	    #1. Solo ficheros dentro del servidor openresty (nginx)
            ## Are good both root and alias, but in alias you must append "static/" to the path
            ##root /home/eduard/MyPython/11.softprop-01;  # Path where static files are stored
            #alias /home/eduard/MyPython/11.softprop-01/static/;  # Path where static files are stored
            
            ## Use browser cache !!! 
            #expires max;
            #access_log off;
            #gzip on;
            #gzip_types text/css application/javascript image/svg+xml;
            
            #2. Solo ficheros dentro del uno de los servidores que actuan como web servers del upstream "softpropsrv"
            proxy_pass https://softpropsrv/static/;
            proxy_set_header Host $host;
            proxy_ssl_verify off;  # If using SSL
            proxy_cache my_cache;
            proxy_cache_valid 200 30d;
            proxy_cache_use_stale error timeout updating;
            add_header X-Cache-Status $upstream_cache_status;
        }
        
        # Serve the login form
        location = /login {
            include nginx.conf.login;
        }
        
        location = /initial {
            include nginx.conf.initial;
        }
        
        # Protected proxy location
        location /protected {
            include nginx.conf.protected;
        }
        
        #location = /success {
        #    include nginx.conf.success;
        #}
        
        location = /error {
            include nginx.conf.error;
        }
        
        location = /logout {
            include nginx.conf.logout;
        }
        
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}



No hay comentarios :

Publicar un comentario