miércoles, 2 de abril de 2025

Nginx (XI) Openresty. Resolución de problemas

 1. No encuentra xmauth.lua en la sentencia lua "require xmauth"

xmauth.lua es una función propia por tanto no no se puede descargar de internet. En principio se coloca en el mismo lugar que nginx.conf y luego se debe indicar a ruta a la variable lua_package_path

2. En un proceso largo (como la descarga de todos los decretos) aparece el error 504 Gateway Timeout 

Hay que darle un valor alto a la variable proxy_read_timeout por ejemplo 1000s (1000 segundos)
Si el cliente es lento (que no es el caso) existen estos parámetros: 
  • proxy_connect_timeout 90s;  # Default is 60s
  • proxy_send_timeout 90s;     # Default is 60s
  • send_timeout 90s;           # Default is 60s

3. No se redirige al endpoint /softprop

Es debido a que :
  • Se ha borrado la ruta location=endpoint {
  • No se redirige desde otra ruta

martes, 1 de abril de 2025

Nginx (X) Configurar Openresty para login con certificado cliente

 1. Obtener los certificados de las CA de confianza.

Podemos partir de descargar las autoridades de certificacióbn de confianza (CA ) que tenemos instaladas en Google Chrome, para ello:

  • En windows: Ir a chrome e indicar esta url: chrome://certificate-manager/crscerts,  que es donde se definen las CA de confianza y darle al botón "export" y elegir como nombre de fichero ca.cert y tipo Base64-encoded ASCII, multiple certificates (*.pem,; *.crt) y se guarda en una carpeta concreta.



  • En linux (Ubuntu), ejecutar:
    # Extract all system-trusted CAs (PEM format)
    sudo cp /etc/ssl/certs/ca-certificates.crt ~/ca.crt
    

Ahora que ya tenemos el fichero ca.cer. De todas formas podemos combinar ambos ficheros si tenemos los dos sistemas operativos. Si al fichero de windows lo llamamos ca_win.cer y al de ubuntu lo llamamos ca_linux.cer podemos ejecutar:

# Unir los ficheros
cat ca_win.cer ca_linux.cer > ca_all.cer


2. Configurar openresty para obtener los datos del certificdo de cliente


Añadimos a la configuración de TSL la parte para tratar el certificado del cliente. Veamos la parte básica adaptada a nuestro 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;
        
        # Client certificate part
        ssl_client_certificate /etc/nginx/ssl/ca.crt; # Set of all the CA certrificates
	ssl_verify_client on;  # Require client certificate
	ssl_verify_depth 2;
    
        # 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;
            
            # Get info from client certificate
            proxy_pass http://127.0.0.1:5000/;  # Forward request to Python app
	    proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
	    proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
	    proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
	    proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
            
            # Filter by UE country
            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;
        }

        location = /softprop {
            # Time for waiting a response
            proxy_read_timeout 1000s;    # Default is 60s
            include nginx.conf.softprop;
        }
#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; } } }


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;
        }
    }
}



martes, 25 de marzo de 2025

Nginx (VIII) Configurar Openresty para Caching, logging and Rate limiting.

 1. Caching

El caching permite ahorrar llamadas a los servidores, hay varios tipos de caching:

  • Nginx Proxy Cache : que cachea las respuestas de la granjua de servidores a los que llama (upstream servers). Se preferirá este método para las llamadas a los servidores
  • Lua Shared Dictionary :(lua_shared_dict) : que guarda pares de claves y valores. En principio este método es mejor cuando solo se utilizan llamadas tipo clave-valor
  • FastCGI Cache : Para respuestas FastCGI como PHP u otras peticiones FastCGI
Para nuestro caso utilizaremos la primer opción, que tiene estops parámetros:

