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.
- 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.
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