1. Introducción
Los pasos a seguir son:
- Arreglar el módulo python (mnu_main.py) que ejecuta el servidor web (uvicorn) para que vaya en https al puerto 5001
- Arreglar el módulo python autenticación LDAP (xmopenresty.py) para que se ejecute sobre https en el puerto 5000
- En nginx.conf, cambiar el puerto al 5001 del servidor cuando se define la batería de servidores en "upstream softpropsrv"
- Cambiar en nginx.conf y todos sus "includes" (nginx.conf.softprop) aquellas sentencias proxy_pass http://softpropsrv por proxy_pass https://softpropsrv
- Cambiar en nginx.conf.initial la sentencia
- local res, err = httpc:request_uri("http://127.0.0.1:5000/auth", .... por
- local res, err = httpc:request_uri("https://127.0.0.1:5000/auth", ...
- Añadir el módulo lua xmauth.lua
2. mnu_main.py
Veamos el nuevo código del programa principal donde se ha cambiado fh.serve() por el código marcado en amarillo, donde se le indica el host, puerto y la ruta de los ficheros de la clave y el certificado de servidor
#!/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
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 si vamos a la consola y ejecutamos ./menus/mnu_main.py podemos ejecutar el programa gracias al shebang de la primera línea y vemos que se esta ejecutando sobre https
3. xmoprenresty.py
Veamos el modulo python de autenticación LDAP donde ya habíamos utilizado uvicorn directamente para utilizar el puerto 5000 y ahora le añadimos el protocolo https
#!/home/eduard/MyPython/11.softprop-01/venv_softprop/bin/python3
# auth_service.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn
import os
import sys
import base64
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
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 basicutils import xmldap
# ------Fin imprescindible
app = FastAPI()
class AuthRequest(BaseModel):
username: str
password: str
class AuthResponse(BaseModel):
authenticated: bool
def authenticate(username: str, password: str) -> bool:
"""
Your existing authentication function.
Replace this implementation with your actual authentication logic.
"""
# Example implementation
#if username == "admin" and password == "secret":
# return True
#return False
return xmldap.autheticate_by_login(username, password)
def is_valid_user(username: str) -> bool:
"""
Your existing authentication function.
Replace this implementation with your actual authentication logic.
"""
return xmldap.exists_user(username)
@app.post("/auth", response_model=AuthResponse)
async def auth(request: AuthRequest):
is_valid = authenticate(request.username, request.password)
return AuthResponse(authenticated=is_valid)
@app.post("/auth_username", response_model=AuthResponse)
async def auth_username(request: AuthRequest):
is_valid = is_valid_user(request.username)
return AuthResponse(authenticated=is_valid)
if __name__ == "__main__":
#uvicorn.run(app, host="127.0.0.1", port=5000)
uvicorn.run(app, host="0.0.0.0", port=5000, 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 si vamos a la consola y ejecutamos dentro su carpeta ./xmopenresty.py obtenemos
Pues el shebang de la primer línea permite obtener el entorno virtual de python y además lo más importante es que se está ejecutando bajo el protocolo https
4. Cambios en nginx.conf y nginx.conf.softprop de la carpeta /usr/local/openresty/nginx/conf/
Como no hemos cambiado la dirección del servidor ni el puerto (aunque si el protocolo http por https), el fichero nginx.conf queda igual que estaba, y solo cambia nginx.conf.softprop que se ha cambiado en proxy_pass el http por https
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 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/ /;
5. Cambios en nginx.conf.initial para la utenticación LDAP
Se cambia la sentencia
local res, err = http:request_uri("http://127.0.0.1:5000/auth", ....
por
local res, err = https:request_uri("https://127.0.0.1:5000/auth", ...
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"]
ngx.log(ngx.INFO, "initial username " .. username )
ngx.log(ngx.INFO, "initial password " .. password )
ngx.log(ngx.INFO, "initial tab_id " .. tab_id )
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("https://127.0.0.1:5000/auth", {
method = "POST",
body = cjson.encode({
username = username,
password = password
}),
headers = {
["Content-Type"] = "application/json",
}
})
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)
return ngx.redirect(my_url)
else
ngx.redirect("/error")
end
}
6. El módulo xmauth.lua
Se ha llamado en varios módulos y el código es:
-- 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