Para la parte http de ngonx.conf se utiliza:
  • proxy_cache_path :La carpeta donde se guardan los datos cacheados
    • levels=1:2 : Estructura de las carpetas que guardan los datos
    • keys_zone=my_cache:10m Define una zona de memoria compartida de metadatos de 10MB
    • max_size=100m Define un tamaño máximo de 100MB
    • inactive=60m Elimina los datos que no se han usado durante 60 minutos
    • use_temp_path=off No permite usar almacenamiento temporal
Para la parte de rutas concretas a cachear (location) y se utilizan estos parámetros.
  • proxy_cache my_cache Permite cachear utilizando la zona my_cache.
  • proxy_cache_valid – Especifica durante cuanto tiempo se cachearan diferentes respuestas.
  • proxy_cache_bypass – Permite hacer un bypass del cache en determinadas condiciones
  • add_header X-Cache-Status $upstream_cache_status – Añade un caché adaptado para verificar el estado del caché (HIT, MISS, BYPASS).

Para ficheros estáticos, podemos optar para que el caché lo suministre el navegador, pero en este caso, los ficheros tienen estáticos tienen que estar dentro del servidor openresty (nginx) y no dentro de pasándole estos 2 parámetros:
  • expires max que le pide al navegador que guarde el fichero lo máximo posible
  • add_header Cache-Control "public, max-age=31536000, immutable"
    • public → Permite el caching tanto para el browser como los proxys
    • max-age=31536000 → Cachea el fichero para 1 año (in segundos).
    • immutable → Indica que el el fichero nunca cambia, por tanto no lo revalida nunca


Veamos el varios casos:

1.1 Cacheo de ficheros estáticos que se encuentran dento de una carpeta del servidor openresty (nginx)

Puede servir si el servidor nginx es el mismo que el servidor web

En este caso, lo cacheamos al browser (navegador)

worker_processes  auto;

error_log  /var/log/openresty/error.log notice;

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  /var/log/openresty/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    
    # Enable Lua shared dictionary for sessions
    lua_shared_dict sessions 10m;
    lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/openresty/nginx/conf/?.lua;;';
    lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';
    
    # 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;
    }        
    

    server {
        #->listen       8001;
        listen       8449 ssl; #->New
        
        server_name  localhost;
        #server_name  edu.tavernes.es;
        
        #-> 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 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;
        }
        
        location /softprop {
            include nginx.conf.softprop;
        }

        # Redirect root to login page
        location = / {
            return 301 /login;
        }
        
        # 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;
        }
    }
}

1.2 Cacheo de ficheros estáticos que se encuentran dentro de una carpeta del servidor web (uvicorn)

Par ello tenemos que modificar el program menu_main.py para definbir la ruta a la carpeta a ficheros estáticos con:
app.mount("/static", fh.StaticFiles(directory="/home/eduard/MyPython/11.softprop-01/static"), name="static")

#!/home/eduard/MyPython/11.softprop-01/venv_softprop/bin/python3

#1. Imports
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path

from sqlalchemy import Table
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
from menus.mnu_fh import fh, app
#OJO: No eliminar las dependencias marcadas en gris (routes01mnu, routes02form, routes03grid,app) pues, sinó falla el programa
from menus import routes00comp, routes01mnu, routes02form, routes03grid # No eliminar !!!
from menus.mnu_fh import fh, app                         # No eliminar app !!!
from basicutils import xmdb
from session.xmsessionmodels import SessionStoreAbst, SessionStore, SessionStoreHst       # No eliminar  !!!
# ------Fin imprescindible

from basicutils import xmdb 

# Create database tables
xmdb.Base.metadata.create_all(bind=xmdb.engine)

# Execute the web server, but now not using fh.serve but uvicorm.run instead
#fh.serve()
if __name__ == "__main__":
    import uvicorn
    app.mount("/static", fh.StaticFiles(directory="/home/eduard/MyPython/11.softprop-01/static"), name="static")
    uvicorn.run(app, host="0.0.0.0", port=5001, 
        ssl_keyfile="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard2023Nginx.rsa", 
        ssl_certfile="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard2023Nginx.crt")

