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

 


No hay comentarios :

Publicar un comentario