Mostrando entradas con la etiqueta tsl. Mostrar todas las entradas
Mostrando entradas con la etiqueta tsl. Mostrar todas las entradas

martes, 14 de enero de 2025

Nginx (V) Openresty Autenticación LDAP(IV). Manejo de sesiones y TSL (https)

1. Configurando openresty con TSL

Veamos el fichero de configuración de openresty (/usr/local/openresty/nginx/conf/nginx.conf)

Las líneas importantes son:

39: Le indicamos el puerto ssl 8449

46-47: Le damos la ruta del certificado de servidor

50-51: Protocolos

54-57: Propiedades del proxy para pasar información al servidor

62-101: Se indican las posibles rutas a utiizar y el tratamiento de las mismas. Para ello se hace includes de ficheros que están en las mismas carpetas. Estos ficheros ya se vieron en una entrada anterior.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
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"';

    error_log /usr/local/openresty/nginx/logs/error.log info;

    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;/usr/local/openresty/nginx/conf/?.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 127.0.0.1:5001;
        #server el_que_sea.com;
    }        
    

    server {
        #->listen       8001;
        listen       8449 ssl; #->New
        
        server_name  localhost;
        #server_name  edu.tavernes.es;
        
        #-> Begin new
        # SSL certificate configuration
        ssl_certificate     /usr/local/openresty/nginx/conf/wildcard2023Nginx.crt;
        ssl_certificate_key /usr/local/openresty/nginx/conf/wildcard2023Nginx.rsa;
    
        # 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 of static files
        location /static/ {
            root /home/eduard/MyPython/11.softprop-01;  # 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. Manejo de sesiones en Python

En cualquier ruta, validamos la sesión con "xmsession.validate(tab_id=tab_id, req=req)"

veamos un ejemplo del tratamiento de un ruta

@app.get("/softprop/tree")
def geto2(tab_id:str | None= None, req=None):#request:Request): 
	''' Execute the main view that is tree menu'''
	
	# 1. Validem que haja tab_id i que l'usuari estiga en loa sessió de la BD
	my_response,myerror=xmsession.validate(tab_id=tab_id, req=req)
	if (len(myerror.strip())>1):
		print ("redirecting from /softprop/tree" )
		return my_response

Y el sistema te rebota a la pantalla de login si considera que no se ha validado convenientemente la sesión. 

Veamos que es lo que se valida, para ello veamos el código de xmsession.py y en concreto lo marcado en amarillo claro.

Primero se verifica que haya un tab_id y después que exista en la base de datos una sessión referenciada por el tab_id. SI no se cumplen las condiciones te envia a la pantalla de "login" para que te identifiques

El tab_id nos sirve para poder separar las sesiones que hay no solo entre diferentes navegadores o ventanas del navegador, sino también entre diferentes pestañas.

Hay una función "test_session" en rosa claro, que verifica que el tab_id se encuentra en una cookie y que además está guardada en la BD y que el navegador y la IP coinciden (esto puede dar lugar a fallos en dispositivos móviles si cambian la conexión).

from datetime import datetime, timedelta, timezone
import uuid
import orjson as json
from sqlalchemy.orm import Session
from fasthtml import common as fh
import os 

#------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 xmjwt, xmother, xmldap, xmdb
from session.xmsessionmodels import SessionStore, SessionStoreAbst, SessionStoreHst, SessionExistence
#from basicutils.xmdb import dbSingleton
# ------Fin imprescindible




def delete_old_sessions(db: Session, days: int=1):
	'''Delete old tokens from the db'''
	#1. Get the date to delete
	date_to_delete=datetime.now(timezone.utc) - timedelta(days=days)
	#2. Delete the old tokens
	db.query(SessionStore).filter(SessionStore.creation_date < date_to_delete).delete()
	db.commit()	

#-------------------------------------------------------------------	

def get_is_nginx(req)->bool:
	isnginx=False
	try:
		headers = req.headers
		isnginx = ("x-forwSessionarded-for" in headers or "x-forwarded-proto" in headers)
	except Exception: pass	
	return isnginx

def get_user_agent(req)->str:
	useragent=None	
	try:
		useragent=req.headers.get("User-Agent",'')
	except Exception: pass
	return useragent

def get_user_ip(req)->str:
	user_ip=None
	if get_is_nginx(req):
		x_forwarded_for = req.headers.get('X-Forwarded-For')
		if x_forwarded_for:
			# If there is more than one IP, the first is the client IP
			try:
				user_ip = x_forwarded_for.split(',')[0]
			except Exception:
				user_ip=req.headers.get("X-Real-IP",'') 
	else:
		# Fallback to the direct client IP
		user_ip = req.client.host
	return user_ip	

def get_req_info(req):
	'''
	Get client ip, user agent, is_nginx .... from request
	'''
	user_agent=user_ip=""
	is_nginx=False

	is_nginx=get_is_nginx(req)
	user_agent=get_user_agent(req)
	user_ip=get_user_ip(req)
	return user_ip, user_agent, is_nginx


def set_session(req, db: Session, user: str, tab_id:str)->tuple:
	''' creates the session in a DB after the login '''
	user_ip,user_agent,is_nginx=get_req_info(req)
	mysession=SessionStoreAbst(description=tab_id, user=user, user_agent=user_agent, user_ip=user_ip, is_nginx=is_nginx)	
	mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst)
	if not mysession: return "", "Cannot create session"
	return user,''

def test_session(req, db: Session, tab_id:str)->tuple:
	'''Tests if the session in the DB and the cookie (tab_id) has the same values'''
	# 1. Get the user from the tab_id cookie
	cookie_value= req.cookies.get(tab_id)
	if not cookie_value: return "","No tab_id cookie found"
	my_dict= json.loads(cookie_value)
	user=my_dict.get("username",'')
	user_ip,user_agent,is_nginx=get_req_info(req)

	# 2. Get the session from the DB
	mysession=db.query(SessionStore).filter_by(description=tab_id).first()
	
	# 3. If is nginx and has no session let's create a session
	if not mysession and is_nginx:
		mysession=SessionStoreAbst(description=tab_id, user=user, user_agent=user_agent, user_ip=user_ip, is_nginx=is_nginx)	
		mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst)

	# 4. If it has no session there is a problem !
	if not mysession: return "","Session not existing."
	
	# 3. Compare values.
	if mysession.user != user: return "","Users are different: "+user + "!=" + mysession.user

	if mysession.user_agent != user_agent: return "","Users agents are different: "+user_agent + "!=" + mysession.user_agent  

	if mysession.is_nginx != is_nginx: return "","Nginx problem: session is_nginx is different from is_nginx "

	if mysession.user_ip != user_ip: return "","User IPs are different: "+user_agent + "!=" + mysession.user_agent     
	
	return user,''

