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 proxy’s 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
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;
}
}
}