domingo, 5 de enero de 2025

Nginx (III) Autenticación LDAP(II). Openresty. Cookies HttpOnly

1. Cookies HttpOnly

Este tipo de cookies son para que desde el cliente no se pueda acceder a dichas cookies. 

Si se entra en herramientas administrativas de google chrome, NO se verá esta cookie. 

Desde el cliente con Javascript no se puede crear una cookie tipo HttpOnly .

El objeto "session" de la request de FastHtml, utiliza este método pues no se pueden ver lo que almacena esta "session" desde el cliente.

2. Problemas de seguridad

El problema que estamos teniendo es que un posible atacante puede crear una cookie local con nombre "session_id" y engañar al sistema, pues estamos comprobando solamente que exista esta cookie. Por tanto se tendría que verificar el contenido de esta cookie para ver si el contenido de esta cookie es el que hemos generado nosotros. Para elll en openresty, se dispone de un repositorio de datos que es "ngx.shared" que es un tabla de diccionarios compartidos. En este caso utilizamos "ngx.shared.sessions" que contiene el "session_id" como clave de búsqueda y el nombre del usuario y el tiempo de duracion de la conexión. 

3. Configuración. 

En la carpeta (/usr/local/openresty/conf/ se tienen estos ficheros

  • 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:8001/ que se encuentra en la entrada server { listen 8001 server_name localhost...
  • nginx.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.
  • nginx.conf.initial: se aplica a la ruta /initial y realiza la validación. El servicio de validación se explica en el próxima entrada y es un programa python que se ejecuta en unicorv y que consulta el LDAP para la autenticación. Se accede a este servicio con http://127.0.0.1:5000/auth y mete un número aleatorio en una cookie de nombre "session_id" que es HttpOnly para que no se pueda manipular desde el cliente y evitar . Por tanto si aparece una cookie con
  • nginx.conf.protected: se aplica a las rutas que comienzan por /protected y lo que hace es eliminar el prefijo "/protected" de la ruta (tras comprobar que ya nos hemos validado) y le pasa el testigo a nginx.conf.softprop
  • nginx.conf.softprop: reenvía a la batería de servidores softpropsrv (como buen reverse proxy) y le añade las cabeceras para saber el host que va procvesar la petición ($host), la IP del cliente ($remote_addr), cadena completa de las IPs que han intervenido ($proxy_add_x_forwarded_for) y si utiliza http o https ($scheme)
  • nginx.conf.error: para la ruta  "/error" que nos muestra una pantalla de error.
  • nginx.conf.logout: para la ruta /logout que elimina la cookie que indica que estamos autenticados.
Resumiendo:
  • Hemos creado un fichero nginx.conf, y para cada enpoint o ruta hemos hecho un "include" de un fichero específico para tratar dicha ruta y tener por tanto ficheros mas manejables.
  • Se muestra una pantalla de login que hay que dar las credenciales de acceso al LDAP:                               

  • Para el servicio de validación de "login", se sebe crear un servicio que funciona con unicorv en la url http://127.0.0.1:5000/auth. Dicho servicio está hecho en python (xmopenresty.py) y se ve en la próxima entrada. Si no está arancado dicho servicio, no se va a validar el usuario sobre el LDAP.

Veamos los ficheros de configuración

3. nginx.conf (/usr/local/openresty/conf/nginx.conf)

worker_processes  auto;

error_log  /var/log/openresty/error.log notice;

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"';

    access_log  /var/log/openresty/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    
    # Enable Lua shared dictionary for sessions
    lua_shared_dict sessions 10m;
    lua_package_path '/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?.lua;;';
    lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';

    # Creamos una bateria de servidores para softprop con un solo servidor
    upstream softpropsrv {
        server 0.0.0.0:5001;
        #server el_que_sea.com;
    }        
    

    server {
        listen       8001;
        server_name  localhost;
        
        #Location of static files
        location /static/ {
            root /home/eduard/MyPython/05.fasthtml;  # Path where static files are stored
            expires max;
            access_log off;
        }
        
        location /softprop {
            include nginx.conf.softprop;
        }

        # Redirect root to login page
        location = / {
            return 301 /login;
        }
        
        # 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;
        }
} }


2.1 nginx.conf.login

            default_type text/html;
            content_by_lua_block {
                -- Check if already authenticated
                local session_id = ngx.var.cookie_session_id
                local sessions = ngx.shared.sessions
                
                if session_id and sessions:get(session_id) then
                    return ngx.redirect("/protected/softprop/tree")
                end
                
                local login_form = [[
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <title>Login</title>
                        <style>
                            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;
                            }
                        </style>
                    </head>
                    <body>
                        <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>
                                <button type="submit">Login</button>
                            </form>
                        </div>
                    </body>
                    </html>
                ]]
                ngx.say(login_form)
            }


2.2 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"]
                
                if not username or not password then
                    ngx.redirect("/login")
                    return
                end
                
                -- Create HTTP connection
                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", {
                    method = "POST",
                    body = cjson.encode({
                        username = username,
                        password = password
                    }),
                    headers = {
                        ["Content-Type"] = "application/json",
                    }
                })
                
                if not res then
                    ngx.log(ngx.ERR, "Failed to request: ", err)
                    ngx.redirect("/error")
                    return
                end
                
                local body = cjson.decode(res.body)
                
                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 session_id = ngx.encode_base64(resty_random.bytes(32))
                    sessions:set(session_id, username, 3600) -- 1 hour expiration
                    
                    -- Set session cookie
                    ngx.header["Set-Cookie"] = "session_id=" .. session_id .. "; HttpOnly; Path=/"
                    
                    -- Redirect to protected area
                    return ngx.redirect("/protected/softprop/tree")
                else
                    ngx.redirect("/error")
                end
            }


2.3  nginx.conf.protected

            #root /home/eduard/MyPython/05.fasthtml/menus;  # Base directory for static files
            access_by_lua_block {
                -- Check session
                local session_id = ngx.var.cookie_session_id
                local sessions = ngx.shared.sessions
                
                if not session_id or not sessions:get(session_id) then
                    return ngx.redirect("/login")
                end
            }
            
            # Remove /protected from the forwarded URI
            rewrite ^/protected/(.*) /$1 break;
            
            include nginx.conf.softprop;
           


2.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)
            }


2.5 nginx.conf.softprop

            # Decode the incoming request URI
            #set $decoded_path $uri;
            #rewrite_by_lua_block {
            #    local decoded_uri = ngx.unescape_uri(ngx.var.request_uri)
            #    ngx.var.decoded_path = decoded_uri
            #}
        
            # Proxy to the backend server with the decoded path
            #proxy_pass http://0.0.0.0:5001;
            proxy_pass http://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;

2.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)
            }

2.7 nginx.conf.logout

            content_by_lua_block {
                local session_store = ngx.shared.sessions
                local session_id = ngx.var.http_cookie and ngx.var.http_cookie:match("session=([^;]+)")

                if session_id then
                    session_store:delete(session_id)
                    ngx.header["Set-Cookie"] = "session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
                end
                
                ngx.redirect("/login")
            }








No hay comentarios :

Publicar un comentario