1. Instalar nginx
Si lo instalamos en la máquina local
# 1. Instalar nginx en Ubuntu sudo apt update sudo apt install nginx # 2. Arrancar nginx sudo systemctl start nginx # 3. Verificar systemctl status nginx.service
Pero nos da el problema que elpuerto 80 ya está ocupado:
informatica@srv-glpi-16:~$ systemctl status nginx.service × nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.serv ice; enabled; preset: enabled) Active: failed (Result: exit-code) since Wed 2025-09-24 11:58:58 CEST; 1min 18s ago Docs: man:nginx(8) Process: 73037 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCC ESS) Process: 73038 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=1/FAILU RE) CPU: 11ms Sep 24 11:58:56 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) Sep 24 11:58:56 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use) Sep 24 11:58:57 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) Sep 24 11:58:57 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use) Sep 24 11:58:57 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) Sep 24 11:58:57 srv-glpi-16 nginx[73038]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use) Sep 24 11:58:58 srv-glpi-16 nginx[73038]: nginx: [emerg] still could not bind() Sep 24 11:58:58 srv-glpi-16 systemd[1]: nginx.service: Control process exited, code=exited , status=1/FAILURE Sep 24 11:58:58 srv-glpi-16 systemd[1]: nginx.service: Failed with result 'exi t-code'. Sep 24 11:58:58 srv-glpi-16 systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.
Para ello hay que actuar sobre la configuración de nginx y utilizar otro puerto
Para ello buscamos donde puede estar la línea "listen 80"
# 1. Buscar la cadena "listen" en los ficheros de estos directorios
grep -R "listen" /etc/nginx/sites-enabled/ /etc/nginx/conf.d/
Y aparece el fichero "/etc/nginx/sites-enabled/default"
Lo modificamos con nano y cambiamos "80" por "8088"
# 1. Cambiamos 80por 8088
server {
listen 8088 default_server;
listen [::]:8088 default_server;
Y arrancamos el servicio de nginx y lo verificamos:
# 1. Arrancar nginx sudo systemctl start nginx # 2. Verificar systemctl status nginx.service
Y ya lo tenemos en marcha
2. Substituir nginx por openresty
Veamos los pasos a realizar según Cloudspinx:
#1. Bloquear nginx
sudo systemctl disable nginxsudo systemctl stop nginx
#2. Instalar prerequisitos
sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates lsb-release#3. Importar la clave GPG de descarga (para Ubuntu 22 +)
wget -O - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg#4. Añadir el repositorio en sistemas x86_64 or amd64 para Ubuntu 22 +
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list > /dev/null#5. Actualizar el índice APT
sudo apt-get update#6. Instalar el paquete
sudo apt-get -y install openresty#6.1. Aquí da un error de que el puerto 80 está ocupado
# Por tanto hay que entrar en /usr/local/openresty/nginx/conf/nginx.conf
# y cambiar "listen 80;"
# por "listen 8080;" u otro puerto que no esté pillado
# y volver a ejecutar :
# sudo apt-get -y install openresty
#7. Verificar la instalación que debe devolver: nginx version: openresty/X.Y.Z
openresty -v#8. Arrancar/rearrancar/ para rel servicio
sudo systemctl start openrestysudo systemctl restart openrestysudo systemctl stop openresty#9. Permitir el servicio al arrancar (boot)
sudo systemctl enable openresty#10. Ver el estado del servicio
sudo systemctl status openresty
Ojo, ahora el fichero de configuración esta en una de estas 2 carpetas:- /etc/openresty
- /usr/local/openresty
Y los logs están en:
- /usr/local/openresty/nginx/logs/error.log
- /etc/openresty
- /usr/local/openresty
Y los logs están en:
- /usr/local/openresty/nginx/logs/error.log
Y aparece el mismo error que el puerto 80 está ocupado (después del punto 6)
Para solucionarlo hay que ir al fichero de configuración y cambiar el listen 80 por el puerto 8088 en el fichero "/usr/local/openresty/nginx/conf/nginx.conf"
Para ver que funciona abrimos el navegador con esta direccion:
http://IP_SERVIDOR:8088/
y vemos que funciona
Y aparece el mismo error que el puerto 80 está ocupado (después del punto 6)
Para solucionarlo hay que ir al fichero de configuración y cambiar el listen 80 por el puerto 8088 en el fichero "/usr/local/openresty/nginx/conf/nginx.conf"
Para ver que funciona abrimos el navegador con esta direccion:
http://IP_SERVIDOR:8088/
y vemos que funciona
3. Instalación de luarocks
Luarocks es un paquete de instación de módulos Lua para openresty
Para instalarlo hacemos:
# 1.Prerequisitos
sudo apt install build-essential libreadline-dev unzip
# 2. Descargar
wget https://luarocks.org/releases/luarocks-3.12.2.tar.gz
# 3. Descomprimir
tar zxpf luarocks-3.12.2.tar.gz
# 4. Compilar e instalar
cd luarocks-3.12.2
sudo ./configure && make && sudo make install
sudo luarocks install luasocket
lua
# Debe de aparecer
Lua 5.4.7 Copyright (C) 1994-2024 Lua.org, PUC-Rio
# 1.Instalar paquetes.
# En este caso instala el paquete lua-python para poder ejecutar modulos python
sudo luarocks install lua-resty-http
sudo luarocks install lua-resty-openssl
sudo luarocks install lua-resty-iputils
# 2. Ver los módulos instalados
luarocks list
Veamos algunos usos de luarocks
A la hora de instalar lua han aparecido algunos problemas que se han solucionado con chatgpt
# 1.Prerequisitos
sudo apt install build-essential libreadline-dev unzip
# 2. Descargar
wget https://luarocks.org/releases/luarocks-3.12.2.tar.gz
# 3. Descomprimir
tar zxpf luarocks-3.12.2.tar.gz
# 4. Compilar e instalar
cd luarocks-3.12.2
sudo ./configure && make && sudo make install sudo luarocks install luasocket lua
# Debe de aparecer
Lua 5.4.7 Copyright (C) 1994-2024 Lua.org, PUC-Rio
# 1.Instalar paquetes. # En este caso instala el paquete lua-python para poder ejecutar modulos pythonsudo luarocks install lua-resty-http sudo luarocks install lua-resty-opensslsudo luarocks install lua-resty-iputils # 2. Ver los módulos instalados luarocks list
4. Copiar los ficheros de configuración de openresty
Los ficheros a copiar al directorio /usr/local/openresty/nginx/conf són:
1. nginx.conf: que es el fichero de configuración principal. Para cada una de las rutas permitidas, se redirige a otros ficheros de configuración. En principio actua en http://localhost:5001/ que se encuentra en la entrada upstream softpropsrv {server 127.0.0.1:5001;
Utiliza el fichero eu_filter.lua para filtrar las IPs de los usuarios de Europa
Entre otras cosas realiza:
- con el fichero mime.types definimos los tipos de ficheros
- se definen las rutas con location (/, /login, /softprop,/ /static,/protected,/logout ...) y sus ficheros de includes
- un "rate limiting" para evitar un número de peticiones excesivas
- filtra para solo los usuarios de Europa
- definimos los ficheros donde se guardarán los accesos y los errores
- definimos donde estan los certificados y claves para ssl
- exigimos al cliente que presente certifiacado
- en ca_all_certificates.cer tenemos todos los certificados de las CA de confianza
- esd importante epecificar los ficheros "static" que se recogerán
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 127.0.0.1:5001;
#server 192.168.10.5:5001;
#server edu.tavernes.es:5001;
#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;
#resolver 192.168.10.5; # Servidor de DNS
#--- 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;
ssl_certificate /usr/local/openresty/nginx/conf/wildcard.tavernes.es.crt;
ssl_certificate_key /usr/local/openresty/nginx/conf/wildcard.tavernes.es.key;
# Client certificate part
ssl_client_certificate /usr/local/openresty/nginx/conf/ca_all.cer; # 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 https://0.0.0.0:5001/login; # 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;
return 301 /login;
}
# 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 /softprop {
# Timeout settings
# Problems with slow processes
#proxy_connect_timeout 90s; # Default is 60s
#proxy_send_timeout 90s; # Default is 60s
# Time for waiting a response
proxy_read_timeout 1000s; # Default is 60s
#send_timeout 90s; # Default is 60s
include nginx.conf.softprop;
}
#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;
}
}
}
2. nging.conf.login:se aplica a la ruta /login y muestra un formulario que pide usuario y contraseña; para validar envía a la ruta /inicial. Si ya se había validado anteriormente reenvia a /protected/softprop/tree. Si detecta la cookie que se ha generado en /initial te da como ya validado.Lee tres ficheros que definen la pantalla (form_style.css, form_script.js y form_body.html) y tambien el fichero xmauth.lua
default_type text/html;
content_by_lua_block {
-- Check if already authenticated
-- local session_id = ngx.var.cookie_session_id
-- local tab_id = ngx.var.cookie_tab_id
-- local sessions = ngx.shared.sessions
-- local tabs = ngx.shared.tabs
-- if session_id and sessions:get(session_id) and tab_id and tabs:get(tab_id) then
-- return ngx.redirect("/protected/softprop/tree")
-- end
-- Helper function to read the entire content of a file.
local function read_file(filename)
local mypath="/usr/local/openresty/nginx/conf/"
local new_filename = mypath .. filename
local f, err = io.open(new_filename, "r")
if not f then
ngx.log(ngx.ERR, "failed to open file ", new_filename, ": ", err)
return nil
end
local content = f:read("*a")
f:close()
return content
end
-- End Helper function
-- Create the tab_id
--local resty_random = require "resty.random"
--local tab_id = ngx.encode_base64(resty_random.bytes(32))
local xmauth = require "xmauth"
local tab_id = xmauth.random_string(32)
local n = 0
-- Read content of three files into three string variables.
local form_style = read_file("form_style.css","r")
local form_script = read_file("form_script.js","r")
local form_body = read_file("form_body.html","r")
local login_form = [[
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
$STYLE$
</style
</head>
<body>
<script>
$SCRIPT$
</script>
$BODY$
</body>
</html>
]]
login_form, n =string.gsub(login_form, "%$STYLE%$" , form_style)
login_form, n =string.gsub(login_form, "%$SCRIPT%$", form_script)
form_body , n =string.gsub(form_body , "%$TAB_ID%$", tab_id)
login_form, n =string.gsub(login_form, "%$BODY%$" , form_body)
-- ngx.log(ngx.INFO, login_form)
ngx.say(login_form)
}
3. nginx.conf.initial
content_by_lua_block {
-- Get authentication credentials
ngx.req.read_body()
local args = ngx.req.get_post_args() or {}
local username = args["username"]
local password = args["password"]
local tab_id = args["tab_id"]
-- certificate info
-- local cert = ngx.var.ssl_client_cert
local cert_verify=ngx.var.ssl_client_verify
local cert_dn=ngx.var.ssl_client_s_dn
ngx.log(ngx.INFO, "initial username " .. username )
ngx.log(ngx.INFO, "initial password " .. password )
ngx.log(ngx.INFO, "initial tab_id " .. tab_id )
ngx.log(ngx.INFO, "initial cert_dn " .. cert_dn )
if not username or not password or not tab_id then
ngx.redirect("/login")
return
end
-- Create HTTP connection
ngx.log(ngx.INFO, "initial validating user to python service" )
local http = require "resty.http"
local cjson = require "cjson"
local httpc = http.new()
-- Connect to Python auth service
-- local res, err = httpc:request_uri("http://127.0.0.1:5000/auth", {
-- Per a que accepte un nom de domini (com "edu.tavernes.es" cal tenir
-- una sentencia "resolver IP_del_servidor_DNS".
-- sinó no pot trobar el domini. Per tant cal ficar directament la Ip
-- La sentencia següent no va, perquè bno troba "edu.tavernes.es"
-- local res, err = httpc:request_uri("https://edu.tavernes.es:5000/auth", {
local res, err = httpc:request_uri("https://192.168.10.5:5000/auth", {
method = "POST",
body = cjson.encode({
username = username,
password = password,
cert_verify = cert_verify,
cert_dn = cert_dn
}),
headers = {
["Content-Type"] = "application/json",
},
-- if we have problems with the certificates ???
ssl_verify = false -- Disable SSL verification (useful for self-signed certs)
})
ngx.log(ngx.INFO, "initial after validating user to python service" )
if not res then
ngx.log(ngx.ERR, "Failed to request: ", err)
ngx.redirect("/error")
return
end
local body = cjson.decode(res.body)
local body_json=cjson.encode(body)
ngx.log(ngx.INFO, "initial body=" .. body_json )
if body.authenticated then
--- ngx.redirect("/success")
--- ngx.redirect("https://www.gva.es")
-- Create session
local sessions = ngx.shared.sessions
--local resty_random = require "resty.random"
local xmauth = require "xmauth"
--local session_id = ngx.encode_base64(resty_random.bytes(32))
--local session_id = xmauth.random_string(32)
local my_data = { username = username }
local my_data_json = cjson.encode(my_data)
--sessions:set(session_id, my_data_json , 3600) -- 1 hour expiration
sessions:set(tab_id, my_data_json , 3600) -- 1 hour expiration
local user_tab = sessions:get(tab_id) or "5"
ngx.log(ngx.INFO,"Initail tab_id + user_tab: " .. tab_id .. ' ' .. user_tab)
-- Set session and tab_id cookies for authentication
ngx.header["Set-Cookie"] = {
-- "session_id=" .. my_data_json .. "; HttpOnly; Path=/",
tab_id .. "=" .. my_data_json .. "; HttpOnly; Path=/",
-- "username=" .. username .. "; HttpOnly; Path=/"
}
local cookies = ngx.header["Set-Cookie"]
if type(cookies) == "table" then
for _, cookie in ipairs(cookies) do
ngx.log(ngx.INFO,"Set-Cookie Header: " .. cookie)
end
else
ngx.log(ngx.INFO,"Set-Cookie Header: " .. cookies)
end
--ngx.log(ngx.INFO, "initial redirecting to /protected/softprop/tree" )
ngx.log(ngx.INFO, "initial redirecting to /softprop/tree" )
-- Redirect to protected area
--local my_url="/protected/softprop/tree?tab_id=" .. ngx.escape_uri(tab_id)
-- Redirect to softprop instead of protected area
local my_url="/softprop/tree?tab_id=" .. ngx.escape_uri(tab_id)
ngx.log(ngx.INFO, "initial my_url=" .. my_url)
return ngx.redirect(my_url)
else
ngx.redirect("/error")
end
}
nginx.conf.protected
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 127.0.0.1:5001;
#server 192.168.10.5:5001;
#server edu.tavernes.es:5001; #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; #resolver 192.168.10.5; # Servidor de DNS #--- 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; ssl_certificate /usr/local/openresty/nginx/conf/wildcard.tavernes.es.crt; ssl_certificate_key /usr/local/openresty/nginx/conf/wildcard.tavernes.es.key; # Client certificate part ssl_client_certificate /usr/local/openresty/nginx/conf/ca_all.cer; # 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 https://0.0.0.0:5001/login; # 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; return 301 /login; } # 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 /softprop { # Timeout settings # Problems with slow processes #proxy_connect_timeout 90s; # Default is 60s #proxy_send_timeout 90s; # Default is 60s # Time for waiting a response proxy_read_timeout 1000s; # Default is 60s #send_timeout 90s; # Default is 60s include nginx.conf.softprop; } #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; } } }
default_type text/html; content_by_lua_block { -- Check if already authenticated -- local session_id = ngx.var.cookie_session_id -- local tab_id = ngx.var.cookie_tab_id -- local sessions = ngx.shared.sessions -- local tabs = ngx.shared.tabs -- if session_id and sessions:get(session_id) and tab_id and tabs:get(tab_id) then -- return ngx.redirect("/protected/softprop/tree") -- end -- Helper function to read the entire content of a file. local function read_file(filename) local mypath="/usr/local/openresty/nginx/conf/" local new_filename = mypath .. filename local f, err = io.open(new_filename, "r") if not f then ngx.log(ngx.ERR, "failed to open file ", new_filename, ": ", err) return nil end local content = f:read("*a") f:close() return content end -- End Helper function -- Create the tab_id --local resty_random = require "resty.random" --local tab_id = ngx.encode_base64(resty_random.bytes(32)) local xmauth = require "xmauth" local tab_id = xmauth.random_string(32) local n = 0 -- Read content of three files into three string variables. local form_style = read_file("form_style.css","r") local form_script = read_file("form_script.js","r") local form_body = read_file("form_body.html","r") local login_form = [[ <!DOCTYPE html> <html> <head> <title>Login</title> <style> $STYLE$ </style </head> <body> <script> $SCRIPT$ </script> $BODY$ </body> </html> ]] login_form, n =string.gsub(login_form, "%$STYLE%$" , form_style) login_form, n =string.gsub(login_form, "%$SCRIPT%$", form_script) form_body , n =string.gsub(form_body , "%$TAB_ID%$", tab_id) login_form, n =string.gsub(login_form, "%$BODY%$" , form_body) -- ngx.log(ngx.INFO, login_form) ngx.say(login_form) }
content_by_lua_block { -- Get authentication credentials ngx.req.read_body() local args = ngx.req.get_post_args() or {} local username = args["username"] local password = args["password"] local tab_id = args["tab_id"] -- certificate info -- local cert = ngx.var.ssl_client_cert local cert_verify=ngx.var.ssl_client_verify local cert_dn=ngx.var.ssl_client_s_dn ngx.log(ngx.INFO, "initial username " .. username ) ngx.log(ngx.INFO, "initial password " .. password ) ngx.log(ngx.INFO, "initial tab_id " .. tab_id ) ngx.log(ngx.INFO, "initial cert_dn " .. cert_dn ) if not username or not password or not tab_id then ngx.redirect("/login") return end -- Create HTTP connection ngx.log(ngx.INFO, "initial validating user to python service" ) local http = require "resty.http" local cjson = require "cjson" local httpc = http.new() -- Connect to Python auth service -- local res, err = httpc:request_uri("http://127.0.0.1:5000/auth", { -- Per a que accepte un nom de domini (com "edu.tavernes.es" cal tenir -- una sentencia "resolver IP_del_servidor_DNS". -- sinó no pot trobar el domini. Per tant cal ficar directament la Ip -- La sentencia següent no va, perquè bno troba "edu.tavernes.es" -- local res, err = httpc:request_uri("https://edu.tavernes.es:5000/auth", { local res, err = httpc:request_uri("https://192.168.10.5:5000/auth", { method = "POST", body = cjson.encode({ username = username, password = password, cert_verify = cert_verify, cert_dn = cert_dn }), headers = { ["Content-Type"] = "application/json", }, -- if we have problems with the certificates ??? ssl_verify = false -- Disable SSL verification (useful for self-signed certs) }) ngx.log(ngx.INFO, "initial after validating user to python service" ) if not res then ngx.log(ngx.ERR, "Failed to request: ", err) ngx.redirect("/error") return end local body = cjson.decode(res.body) local body_json=cjson.encode(body) ngx.log(ngx.INFO, "initial body=" .. body_json ) if body.authenticated then --- ngx.redirect("/success") --- ngx.redirect("https://www.gva.es") -- Create session local sessions = ngx.shared.sessions --local resty_random = require "resty.random" local xmauth = require "xmauth" --local session_id = ngx.encode_base64(resty_random.bytes(32)) --local session_id = xmauth.random_string(32) local my_data = { username = username } local my_data_json = cjson.encode(my_data) --sessions:set(session_id, my_data_json , 3600) -- 1 hour expiration sessions:set(tab_id, my_data_json , 3600) -- 1 hour expiration local user_tab = sessions:get(tab_id) or "5" ngx.log(ngx.INFO,"Initail tab_id + user_tab: " .. tab_id .. ' ' .. user_tab) -- Set session and tab_id cookies for authentication ngx.header["Set-Cookie"] = { -- "session_id=" .. my_data_json .. "; HttpOnly; Path=/", tab_id .. "=" .. my_data_json .. "; HttpOnly; Path=/", -- "username=" .. username .. "; HttpOnly; Path=/" } local cookies = ngx.header["Set-Cookie"] if type(cookies) == "table" then for _, cookie in ipairs(cookies) do ngx.log(ngx.INFO,"Set-Cookie Header: " .. cookie) end else ngx.log(ngx.INFO,"Set-Cookie Header: " .. cookies) end --ngx.log(ngx.INFO, "initial redirecting to /protected/softprop/tree" ) ngx.log(ngx.INFO, "initial redirecting to /softprop/tree" ) -- Redirect to protected area --local my_url="/protected/softprop/tree?tab_id=" .. ngx.escape_uri(tab_id) -- Redirect to softprop instead of protected area local my_url="/softprop/tree?tab_id=" .. ngx.escape_uri(tab_id) ngx.log(ngx.INFO, "initial my_url=" .. my_url) return ngx.redirect(my_url) else ngx.redirect("/error") end }
#root /home/eduard/MyPython/05.fasthtml/menus; # Base directory for static files access_by_lua_block { local xmauth = require "xmauth" local tab_id = ngx.var.arg_tab_id -- Get the 'tab_id' parameter if not tab_id then ngx.log(ngx.INFO, "nginx.conf.protected: 1 NO parameter tab_id") return ngx.redirect("/login") end ngx.log(ngx.INFO, "nginx.conf.protected: 2 parameter tab_id= '" .. tab_id ) local user = xmauth.is_authenticated(tab_id) if not user then ngx.log(ngx.INFO, "nginx.conf.protected: 3 NO user") return ngx.redirect("/login") end -- If the user is authenticated, you can proceed: -- ngx.say("Welcome, " .. user .. "!") ngx.log(ngx.INFO, "Protected: Welcome authenticated," .. user .. "!") return ngx.redirect("/login") } # Remove /protected from the forwarded URI #rewrite ^/protected/(.*) /$1 break; rewrite ^/protected/(.*) /$1 redirect; #include nginx.conf.softprop; #proxy_pass http://softpropsrv; # Forward to a backend if needed
4. nginx.conf.error
default_type text/html; content_by_lua_block { local error_page = [[ <!DOCTYPE html> <html> <head> <title>Error</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; } .error-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); text-align: center; color: #f44336; } .back-button { margin-top: 15px; padding: 8px 16px; background-color: #f44336; color: white; border: none; border-radius: 4px; text-decoration: none; display: inline-block; } .back-button:hover { background-color: #da190b; } </style> </head> <body> <div class="error-container"> <h2>Authentication Failed</h2> <p>Invalid username or password.</p> <a href="/login" class="back-button">Try Again</a> </div> </body> </html> ]] ngx.say(error_page) }
5. nginx.conf.softprop
access_by_lua_block { local url = ngx.var.request_uri ngx.log (ngx.INFO, "nginx.conf - location /softprop: 0 The current URL is: " .. url) local xmauth = require "xmauth" local tab_id = ngx.var.arg_tab_id -- Get the 'tab_id' parameter if tab_id then tab_id= tab_id:gsub("[^%w]","") ngx.log(ngx.INFO, "0. tab_id=" .. tab_id .. "0") end if url ~= "/softprop/login" then if not tab_id then -- Test if the tab_id is a post parameter local method = ngx.req.get_method() if method == "POST" then -- Read request body ngx.req.read_body() local post_args = ngx.req.get_post_args() -- Get POST parameters ngx.log(ngx.INFO, "nginx.conf - AAAAA-> post_args",table.concat(post_args, ", ")) local body_data = ngx.req.get_body_data() ngx.log(ngx.INFO, "nginx.conf - BBBBB-> body_data",body_data) -- 1. Assume the `hx-vals` passed a parameter named "tab_id" tab_id = post_args["tab_id"] if tab_id then tab_id= tab_id:gsub("[^%w]","") ngx.log(ngx.INFO, "1. tab_id=" .. tab_id .. "1") end -- 2. Maybe the it is a multipart/form-data if not tab_id and body_data then tab_id = body_data:match('name="tab_id"%s*(.-)%-%-') if tab_id then tab_id= tab_id:gsub("[^%w]","") ngx.log(ngx.INFO, "2. tab_id=" .. tab_id .. "2") end end end if not tab_id then ngx.log(ngx.INFO, "nginx.conf - location /softprop: 1 NO parameter tab_id redirecting to /login") return ngx.redirect("/login") end end --else ngx.log(ngx.INFO, "nginx.conf - location /softprop: 2 parameter tab_id= " .. tab_id ) local user = xmauth.is_authenticated(tab_id) if not user then ngx.log(ngx.INFO, "nginx.conf - location /softprop: 3 NO user redirectiong to /login") return ngx.redirect("/login") else -- If the user is authenticated, you can proceed: -- ngx.say("Welcome, " .. user .. "!") ngx.log(ngx.INFO, "/SOFTPROP: Welcome authenticated," .. user .. "!") end --end else ngx.log(ngx.INFO, "/SOFTPROP: Anem a /softprop/login per autehenticar-nos al python !") end } # Proxy to the backend server with the decoded path #proxy_pass http://0.0.0.0:5001; #proxy_pass http://softpropsrv; proxy_pass https://softpropsrv; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Ensure redirects from the backend work properly #proxy_redirect http://127.0.0.1:5001/ /;
6. nginx.conf.error:
default_type text/html; content_by_lua_block { local error_page = [[ <!DOCTYPE html> <html> <head> <title>Error</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; } .error-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); text-align: center; color: #f44336; } .back-button { margin-top: 15px; padding: 8px 16px; background-color: #f44336; color: white; border: none; border-radius: 4px; text-decoration: none; display: inline-block; } .back-button:hover { background-color: #da190b; } </style> </head> <body> <div class="error-container"> <h2>Authentication Failed</h2> <p>Invalid username or password.</p> <a href="/login" class="back-button">Try Again</a> </div> </body> </html> ]] ngx.say(error_page) }
7. nginx.conf.logout:
content_by_lua_block { local session_store = ngx.shared.sessions local tab_id = ngx.var.cookie_tab_id -- local session_id = ngx.var.http_cookie and ngx.var.http_cookie:match("session=([^;]+)") -- if session_id then if tab_id then session_store:delete(tab_id) ngx.header["Set-Cookie"] = "tab_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT" end ngx.redirect("/login") }
5. Ficheros adicionales para el nginx.conf
1. mime.types:
types { text/html html htm shtml; text/css css; text/xml xml; image/gif gif; image/jpeg jpeg jpg; application/javascript js; application/atom+xml atom; application/rss+xml rss; text/mathml mml; text/plain txt; text/vnd.sun.j2me.app-descriptor jad; text/vnd.wap.wml wml; text/x-component htc; image/avif avif; image/png png; image/svg+xml svg svgz; image/tiff tif tiff; image/vnd.wap.wbmp wbmp; image/webp webp; image/x-icon ico; image/x-jng jng; image/x-ms-bmp bmp; font/woff woff; font/woff2 woff2; application/java-archive jar war ear; application/json json; application/mac-binhex40 hqx; application/msword doc; application/pdf pdf; application/postscript ps eps ai; application/rtf rtf; application/vnd.apple.mpegurl m3u8; application/vnd.google-earth.kml+xml kml; application/vnd.google-earth.kmz kmz; application/vnd.ms-excel xls; application/vnd.ms-fontobject eot; application/vnd.ms-powerpoint ppt; application/vnd.oasis.opendocument.graphics odg; application/vnd.oasis.opendocument.presentation odp; application/vnd.oasis.opendocument.spreadsheet ods; application/vnd.oasis.opendocument.text odt; application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; application/vnd.wap.wmlc wmlc; application/wasm wasm; application/x-7z-compressed 7z; application/x-cocoa cco; application/x-java-archive-diff jardiff; application/x-java-jnlp-file jnlp; application/x-makeself run; application/x-perl pl pm; application/x-pilot prc pdb; application/x-rar-compressed rar; application/x-redhat-package-manager rpm; application/x-sea sea; application/x-shockwave-flash swf; application/x-stuffit sit; application/x-tcl tcl tk; application/x-x509-ca-cert der pem crt; application/x-xpinstall xpi; application/xhtml+xml xhtml; application/xspf+xml xspf; application/zip zip; application/octet-stream bin exe dll; application/octet-stream deb; application/octet-stream dmg; application/octet-stream iso img; application/octet-stream msi msp msm; audio/midi mid midi kar; audio/mpeg mp3; audio/ogg ogg; audio/x-m4a m4a; audio/x-realaudio ra; video/3gpp 3gpp 3gp; video/mp2t ts; video/mp4 mp4; video/mpeg mpeg mpg; video/quicktime mov; video/webm webm; video/x-flv flv; video/x-m4v m4v; video/x-mng mng; video/x-ms-asf asx asf; video/x-ms-wmv wmv; video/x-msvideo avi; }
2. eu_filter.lua:
-- 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
6. Ficheros adicionales para el nginx.conf.login
1. form_body.html:
<div class="login-container"> <h2>Login</h2> <form action="/initial" method="POST"> <div class="form-group"> <label for="username">Username:</label> <input type="text" id="username" name="username" required> </div> <div class="form-group"> <label for="password">Password:</label> <input type="password" id="password" name="password" required> </div> <input type="hidden" id="tab_id" name="tab_id" value="$TAB_ID$"> <!-- hidden --> <button type="submit">Login</button> </form> </div>
2. form_style.css:
body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; } .login-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .form-group { margin-bottom: 15px; } input { width: 100%; padding: 8px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } button { width: 100%; padding: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #45a049; }
3. form_script.js :
// Save to session storage the id_tab sent by the server window.onload = () => { tabId = document.getElementById("tab_id").value; sessionStorage.setItem("id_tab", tabId); };
4. xmauth.lua:
-- auth.lua ------------------------------------------------ -- Funció per a veure si s'esta autenticat ------------------------------------------------ local _M = {} -- Function to test if the user is authenticated. -- Returns the username if authenticated, or nil if not. function _M.is_authenticated( tab_id ) -- HttpOnly cookies cannot be accessed by ngx.var.http_cookie -- HttpOnly cookies must be accessed by ngx.var.cookie_<cookie_name> -- So the names of the cookies must be known beforehand -- This code is for not HttpOnly cookies -- local cookie_header = ngx.var.http_cookie -- local cookies = {} -- local cookie_header = ngx.var.http_cookie -- Get all cookies from the request -- if cookie_header then -- for key, value in string.gmatch(cookie_header, "([^=]+)=([^;]+)") do -- cookies[key] = value -- Store key-value pairs in a table -- ngx.log(ngx.INFO, "Cookie: " .. key .. " = " .. value) -- end -- end -- Let's access individual cookies: session_id --local session_id = ngx.var.cookie_session_id or "1" -- ngx.log(ngx.INFO, "xmauth: session_id=" .. session_id ) --local tab_id = ngx.var.cookie_tab_id or "2" -- ngx.log(ngx.INFO, "xmauth: tab_id=" .. tab_id ) --local username = ngx.var.cookie_username or "3" -- ngx.log(ngx.INFO, "xmauth: username=" .. username ) --local my_cookie_key = "session_id" -- Example dynamic cookie name -- Get the full cookie string from request headers if not tab_id then ngx.log(ngx.INFO, "xmauth: -1 param tab_id NOT found") else ngx.log(ngx.INFO, "xmauth: 0 param tab_id= '" .. tab_id ) end local cookie_string = ngx.var.http_cookie if not cookie_string then ngx.log(ngx.INFO, "xmauth: 1 No cookies found") return nil else -- Build the pattern to match the dynamic cookie name local pattern = tab_id .. "=([^;]+)" -- Extract the cookie value local cookie_value = cookie_string:match(pattern) if not cookie_value then ngx.log(ngx.INFO, "xmauth: 2 Cookie '" .. tab_id .. "' not found") return nil end end local sessions = ngx.shared.sessions local user_data_json = sessions:get(tab_id) if not user_data_json then ngx.log(ngx.INFO, "xmauth: 3 Cookie '" .. tab_id .. "' has no data") end local cjson = require "cjson" local success_user, user_data = pcall(cjson.decode, user_data_json) if not success_user then ngx.log(ngx.INFO, "xmauth: 4 Error getting data from Cookie '" .. tab_id ) return nil else return user_data.username end end function _M.random_string(length) math.randomseed(ngx.time() + ngx.worker.pid()) -- Seed RNG with time & worker ID local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" local str = {} for i = 1, length do local rand_index = math.random(1, #chars) str[i] = chars:sub(rand_index, rand_index) end return table.concat(str) end return _M
5. Copiar los certificados y rearrancar el servicio
Copiamos los certificados que tenemos
ca_all.cer, widlcard*
y rearrancamos el servicio
sudo systemctl restart openresty
y vamos a https://IP_SERVIDOR:8449/softprop/openresty y nos pide un certificado para acceder
No hay comentarios :
Publicar un comentario