diff --git a/README.md b/README.md index e69de29..6e4908b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,90 @@ +## Descripción + +Este proyecto realiza de manera automatizada, la eliminación de trabajos (jobs) dentro de MISP que hayan quedado "colgados" (stuck) durante horas. + +### Características: + +- Funciona de forma local en servidor MISP (requerido) +- Soporta todos los tipos de Jobs (pull, push, publish, etc) +- Configuración de umbral de tiempo. Se puede definir hasta cuantas horas hacia atras desde la ejecución realize revision de Jobs. Por defecto (24h) + +## Componentes + +- Libreria PyMISP (pymisp): https://pypi.org/project/pymisp/ +- Libreria SQLAlchemy + +## Descarga + +1. Descargar ZIP del repositorio (https://git.csirt.gob.cl/public/misp-jobfixer/archive/main.zip) + +2. Al descomprimir el archivo, mover carpeta "misp-jobfixer" dentro de carpeta "home" de usuario u otra ubicación definida. + +## Configuración inicial + +1. En el archivo config.py se debe definir los parametros según caso: + +``` python + +DB_DATA = { + "user":"root", + "pass":"", + "host":"localhost", + "port":3306, + "dbname":"misp" +} + +CHECK_JOBS = { + "hours_limit": 24 +} + +``` +En config.py podemos: + +- DB_DATA: Se definen valores de conexión a BD de MISP. Por defecto solo se necesita agregar pass de DB (En archivo de log de instalación de MISP se encuentra pass de DB) +- CHECK_JOBS: En "hours_limit" se define el umbral máximo de tiempo que tiene la app para buscar jobs (Por defecto: 24 hrs) + + +## Instalación en entorno virtual + +Una vez configurado "config.py" procedemos con la instalación + +1. Se crea entorno virtual de Python +``` shell +cd ./misp-jobfixer +python3 -m venv venv +``` +2. Se activa entorno virtual: +``` shell +source venv/bin/activate +``` +3. Instalar librerias de Python: +``` shell +pip install -r requirements.txt +``` + +## Agengar + +Preparamos el archivo "run.sh" ajustando las rutas en "source" y "cd" para la ejecución correcta del archivo: + +```shell +#!/bin/bash + +# Activate +source /home/user/misp-jobfixer/venv/bin/activate + +# Enter folder +cd /home/user/misp-jobfixer/ + +# Llamar al script Python +python main.py + +# Deactivate +deactivate +``` + +Debemos crear en crontab una entrada con la ejecución programada, por ejemplo para que realice la revisión cada dia a las 00:00 (recomendado) : + +``` shell +@daily bash ./misp-jobfixer/run.sh +``` + diff --git a/config.py b/config.py new file mode 100644 index 0000000..cc25ce7 --- /dev/null +++ b/config.py @@ -0,0 +1,11 @@ +DB_DATA = { + "user":"root", + "pass":"", + "host":"localhost", + "port":3306, + "dbname":"misp" +} + +CHECK_JOBS = { + "hours_limit": 24 +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..30be112 --- /dev/null +++ b/main.py @@ -0,0 +1,95 @@ +from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, select +from sqlalchemy.orm import sessionmaker, declarative_base +from sqlalchemy.exc import SQLAlchemyError +from datetime import datetime, timedelta +import config +import os +import logging +from logging.handlers import RotatingFileHandler + + +DIR_ACTUAL = os.getcwd() +DIR_LOGS = os.path.join(DIR_ACTUAL,'logs') + +os.makedirs(DIR_LOGS, exist_ok=True) + +rotating_handler = RotatingFileHandler(os.path.join(DIR_LOGS,"misp_limpiajobs_"+datetime.now().strftime("%Y%m%d")+".log"), maxBytes=262144000, backupCount=10) +logging.basicConfig(level=logging.INFO, handlers=[rotating_handler], + format='%(asctime)s - %(levelname)s - %(message)s') + +# Configuración de la conexión para MariaDB con pymysql +DATABASE_URL = "mariadb+pymysql://"+config.DB_DATA['user']+":"+config.DB_DATA['pass']+"@"+config.DB_DATA['host']+":"+str(config.DB_DATA['port'])+"/"+config.DB_DATA['dbname'] + +# Inicializa SQLAlchemy +Base = declarative_base() +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) +session = Session() + +# Modelo de la tabla jobs +class Job(Base): + __tablename__ = 'jobs' + + id = Column(Integer, primary_key=True, autoincrement=True) + worker = Column(String(32), nullable=False) + job_type = Column(String(32), nullable=False) + job_input = Column(Text, nullable=False) + status = Column(Integer, nullable=False, default=0) + retries = Column(Integer, nullable=False, default=0) + message = Column(Text, nullable=False) + progress = Column(Integer, nullable=False, default=0) + org_id = Column(Integer, nullable=False, default=0) + process_id = Column(String(36), nullable=True) + date_created = Column(DateTime, nullable=False) + date_modified = Column(DateTime, nullable=False) + +# Función para detener jobs no completados después de 24 horas +def kill_stale_jobs(): + """ + Identifica y detiene jobs que no han sido completados + y llevan más de 24 horas sin cambios. + """ + try: + time_limit = datetime.now() - timedelta(hours=config.CHECK_JOBS['hours_limit']) + + # Consulta usando la sintaxis de SQLAlchemy 2.x + stmt = select(Job).where( + Job.status == 0, # Status 0: Sigue en progreso + Job.progress < 100, # Menor a 100, sigue activo + Job.date_modified < time_limit + ) + + stale_jobs = session.execute(stmt).scalars().all() + + if not stale_jobs: + logging.info("No hay jobs pendientes que cumplan con las condiciones.") + return + + processed_count = 0 + failed_count = 0 + + for job in stale_jobs: + try: + job.status = 3 # Killed + job.progress = 100 + job.message = "Killed job manually." + session.commit() + processed_count += 1 + logging.info(f"Job {job.id} detenido automáticamente.") + except SQLAlchemyError as sql_error: + session.rollback() + failed_count += 1 + logging.error(f"Error al detener el Job {job.id}: {str(sql_error)}") + + logging.info(f"Resumen: {processed_count} jobs detenidos exitosamente, {failed_count} errores.") + + except Exception as e: + logging.error(f"Error al procesar los jobs: {str(e)}") + + finally: + session.close() + logging.info("Sesión cerrada correctamente.") +# Ejecución del script +if __name__ == "__main__": + logging.info("CuraJobs v1.0 comenzando") + kill_stale_jobs() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..22de2ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pymysql +SQLAlchemy \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..b1bac35 --- /dev/null +++ b/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Activate +source /home/user/misp-jobfixer/venv/bin/activate + +# Enter folder +cd /home/user/misp-jobfixer|/ + +# Llamar al script Python +python main.py + +# Deactivate +deactivate + + + + + +