Y para  cachear el servidor web nesa ruta , cambiamos la configuración del fichero  /usr/local/openresty/nginx/conf/nginx.conf

Y utilizamos una caché tipo nginx-proxy

worker_processes  auto;

error_log  /var/log/openresty/error.log notice;

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  /var/log/openresty/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    
    # Enable Lua shared dictionary for sessions
    lua_shared_dict sessions 10m;
    lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/openresty/nginx/conf/?.lua;;';
    lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';
    
    # 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;
    }        
    

    server {
        #->listen       8001;
        listen       8449 ssl; #->New
        
        server_name  localhost;
        #server_name  edu.tavernes.es;
        
        #-> 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 of static files
        location /static/ {
	    
	    #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;
        }
        
        location /softprop {
            include nginx.conf.softprop;
        }

        # Redirect root to login page
        location = / {
            return 301 /login;
        }
        
        # 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;
        }
    }
}


2. Logging

El logging ya lo deifinimos inicialmente en nginx.conf con el siguiente código parcial:

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  /var/log/openresty/access.log  main;


3. Rate limiting

Mediante el "rate limiting" se controla el número de peticiones que un cliente puede hacer en un instante. En principio sirve para prevenir ataques DDoS (distributed denial of service) o de fuerza bruta o un uso excesivo de la API.