def validate(tab_id:str, req)->tuple:
	# 1. Si es la primera vegada, no tenim tab_id i envia un script per a arreplegar-lo
	if tab_id is None:
		redirection='/softprop/login'
		a=fh.RedirectResponse(redirection, status_code=303)
		print('enviem a  /softprop/login perque NO hi ha TAB_ID' )	
		return a, "no hi ha tab_id" 
		

	#2. Si tenim tab_id 
	with xmdb.session_local() as db:
		user, myerror=test_session(req, db, tab_id)
	
	if (len(user.strip())==0 or (len(myerror.strip())>0)):
		redirection='/softprop/login'
		a=fh.RedirectResponse(redirection, status_code=303)
		print('enviem a  /softprop/login perque:' + myerror)	
		return a, myerror  

	return '',''

def read_session_info(tab_id:str, db: Session)->dict:
	'''Get the field info from the table "x_session_store" with id=tab_id'''
	# 1. Get the session from the DB
	mysession=db.query(SessionStore).filter_by(description=tab_id).first()
	
	if not mysession : return {}
	
	info=mysession.info
	try:
		info_dict=json.loads(info)
	except json.JSONDecodeError:
		info_dict = {}
	return info_dict

def write_session_info(tab_id:str, db: Session, info:dict):
	'''Writes the field info from the table "x_session_store" with id=tab_id'''
	# 1. Get the session from the DB
	mysession=db.query(SessionStore).filter_by(description=tab_id).first()
	
	if mysession : 
		info_json=json.dumps(info).decode("utf-8")
		mysession.info=info_json
		mysession=xmdb.merge_to_main_and_histo(db, mysession, SessionStore, SessionStoreHst)
	
	





miércoles, 8 de diciembre de 2021

Instalar certificado LetsEncrypt en Tomcat Ubuntu manual. Shell de actualización del certificado

 0. Introducción

Vamos a seguir a Mahdi Mashrur Matin a ver si esta vez sale bien. 

1. Prerequisitos

  • Tener privilegio de superusuario
  • Tener un dominio registrado ("webprop.es" en mi caso)
  • Versión de Java reciente instalada
  • Versión reciente de Tomcat instalada
  • Tener abiertos los puertos 80 y 8443 en el firewall
  • Tener instalado Openssl

2. Instalar Certbot

Tal como vimos en el fallido blog anterior, primero añadimos al repositorio

  • sudo add-apt-repository ppa:certbot/certbot

Instalamos el paquete Apache Certbot

  • sudo apt install python-certbot-apache


