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
2. Descarga de GeoLite desde https://lite.ip2location.com/
3. Descargar la BD cada mes.
cd /usr/local/openresty/ip2location
curl -O "https://www.ip2location.com/download/?token=WLi6PORLxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCsEPf&file=DB1LITEMMDB"
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
# 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
-- 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
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; } } }