Se utilizan estas 2 directivas que contienen parámetros:

  1. limit_rq_zone: se define en el apartado http {..} y se le da un nombre (en este caso api_limitcon el parámetro zone, un tamaño (10 MB)y una tasa de peticiones (request) por segundo con rate, si se quiere hacer un seguimiento sobre la IP del cliente se usará $binary_remote_addr, y si se quiere hacer por usuario se usará $http_authorization
    http {
        # Define rate limit (10 requests per second, with a burst of 20)
        limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    
    Se pueden definir varios limit_req_zone, teniendo la precaución de asignar diferentes nombres.      
  2. limit_req:  se define en el apartado del endpoint (direccion) afectado y tiene 2 parámetros, el primero zone identifica el nombre del limit_rq_zone anterior, busrt que indica el número de peticiones temporales a modo de ráfaga que puede admitir, y nodelay que rechaza el exceso de request inmediatamente si sobrepasan los umbrales indicados. En vez de nodelay se puede indicar delay con ún número de request a retrasar, y sobrepasado este límite se rechazan. Este ejemplo no es el real !!!

http {
    limit_req_zone $binary_remote_addr zone=low_limit:10m rate=5r/s;
    limit_req_zone $http_authorization zone=high_limit:10m rate=20r/s;

    server {
        listen 80;

        location /api/slow/ {
            limit_req zone=low_limit burst=10 nodelay;
            proxy_pass http://backend;
        }

        location /api/fast/ {
            limit_req zone=high_limit burst=30 delay=10;
            proxy_pass http://backend;
        }
    }
}

Si queremos comprobar  si va bien o no el rate limiting, se puede ejecutar este shell:

ab -n 50 -c 1 http://yourserver/api/

Veamos que problemas pueden surgir según el ChatGPT y como solucionarlo:

ErrorCauseFix
503 Service UnavailableRequests exceed rate and burst limit.Increase burst or use delay.
Rejected requests even when under limitRequests exceed rate per microsecond.Use a higher burst value.
IP is blocked too longRequests accumulate in the zone.Use a smaller zone size (5m instead of 10m).

4. Filtrado por paises NO VA?????

4.1 Instalar ip2location-resty

sudo opm get ip2location/ip2location-resty
 
y se instala en  /usr/local/openresty/site/lualib
y en concreto copia estas 2 librerías: ip2location.lua y ip2locationwebservice.lua


4.2 Indicar la ruta de las librerías "lua" instaladas en el nginx.conf

La variable lua_package_path debe incluir "/usr/local/openresty/site/lualib/?.lua" que es donde se ha instalado.

En mi caso es:

lua_package_path '/usr/local/openresty/site/lualib/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/openresty/nginx/conf/?.lua;/usr/local/openresty/nginx/conf/?.lua;;'


4.3 Descargar la BD "gratis" desde https://lite.ip2location.com/

Seleccionar o "Sign Up" en la parte superior dercha o "Sign Up Free Account"

Aparece una pantalla y má sabajo podemos usar una cuenta Google en vez de crear una cuenta nueva
 

Nos pide seleccionar una cuenta

A continuación nos pide que hagamos login y después usamos otra vez la cuenta de google

Ahora podemos descargar la base de datos. En principio hay que descargar el formato BIN.




Para detectar fraudes solo me hace falta la de IP-COUNTRY

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.



Supongamos que queremos que solamente puedan acceder los paises de la Unión Europea, veamos los pasos a seguir:

4.1 Instalar el módulo GeoIP2 Module

sudo apt update
sudo apt install libmaxminddb-dev

4.2 Descargar la BD GeoIP2

mkdir -p /usr/share/GeoIP
cd /usr/share/GeoIP
wget https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb

4.3 Configurar OpenResty para usar GeoIP2

Veamos como queda esta parte en nginx.conf. Se plantea crear un map para definir los paises qu tienen permiso (esto es solo un ejemplo)


http {
   #Definimos la dependencia GeoIP
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_code country iso_code;
    }
    
    #Definimos los paises que pueden acceder EU (European Union)
    map $geoip2_data_country_code $allowed_country {
        default 0;
        # European Union countries
        ~^(AT|BE|BG|HR|CY|CZ|DK|EE|FI|FR|DE|GR|HU|IE|IT|LV|LT|LU|MT|NL|PL|PT|RO|SK|SI|ES|SE)$ 1;
    }

    server {
        listen 443 ssl;
        server_name yourdomain.com;

        location / {
            # Only allowed countries
            if ($allowed_country = 0) {
                # Allow access or perform specific logic
                return 403 "Access Denied";
            }
            proxy_pass http://backend;
        }
    }
}


5. Configuración final


Veamos los ficheros afectados de la carpeta /usr/local/openresty/nginx/conf.
Se indica en estos colores:

Amarillo: filtrado de paises
Naranja: Rate limiting
Azul:  Caché

nginx.conf

worker_processes  auto;

error_log  /var/log/openresty/error.log notice;

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  /var/log/openresty/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    
    # Enable Lua shared dictionary for sessions
    lua_shared_dict sessions 10m;
    lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/openresty/nginx/conf/?.lua;;';
    lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';
    
    # 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;
    
    
    #Definimos la dependencia GeoIP
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_code country iso_code;
    }
    
    #Definimos los paises que pueden acceder EU (European Union)
    map $geoip2_data_country_code $allowed_country {
        default 0;
        # European Union countries
        ~^(AT|BE|BG|HR|CY|CZ|DK|EE|FI|FR|DE|GR|HU|IE|IT|LV|LT|LU|MT|NL|PL|PT|RO|SK|SI|ES|SE)$ 1;
    }
    

    server {
        #->listen       8001;
        listen       8449 ssl; #->New
        
        server_name  localhost;
        #server_name  edu.tavernes.es;
        
        #-> 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 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;
            
                

        }
        
        location /softprop {
            include nginx.conf.softprop;
        }

        # Redirect root to login page
        location = / {
            # Aplicamos el rate limiting a esta ruta	
            limit_req zone=my_api_limit burst=30 delay=10;
        
            # Only allowed countries
            if ($allowed_country = 0) {
                # Allow access or perform specific logic
                return 403 "Access Denied";
            }
            return 301 /login;
        }
        
        # 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;
        }
    }
}

PROBLEMAS:


1. nginx[983041]: nginx: [emerg] unknown directive "geoip2" in /usr/local/openresty/nginx/conf/nginx.conf:44

La sentencia en cuestión es:

    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_code country iso_code;
    }

Hacemos las siguientes averiguaciones