3. Genera keypair y el certificado sobre el dominio con Certbot.


En el fallido blog anterior, estos ficheros si que los tenemos (por tanto podemos saltar este paso). 

Para los que no hayais pasado por el blog anterior, debeis ejecutar:

sudo certbot certonly --standalone -d myweb.es

Y si toda ha ido bien, en el directorio /etc/letsencrypt/live/myweb.es (OJO: normalmente las direcciones suelen ser "csv.rocasdemar.es"  o algo por el estilo, siendo el directorio /etc/letsencrypt/live/csv.rocasdemar.es) Deben existir estos ficheros:
  • cert.pem (certificado "solo")
  • chain.pem (cadena de certificados del root)
  • fullchain.pem (certificado "solo" + cadena de certificados del root. Equivale a los 2 ficheros anteriores juntos)
  • privkey.pem (clave privada)
Si no utilizais LetsEncript, tenéis que realizar estos pasos:
=============================================================
1. Generar un par de clave privada y el CSR público (Certificate Signing Request) para el servidor web ("server"), con este comando

openssl req -nodes -newkey rsa:2048 -keyout domain.name.key -out domain.name.csr

2. Enviar el CSR al la entidad certificadora (CA) y ésta verificará tu dominio, y te suministrará un certificado (fichero .crt)  y cadena de certificados (certificate chain)
=============================================================

4. Configurar https en el puerto 19443 u otro de forma fácil (Opción recomendada)

Modificamos el conector en el fichero server.xml de la carpeta conf de tomcat y para el puerto 19443 para que quede así

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 <Connector port="19443"
     protocol="HTTP/1.1"
     SSLEnabled="true"
     maxThreads="150"
     scheme="https"
     secure="true"
     sslProtocol="TLS"

     clientAuth="true"
     keyAlias="tomcat"


     SSLCertificateFile="/etc/letsencrypt/live/midominio.es/cert.pem"
     SSLCertificateChainFile="/etc/letsencrypt/live/midominio.es/chain.pem"
     SSLCertificateKeyFile="/etc/letsencrypt/live/midominio.es/privkey.pem"
 />


5. Renovar el certificado en el servidor

Solo hace falta ejecutar el comando visto anteriormente, y ya está

sudo certbot certonly --standalone -d myweb.es

Si no funciona pues dice 
Problem binding to port 443: Could not bind to IPv4 or IPv6
Entonces hay que parar el servidor (./shutdown.sh) y ejecutar el comando anterio cambiando
 --standalone por --apache

sudo certbot certonly --apache -d myweb.es

PERO DA PROBLEMAS SI SE ACCEDE DESDE FUERA DE LA ORGANIZACION.


Lo mejor en estos casos es volver a instalar el cerbot, (paso 2)
Solo hay que reaarancar el Tomcat, para ello vamos al directorio "bin" del tomcat y ejecutamos

./shutdown.sh
./startup.sh

6. Configurar https en el puerto 8443 u otro con conversiones (NO RECOMENDADO)

El problema es que queremos utilizar el formato JKS, por tanto tenemos que realizar 2 conversiones

6.1. Convertir "keypair" + certificado a java keystore

1. Crear un PKCS12 que contenga la cadena de certrificados completa y la clave privada con openssl

openssl pkcs12 -export -out /tmp/webprop.es_fullchain_and_key.p12 \ -in /etc/letsencrypt/live/webprop.es/fullchain.pem \ -inkey /etc/letsencrypt/live/webprop.es/privkey.pem \ -name tomcat

2. Convertir el PVCS12 a JKS con keytool de java

keytool -importkeystore \ -deststorepass mi_password -destkeypass mi_password -destkeystore /tmp/webprop.es.jks \ -srckeystore /tmp/webprop.es_fullchain_and_key.p12 -srcstoretype PKCS12 -srcstorepass mi_password \ -alias tomcat


Donde hay que cambiar mi_password por la contraseña que querais

Si no utilizais LetsEncript, tenéis que realizar estos pasos:
=============================================================
1. Con el paso del punto anterior ya tienes la clave privada (fichero .key) y el certificado (fichero .cert) de la entidad certificadora. Se deberá generrar el fichero pfx, con este comando

openssl pkcs12-export -out domain.name.pfx -inkey domain.name.key -in domain.name.crt

2. Convertir el fichero pfx a JKS

keytool-importkeystore -srckeystore webprop.pfx -srcstoretype pkcs12 \ -destkeystore webprop.jks -deststoretype JKS

=============================================================

6.2. Configurar Tomcat con el java keystore


Aseguratrse que esta sección de conf/server.xml está comentada 

<!--- <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> -->

Y configurar el conector para que use el "shared thread pool" 

