lunes, 24 de marzo de 2025

Nginx (VII) Configurar Openresty para que el servidor web con https.

1. Introducción

Los pasos a seguir son:

  1. Arreglar el módulo python (mnu_main.py) que ejecuta el servidor web (uvicorn) para que vaya en https al puerto 5001
  2. Arreglar el módulo python autenticación LDAP (xmopenresty.py) para que se ejecute sobre https en el puerto 5000
  3. En nginx.conf, cambiar el puerto al 5001 del servidor cuando se define la batería de servidores en "upstream softpropsrv"
  4. Cambiar en nginx.conf y todos sus  "includes" (nginx.conf.softprop) aquellas sentencias proxy_pass http://softpropsrv por proxy_pass https://softpropsrv 
  5. Cambiar en nginx.conf.initial la sentencia 
    1. local res, err = httpc:request_uri("http://127.0.0.1:5000/auth", .... por
    2. local res, err = httpc:request_uri("https://127.0.0.1:5000/auth", ...
  6. 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

No hay comentarios :

Publicar un comentario