Files
los-vecinos-de-cisco/cisco_mac_http_service.py
T
Xavier Rodriguez d2ae32a47a Remove legacy configuration and scripts for Cisco MAC Monitor
- Deleted old configuration file (config.yaml) containing device and monitoring settings.
- Removed outdated Docker Compose file (docker-compose.yml) for container setup.
- Eliminated entrypoint script (entrypoint.sh) that handled container initialization and command processing.
- Cleared example output JSON file (example_output.json) used for testing.
- Removed test suite script (test_suite.sh) that validated the project structure and functionality.
- Deleted Zabbix integration script (zabbix_integration.sh) for external script execution.
- Removed Zabbix template examples (zabbix_template_examples.md) for monitoring setup.
- Updated permissions for run.sh and test_new_format.py scripts.
2025-12-09 12:49:14 +01:00

440 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Cisco MAC Monitor HTTPS Service
===============================
Servicio HTTPS que expone la funcionalidad de monitorización de MACs vía REST API.
Mantiene todos los logs informativos pero devuelve solo JSON limpio en las respuestas.
Usa POST para credenciales seguras y soporte SSL/TLS.
Endpoints:
POST /mac-monitor - Body JSON con credenciales
GET /health - Health check
GET /metrics - Métricas del servicio
Autor: Sistema de Monitorización de Red
Fecha: Noviembre 2024
"""
import json
import logging
import sys
import ssl
import os
from datetime import datetime
from typing import Dict, Any
from urllib.parse import parse_qs, urlparse
from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
import signal
import time
# Importar las clases existentes del cisco_mac_monitor
from cisco_mac_monitor import CiscoSSHConnector, CiscoMacTableParser, ZabbixLLDFormatter
class CiscoMacHTTPHandler(BaseHTTPRequestHandler):
"""Handler HTTP para el servicio de monitorización MAC"""
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger(__name__)
super().__init__(*args, **kwargs)
def do_GET(self):
"""Maneja peticiones GET - Solo endpoints públicos"""
try:
if self.path == '/health' or self.path == '/':
self._handle_health()
elif self.path == '/metrics':
self._handle_metrics()
elif self.path.startswith('/mac-monitor'):
self._send_error(405, "Método no permitido. Use POST con JSON body para /mac-monitor")
else:
self._send_error(404, "Endpoint no encontrado")
except Exception as e:
self.logger.error(f"Error procesando petición GET: {str(e)}")
self._send_error(500, f"Error interno: {str(e)}")
def do_POST(self):
"""Maneja peticiones POST - Endpoint seguro de monitorización"""
try:
if self.path == '/mac-monitor':
self._handle_mac_monitor_post()
else:
self._send_error(404, "Endpoint no encontrado")
except Exception as e:
self.logger.error(f"Error procesando petición POST: {str(e)}")
self._send_error(500, f"Error interno: {str(e)}")
def _handle_mac_monitor_post(self):
"""Maneja el endpoint POST de monitorización MAC con JSON body"""
try:
# Leer Content-Length
content_length = int(self.headers.get('Content-Length', 0))
if content_length == 0:
self._send_error(400, "Body JSON requerido")
return
# Leer body JSON
post_data = self.rfile.read(content_length)
try:
json_data = json.loads(post_data.decode('utf-8'))
except json.JSONDecodeError as e:
self._send_error(400, f"JSON inválido: {str(e)}")
return
# Validar parámetros requeridos
required_fields = ['host', 'username', 'password']
missing_fields = []
for field in required_fields:
if field not in json_data or not json_data[field]:
missing_fields.append(field)
if missing_fields:
self._send_error(400, f"Campos faltantes en JSON: {', '.join(missing_fields)}")
return
# Extraer parámetros
host = json_data['host']
username = json_data['username']
password = json_data['password']
hostname = json_data.get('hostname', host)
port = int(json_data.get('port', 22))
timeout = int(json_data.get('timeout', 30))
self.logger.info(f"Iniciando monitorización para {host}")
# Ejecutar monitorización
result = self._execute_mac_monitoring(host, username, password, hostname, port, timeout)
if result:
self._send_json_response(result)
self.logger.info(f"Monitorización completada para {host}")
else:
self._send_error(500, "Error obteniendo datos del dispositivo")
except ValueError as e:
self._send_error(400, f"Parámetro inválido: {str(e)}")
except Exception as e:
self.logger.error(f"Error en monitorización POST: {str(e)}")
self._send_error(500, f"Error de monitorización: {str(e)}")
def _execute_mac_monitoring(self, host: str, username: str, password: str,
hostname: str, port: int, timeout: int) -> Dict[str, Any]:
"""Ejecuta el proceso de monitorización MAC"""
# Inicializar componentes
ssh_connector = CiscoSSHConnector(
host=host,
username=username,
password=password,
port=port,
timeout=timeout
)
mac_parser = CiscoMacTableParser()
zabbix_formatter = ZabbixLLDFormatter(hostname)
try:
# Conectar al dispositivo
if not ssh_connector.connect():
self.logger.error(f"No se pudo establecer conexión SSH con {host}")
return None
# Obtener tabla MAC
mac_entries = mac_parser.get_mac_table(ssh_connector)
if not mac_entries:
self.logger.warning(f"No se obtuvieron entradas de tabla MAC de {host}")
# Obtener la lista de interfaces detectadas
all_interfaces = getattr(mac_parser, 'detected_interfaces', [])
# Formatear para Zabbix incluyendo todas las interfaces
zabbix_data = zabbix_formatter.format_for_zabbix(mac_entries, all_interfaces)
return zabbix_data
finally:
ssh_connector.disconnect()
def _handle_health(self):
"""Endpoint de health check"""
health_data = {
"status": "healthy",
"service": "cisco-mac-monitor-https",
"timestamp": datetime.now().isoformat(),
"version": "2.1-https",
"ssl_enabled": hasattr(self.server, 'socket') and isinstance(self.server.socket, ssl.SSLSocket)
}
self._send_json_response(health_data)
def _handle_metrics(self):
"""Endpoint básico de métricas"""
metrics_data = {
"service": "cisco-mac-monitor-https",
"uptime_seconds": time.time() - start_time,
"requests_total": getattr(self.server, 'request_count', 0),
"timestamp": datetime.now().isoformat(),
"ssl_enabled": hasattr(self.server, 'socket') and isinstance(self.server.socket, ssl.SSLSocket)
}
self._send_json_response(metrics_data)
def _send_json_response(self, data: Dict[str, Any]):
"""Envía respuesta JSON"""
json_data = json.dumps(data, indent=2, ensure_ascii=False)
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Content-Length', len(json_data.encode('utf-8')))
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(json_data.encode('utf-8'))
def _send_error(self, code: int, message: str):
"""Envía respuesta de error"""
error_data = {
"error": message,
"code": code,
"timestamp": datetime.now().isoformat()
}
json_data = json.dumps(error_data, indent=2, ensure_ascii=False)
self.send_response(code)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Content-Length', len(json_data.encode('utf-8')))
self.end_headers()
self.wfile.write(json_data.encode('utf-8'))
self.logger.warning(f"Error {code}: {message}")
def log_message(self, format, *args):
"""Sobrescribir para evitar logs HTTP automáticos"""
# Los logs HTTP automáticos se manejan mediante nuestro logger
pass
class CiscoMacHTTPSServer:
"""Servidor HTTPS para el servicio de monitorización MAC"""
def __init__(self, host='0.0.0.0', port=8443, ssl_cert=None, ssl_key=None):
self.host = host
self.port = port
self.ssl_cert = ssl_cert
self.ssl_key = ssl_key
self.httpd = None
self.server_thread = None
self.logger = logging.getLogger(__name__)
self.running = False
def _generate_self_signed_cert(self):
"""Genera certificado autofirmado si no se proporciona"""
cert_file = '/tmp/cisco_mac_server.crt'
key_file = '/tmp/cisco_mac_server.key'
if os.path.exists(cert_file) and os.path.exists(key_file):
self.logger.info("Usando certificado autofirmado existente")
return cert_file, key_file
self.logger.info("Generando certificado autofirmado...")
try:
import subprocess
# Generar key privada
result = subprocess.run([
'openssl', 'genrsa', '-out', key_file, '2048'
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
self.logger.error(f"Error generando key: {result.stderr.decode()}")
return None, None
# Generar certificado autofirmado
result = subprocess.run([
'openssl', 'req', '-new', '-x509', '-key', key_file,
'-out', cert_file, '-days', '365', '-subj',
'/C=ES/ST=Madrid/L=Madrid/O=CiscoMonitor/CN=cisco-mac-monitor'
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
self.logger.error(f"Error generando certificado: {result.stderr.decode()}")
return None, None
# Cambiar permisos
os.chmod(key_file, 0o600)
os.chmod(cert_file, 0o644)
self.logger.info(f"Certificado autofirmado generado: {cert_file}")
return cert_file, key_file
except subprocess.CalledProcessError as e:
self.logger.error(f"Error generando certificado: {e}")
return None, None
except Exception as e:
self.logger.error(f"Error en subprocess: {e}")
return None, None
except FileNotFoundError:
self.logger.warning("OpenSSL no disponible, funcionando en modo HTTP")
return None, None
def start(self):
"""Inicia el servidor HTTPS"""
try:
self.httpd = HTTPServer((self.host, self.port), CiscoMacHTTPHandler)
self.httpd.request_count = 0
# Configurar SSL si está disponible
ssl_enabled = False
if self.ssl_cert and self.ssl_key:
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(self.ssl_cert, self.ssl_key)
self.httpd.socket = ssl_context.wrap_socket(self.httpd.socket, server_side=True)
ssl_enabled = True
self.logger.info(f"SSL habilitado con certificados personalizados")
else:
# Intentar generar certificado autofirmado
cert_file, key_file = self._generate_self_signed_cert()
if cert_file and key_file:
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(cert_file, key_file)
self.httpd.socket = ssl_context.wrap_socket(self.httpd.socket, server_side=True)
ssl_enabled = True
self.logger.info(f"SSL habilitado con certificado autofirmado")
protocol = "https" if ssl_enabled else "http"
self.logger.info(f"Iniciando servidor {protocol.upper()} en {self.host}:{self.port}")
# Configurar shutdown graceful
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGINT, self._signal_handler)
self.running = True
self.logger.info(f"Servidor disponible en {protocol}://{self.host}:{self.port}")
self.logger.info("Endpoints disponibles:")
self.logger.info(f" POST /mac-monitor - Body JSON con credenciales")
self.logger.info(f" GET /health")
self.logger.info(f" GET /metrics")
if ssl_enabled:
self.logger.info("⚠️ Usando certificado autofirmado - ignorar warnings SSL")
# Iniciar servidor
self.httpd.serve_forever()
except Exception as e:
self.logger.error(f"Error iniciando servidor: {str(e)}")
raise
def stop(self):
"""Detiene el servidor HTTP"""
if self.httpd and self.running:
self.logger.info("Deteniendo servidor HTTP...")
self.running = False
self.httpd.shutdown()
self.httpd.server_close()
self.logger.info("Servidor HTTP detenido")
def _signal_handler(self, signum, frame):
"""Maneja señales de sistema para shutdown graceful"""
self.logger.info(f"Recibida señal {signum}, deteniendo servidor...")
self.stop()
def setup_logging(debug: bool = False) -> logging.Logger:
"""Configura el sistema de logging"""
level = logging.DEBUG if debug else logging.INFO
# Formato de logging
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Handler para stderr
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(formatter)
# Configurar logger principal
logger = logging.getLogger()
logger.setLevel(level)
logger.addHandler(handler)
# Reducir verbosidad de paramiko si no está en debug
if not debug:
logging.getLogger('paramiko').setLevel(logging.WARNING)
return logging.getLogger(__name__)
def main():
"""Función principal"""
import argparse
parser = argparse.ArgumentParser(
description='Cisco MAC Monitor HTTPS Service',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Ejemplos de uso:
%(prog)s --host 0.0.0.0 --port 8443
%(prog)s --host 127.0.0.1 --port 8443 --debug
%(prog)s --ssl-cert /path/to/cert.pem --ssl-key /path/to/key.pem
Endpoints:
POST /mac-monitor - Body JSON: {"host": "192.168.1.10", "username": "admin", "password": "cisco123"}
GET /health
GET /metrics
Ejemplo de petición:
curl -k -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d '{"host":"192.168.1.10","username":"admin","password":"cisco123"}'
"""
)
parser.add_argument('--host', default='0.0.0.0',
help='IP de escucha del servidor (default: 0.0.0.0)')
parser.add_argument('--port', '-p', type=int, default=8443,
help='Puerto de escucha del servidor (default: 8443)')
parser.add_argument('--ssl-cert',
help='Archivo de certificado SSL (opcional)')
parser.add_argument('--ssl-key',
help='Archivo de clave privada SSL (opcional)')
parser.add_argument('--debug', '-d', action='store_true',
help='Habilitar modo debug')
args = parser.parse_args()
# Configurar logging
logger = setup_logging(args.debug)
# Variable global para métricas
global start_time
start_time = time.time()
# Crear y iniciar servidor
server = CiscoMacHTTPSServer(
host=args.host,
port=args.port,
ssl_cert=args.ssl_cert,
ssl_key=args.ssl_key
)
try:
server.start()
except KeyboardInterrupt:
logger.info("Interrupción por teclado recibida")
except Exception as e:
logger.error(f"Error fatal: {str(e)}")
sys.exit(1)
finally:
server.stop()
if __name__ == '__main__':
main()