<Connector
executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

Ahora definir el conector SSL HTTP/1.1 En el puerto 8443

<Connector port="8443"
<!-- protocol="org.apache.coyote.http11.Http11Protocol"-->
protocol="HTTP/1.1"
maxThreads="150" cd ..
SSLEnabled="true"
scheme="https"
secure="true" keystoreFile="/tmp/webprop.es.jks" keystorePass="mi_password" clientAuth="false"
sslProtocol="TLS" />

NOTA: He tenido que comentar la línea:
 "<!-- protocol="org.apache.coyote.http11.Http11Protocol"-->
y cambiarla por la línea de rojo (protocol="HTTP/1.1") pues no me funcionaba.

Ahora hay que rearrancar el Tomcat y debería funcionar. Aunque a mi me ha tardado mucho en arrancar!!


6.3. Crear un shell scrypt para renovar el certificado.

Para ello debemos tener los siguientes prerequisitos:
  • Tener privilegio de superusuario
  • Tener un dominio registrado ("webprop.es" en mi caso)
  • Versión de Java reciente instalada
  • Versión reciente de Tomcat instalada
  • Tener abiertos los puertos 80 y 8443 en el firewall
  • Tener instalado Openssl
  • Tener configurado Tomcat para el puerto 8443 tal como hemos visto en el punto anterior
Veamos el bash

#!/bin/bash

#--Explicació
#---------------------------------------------------------------------------
# S'ha d'executar des de la màquina que s'ha d'instal·lar el certificat 
# Ha d'estar instal·lat el cerbot a la màquina:
#    sudo add-apt-repository ppa:certbot/certbot
#    sudo apt install python-certbot-apache

# La carpeta /etc/letsencrypt/live/csv.tavernesvalldigna.es/ ha de tindre 
#   permissos d'escritura per a eviar cridar-la amb "sudo" 
#   per tant cal executar abans esta orden:    
#     sudo chmod -R ugo+rwx /etc/letsencrypt
#     sudo chmod -R ugo+rwx /var/log
#     sudo chmod -R ugo+rwx /var/lib/letsencrypt

# 1. Indicar el domini
# 2. Indicarla carpeta on guarda el certbot els certificats
# 3. Indicar la carpeta on guardar els certificats tipus jks
#    que és la ruta on Tomcat els busca
# 4. Indicar la paraula de pas del certificat
#---------------------------------------------------------------------------

#--Parameters
myuser='ximo'
domain='webprop.es'
download_certbot='/etc/letsencrypt/live/'${domain}
keystore_folder='/home/'${myuser}'/MyKeystores'
psswrd='mi_password'

work_folder=${keystore_folder}'/letsencrypt/'$(date +%Y.%m.%d)
ks_prefix=${work_folder}'/'${domain}
JAVA_HOME='/home/'${myuser}'/MyPrograms/jdk-15.0.2+7.OpenJ7'

echo '--------------------------------------------------------------'
echo 'Usuari:           ' $myuser
echo 'Domain:           ' $domain
echo 'Descarga Certbot: ' $download_certbot
echo 'Keystore folder:  ' $keystore_folder
echo 'Paraula de pas :  ' $psswrd
echo 'Work folder :     ' $work_folder
echo 'Keystore prefix : ' $ks_prefix
echo 'JAVA_HOME :       ' $JAVA_HOME
echo '--------------------------------------------------------------'

certbot certificates

echo '--------------------------------------------------------------'
echo Por favor, verificar que los datos son correctos
read continuar

# No se si vol "csv.tavernesvalldigna.es" o si "vol sols tavernesvalldigna.es"
# Descarreguem  el certificat a /etc/letsencrypt/live/csv.tavernesvalldigna.es/ 
certbot certonly --standalone -d ${domain}

echo Creem la carpeta ${work_folder}
mkdir -p ${work_folder}
 
echo Convertir el p12 i el pem a PKCS12 
openssl pkcs12 -export -out ${ks_prefix}'_fullchain_and_key.p12' -in ${download_certbot}'/fullchain.pem' -inkey ${download_certbot}'/privkey.pem' -password pass:${psswrd} -name 'tomcat'


echo Convertir el PKCS12 a JKS 
$(${JAVA_HOME}'/bin/keytool' -importkeystore -deststorepass ${psswrd} -destkeypass ${psswrd} -destkeystore ${ks_prefix}'.jks' -srckeystore ${ks_prefix}'_fullchain_and_key.p12'  -srcstoretype PKCS12 -srcstorepass ${psswrd} -alias 'tomcat')

echo Copiar a la carpeta MyKeystore 
cp ${ks_prefix}'.jks' ${keystore_folder}