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.
This commit is contained in:
Xavier Rodriguez
2025-12-09 12:49:14 +01:00
parent 7fc616b5c3
commit d2ae32a47a
36 changed files with 0 additions and 5307 deletions
Regular → Executable
View File
Regular → Executable
View File
-194
View File
@@ -1,194 +0,0 @@
# Cisco MAC Monitor - Fixes de Compatibilidad
## 📋 Resumen
Este documento describe las correcciones de compatibilidad implementadas en el proyecto **Cisco MAC Monitor** para asegurar la conectividad SSH con dispositivos Cisco antiguos y modernos.
---
## 🔧 Fix SSH para Dispositivos Antiguos
### Problema Identificado
Los dispositivos Cisco con versiones IOS antiguas (especialmente 12.x y algunas versiones 15.x) requieren algoritmos criptográficos legacy que han sido deprecados en versiones modernas de SSH por razones de seguridad.
### Síntomas del Problema
- Fallos de conexión SSH con errores como:
```
paramiko.ssh_exception.SSHException: Incompatible ssh peer (no acceptable kex algorithm)
paramiko.ssh_exception.SSHException: Incompatible ssh peer (no acceptable host key types)
```
- Timeouts durante el handshake SSH
- Imposibilidad de conectar desde OpenSSH moderno sin configuración específica
### Algoritmos Requeridos
Los dispositivos Cisco antiguos requieren:
1. **Key Exchange Algorithms (KEX)**:
- `diffie-hellman-group1-sha1` ⚠️ *Legacy, pero necesario*
2. **Host Key Algorithms**:
- `ssh-rsa` ⚠️ *Deprecated en SSH moderno, pero esencial*
### Solución Implementada
#### Ubicación del Fix
- **Archivo**: `cisco_mac_monitor.py`
- **Clase**: `CiscoSSHConnector`
- **Método**: `connect()`
#### Cambios Realizados
```python
def connect(self) -> bool:
"""Establece conexión SSH con el dispositivo"""
try:
# Configuración especial para dispositivos Cisco antiguos
# Estos dispositivos requieren algoritmos legacy para conectividad
# Crear transport manualmente para configurar algoritmos
transport = paramiko.Transport((self.host, self.port))
transport.set_keepalive(30)
# Obtener opciones de seguridad y agregar algoritmos antiguos
security_opts = transport.get_security_options()
# Añadir diffie-hellman-group1-sha1 para dispositivos antiguos
if 'diffie-hellman-group1-sha1' not in security_opts.kex:
security_opts.kex = ['diffie-hellman-group1-sha1'] + list(security_opts.kex)
# Asegurar que ssh-rsa esté disponible
if 'ssh-rsa' not in security_opts.key_types:
security_opts.key_types = ['ssh-rsa'] + list(security_opts.key_types)
# Resto de la implementación...
```
#### Características del Fix
✅ **Backward Compatible**: Mantiene soporte para algoritmos modernos
✅ **Forward Compatible**: No rompe conectividad con equipos nuevos
✅ **Priorización Inteligente**: Coloca algoritmos legacy al inicio de la lista
✅ **Logging Mejorado**: Indica cuándo se usan algoritmos de compatibilidad
✅ **Manejo de Errores**: Fallos específicos por problemas de algoritmos
---
## 🛠️ Detalles Técnicos
### Equivalencia OpenSSH
El fix implementado es equivalente a usar estas opciones en OpenSSH:
```bash
ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 \
-o HostKeyAlgorithms=+ssh-rsa \
admin@192.168.1.10
```
### Modelos Cisco Afectados
Los siguientes modelos suelen requerir estos algoritmos:
- **Catalyst 2960** (IOS 12.2)
- **Catalyst 3560** (IOS 12.2)
- **Catalyst 2950** (IOS 12.1)
- **Catalyst 3550** (IOS 12.1)
- **ISR 1800/2800/3800** Series (IOS 12.4)
- **ASA 5500** Series (versiones antiguas)
### ⚠️ Consideraciones de Seguridad
1. **Uso Limitado**: Solo usar estos algoritmos en redes internas controladas
2. **Deprecación**: `diffie-hellman-group1-sha1` tiene vulnerabilidades conocidas
3. **Mitigación**: Mantener dispositivos en VLANs de management seguras
4. **Upgrade Path**: Migrar a hardware moderno cuando sea posible
---
## 🧪 Testing y Validación
### Test de Compatibilidad
```bash
# Test con dispositivo moderno (debería usar algoritmos seguros)
python3 cisco_mac_monitor.py --host 192.168.1.100 --username admin --password cisco123 --debug
# Test con dispositivo antiguo (debería usar algoritmos legacy)
python3 cisco_mac_monitor.py --host 192.168.1.10 --username admin --password cisco123 --debug
```
### Verificar Logs
Los logs deben mostrar:
```
2024-11-13 10:30:00 - __main__ - INFO - Conectando a 192.168.1.10 con algoritmos compatibles
2024-11-13 10:30:01 - __main__ - INFO - Conexión exitosa a 192.168.1.10
```
### Docker Testing
```bash
# Build con nuevos cambios
docker build -t cisco-mac-monitor:compatibility .
# Test con dispositivo antiguo
docker run --rm --network host cisco-mac-monitor:compatibility \
--host 192.168.1.10 --username admin --password cisco123 --debug
```
---
## 📝 Notas de Implementación
### Por Qué Este Enfoque
1. **Paramiko Transport**: Permite control granular sobre algoritmos SSH
2. **Lista Prepend**: Agrega algoritmos legacy sin remover los modernos
3. **Detección Automática**: No requiere configuración por dispositivo
4. **Zero Breaking Changes**: Backward compatible con código existente
### Alternativas Consideradas
1. ❌ **Configuración por dispositivo**: Muy complejo de mantener
2. ❌ **Fork de Paramiko**: Innecesariamente complejo
3. ❌ **SSH subprocess**: Perdería funcionalidad Python
4. ✅ **Transport manual**: Balance perfecto de control y simplicidad
---
## 🔮 Roadmap Futuro
### Mejoras Planificadas
- [ ] Detección automática de versión IOS
- [ ] Configuración de algoritmos por modelo de dispositivo
- [ ] Métricas de algoritmos SSH utilizados
- [ ] Alertas cuando se usan algoritmos legacy
### Migration Path
A medida que los dispositivos se actualicen:
1. **Monitoring**: Trackear qué dispositivos usan algoritmos legacy
2. **Reporting**: Dashboard de compatibilidad SSH
3. **Gradual**: Migrar dispositivos a firmware moderno
4. **Eventually**: Remover soporte legacy cuando sea seguro
---
## 📚 Referencias
- [RFC 4253 - SSH Transport Layer Protocol](https://tools.ietf.org/html/rfc4253)
- [Paramiko Documentation](https://docs.paramiko.org/)
- [Cisco IOS SSH Configuration Guide](https://cisco.com/c/en/us/support/docs/security-vpn/secure-shell-ssh/4145-ssh.html)
- [OpenSSH Legacy Algorithm Support](https://www.openssh.com/legacy.html)
---
**Fecha**: 13 de noviembre de 2024
**Autor**: Sistema de Monitorización de Red
**Versión**: 1.0
-72
View File
@@ -1,72 +0,0 @@
# Docker Usage Guide - Los Vecinos de Cisco
## 🐳 Imagen Docker Creada
```bash
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/los-vecinos-de-cisco develop f3b4044bcfa4 2 minutes ago 805MB
```
## 🚀 Cómo Usar la Imagen
### Uso básico
```bash
docker run --rm --network host localhost/los-vecinos-de-cisco:develop \
--host 192.168.1.10 \
--username admin \
--password cisco123
```
### Con hostname personalizado
```bash
docker run --rm --network host localhost/los-vecinos-de-cisco:develop \
--host 192.168.1.10 \
--username admin \
--password cisco123 \
--hostname SW-CORE-001
```
### Con modo debug
```bash
docker run --rm --network host localhost/los-vecinos-de-cisco:develop \
--host 192.168.1.10 \
--username admin \
--password cisco123 \
--debug
```
### Modo silencioso (solo JSON)
```bash
docker run --rm --network host localhost/los-vecinos-de-cisco:develop \
--host 192.168.1.10 \
--username admin \
--password cisco123 \
--quiet
```
## 🔧 Características de Compatibilidad SSH
La imagen incluye soporte automático para:
- ✅ **diffie-hellman-group1-sha1** para dispositivos antiguos
- ✅ **ssh-rsa** host key algorithms
- ✅ Compatible con IOS 12.x y 15.x
- ✅ Mantiene compatibilidad con dispositivos modernos
## 📝 Notas Importantes
1. **Plataforma**: Construida para `linux/amd64`
2. **Red**: Usar `--network host` para acceso directo a dispositivos
3. **Compatibilidad**: Funciona con Python 3.6+ en Rocky Linux 8
4. **Salida**: JSON por stdout, logs por stderr
## 🧪 Testing
Para verificar que funciona:
```bash
# Ver ayuda
docker run --rm localhost/los-vecinos-de-cisco:develop --help
# Test rápido (fallará por falta de dispositivo, pero verifica imagen)
docker run --rm localhost/los-vecinos-de-cisco:develop \
--host test --username test --password test --timeout 1
```
-58
View File
@@ -1,58 +0,0 @@
# Dockerfile para Cisco MAC Monitor con compatibilidad SSH legacy
# ===============================================================
FROM rockylinux:8
# Metadatos
LABEL maintainer="Equipo de Monitorización de Red"
LABEL description="Monitor de direcciones MAC para dispositivo Cisco con soporte SSH legacy"
LABEL version="2.0-ssh-compat"
# Variables de entorno
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8
# Actualizar sistema e instalar dependencias base
RUN dnf update -y && \
dnf install -y \
python3 \
python3-pip \
python3-devel \
openssh-clients \
gcc \
openssl-devel \
libffi-devel \
krb5-devel && \
dnf clean all
# Crear usuario no privilegiado
RUN useradd -m -u 1000 -s /bin/bash monitor
# Instalar dependencias Python con versiones específicas para compatibilidad
RUN pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir \
'paramiko>=2.12.0,<4.0' \
'PyYAML>=5.0,<7.0' \
'cryptography>=3.4.8,<42.0'
# Cambiar al usuario no privilegiado
USER monitor
WORKDIR /app
# Copiar scripts principales
COPY --chown=monitor:monitor cisco_mac_monitor.py /app/
COPY --chown=monitor:monitor test_compatibility.py /app/
# Permisos de ejecución
USER root
RUN chmod +x /app/cisco_mac_monitor.py /app/test_compatibility.py
USER monitor
# Healthcheck mejorado que verifica la configuración SSH
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python3 /app/test_compatibility.py > /dev/null && \
python3 /app/cisco_mac_monitor.py --help > /dev/null || exit 1
# Entry point
ENTRYPOINT ["python3", "/app/cisco_mac_monitor.py"]
-106
View File
@@ -1,106 +0,0 @@
# 📁 ESTRUCTURA DEL PROYECTO ORGANIZADA
## 🎯 **ARCHIVOS ESENCIALES EN RAÍZ**
La raíz del proyecto ahora contiene únicamente los archivos **indispensables** para construir y usar la imagen Docker:
```
/
├── 📄 README.md # Documentación principal del proyecto
├── 🐳 Dockerfile.http # Dockerfile para construcción de imagen HTTPS
├── 📋 requirements.txt # Dependencias Python optimizadas
├── 🐍 cisco_mac_monitor.py # Código principal - Conector SSH y parser
├── 🌐 cisco_mac_http_service.py # Servicio HTTPS con API REST
├── 📁 no/ # Archivos no esenciales movidos aquí
└── 📁 old/ # Versiones antiguas del código
```
## ✅ **ARCHIVOS ESENCIALES PARA DOCKER:**
### 1. **Código Python**
- `cisco_mac_monitor.py` - Funcionalidad core SSH + parsing
- `cisco_mac_http_service.py` - Servidor HTTPS + API REST
### 2. **Configuración Docker**
- `Dockerfile.http` - Para construir la imagen
- `requirements.txt` - Dependencias mínimas
### 3. **Documentación**
- `README.md` - Instrucciones de uso
---
## 📂 **ARCHIVOS MOVIDOS A CARPETA "no/"**
Todos los archivos que **NO** son estrictamente necesarios para construir la imagen Docker han sido organizados en `/no/`:
### 📚 **Documentación y Guías**
- `COMPATIBILITY_FIXES.md` - Documentación de compatibilidad SSH
- `DOCKER_USAGE.md` - Guía de uso de Docker
- `HTTPS_SECURE_GUIDE.md` - Guía de configuración HTTPS
- `HTTP_SERVICE_GUIDE.md` - Documentación del servicio HTTP
- `IMPLEMENTACION_COMPLETADA.md` - Resumen de implementación
- `PROMPT` - Archivo de prompt original
### 🐳 **Configuraciones Docker Alternativas**
- `Dockerfile` - Dockerfile original (CLI)
- `docker-compose.yml` - Composición original
- `docker-compose.http.yml` - Composición para HTTPS
### 🧪 **Scripts de Prueba y Testing**
- `test_all_ports.py` - Prueba de funcionalidad completa
- `test_compatibility.py` - Pruebas de compatibilidad SSH
- `test_https_api.sh` - Tests de API HTTPS
- `test_interfaces_simulation.py` - Simulación de interfaces
- `scripts/test_suite.sh` - Suite de pruebas completa
### 🔧 **Scripts de Integración**
- `zabbix_integration.sh` - Script de integración con Zabbix
---
## 🚀 **CONSTRUCCIÓN DE IMAGEN SIMPLIFICADA**
Con la estructura limpia, construir la imagen es ahora muy simple:
```bash
# En la raíz del proyecto
docker build --platform linux/amd64 -t cisco-mac-monitor:https -f Dockerfile.http .
# Ejecutar
docker run -d --name cisco-mac-https -p 8443:8443 cisco-mac-monitor:https
```
---
## 💡 **BENEFICIOS DE LA REORGANIZACIÓN**
### ✅ **Simplicidad**
- Raíz limpia con solo archivos esenciales
- Fácil identificación de lo necesario vs. documentación
### ✅ **Mantenimiento**
- Separación clara entre código de producción y testing
- Dockerfile más eficiente al no copiar archivos innecesarios
### ✅ **Documentación Preservada**
- Toda la documentación se mantiene accesible en `/no/`
- Historia del proyecto conservada en `/old/`
### ✅ **Construcción Optimizada**
- Menos archivos en contexto de Docker = build más rápido
- Cache de Docker más eficiente
---
## 📋 **COMANDOS DE CONSTRUCCIÓN VERIFICADOS**
✅ **Imagen construida exitosamente** con la nueva estructura:
```
✅ FINISHED (14/14) - cisco-mac-monitor:clean
✅ Todos los archivos esenciales presentes
✅ Requirements.txt utilizado correctamente
✅ Estructura limpia y organizada
```
La reorganización está **completa y verificada** ✨
-308
View File
@@ -1,308 +0,0 @@
# Cisco MAC Monitor - HTTPS Secure Service Guide
## 🔒 Servicio HTTPS Seguro Deployado
¡El servicio HTTPS con POST seguro está funcionando perfectamente! Las credenciales ya NO se envían por URLs.
### 🛡️ Mejoras de Seguridad Implementadas
✅ **POST en lugar de GET**: Credenciales en JSON body, no en URL
✅ **HTTPS/SSL**: Cifrado TLS con certificado autofirmado
✅ **JSON Body**: Datos estructurados y seguros
✅ **Logs limpios**: No aparecen credenciales en logs
---
## 🌐 API Endpoints
### 🔐 Monitorización MAC (Seguro)
```http
POST /mac-monitor
Content-Type: application/json
{
"host": "192.168.1.10",
"username": "admin",
"password": "cisco123",
"hostname": "SW-CORE-001", // Opcional
"port": 22, // Opcional, default: 22
"timeout": 30 // Opcional, default: 30
}
```
### 📊 Health Check
```http
GET /health
```
### 📈 Métricas
```http
GET /metrics
```
---
## 🚀 Uso del Servicio
### Con curl
```bash
# Monitorización básica
curl -k -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d '{"host":"10.150.226.19","username":"itops","password":"Tr4!D0r3s"}'
# Con parámetros completos
curl -k -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d '{
"host": "10.150.226.19",
"username": "itops",
"password": "Tr4!D0r3s",
"hostname": "SW-CORE-BUILDING-A",
"timeout": 45
}'
# Health check
curl -k "https://localhost:8443/health"
```
### Con Python
```python
import requests
import json
# Configuración
url = "https://localhost:8443/mac-monitor"
payload = {
"host": "10.150.226.19",
"username": "itops",
"password": "Tr4!D0r3s",
"hostname": "SW-CORE-TEST"
}
# Petición segura
response = requests.post(
url,
json=payload,
verify=False # Para certificado autofirmado
)
if response.status_code == 200:
data = response.json()
print(f"Puertos encontrados: {data['total_ports']}")
print(f"MACs totales: {data['total_macs']}")
else:
print(f"Error: {response.status_code} - {response.text}")
```
---
## 🐳 Gestión del Servicio HTTPS
### Comandos Docker
```bash
# Iniciar servicio HTTPS (recomendado)
docker run -d -p 8443:8443 --name cisco-mac-https localhost/los-vecinos-de-cisco:https-service
# Ver logs en tiempo real
docker logs -f cisco-mac-https
# Reiniciar servicio
docker restart cisco-mac-https
# Detener servicio
docker stop cisco-mac-https && docker rm cisco-mac-https
# Verificar estado
docker ps | grep cisco-mac-https
```
### Reconstruir tras cambios
```bash
# Reconstruir imagen
docker build --platform linux/amd64 -f Dockerfile.http -t localhost/los-vecinos-de-cisco:https-service .
# Recrear contenedor
docker stop cisco-mac-https && docker rm cisco-mac-https
docker run -d -p 8443:8443 --name cisco-mac-https localhost/los-vecinos-de-cisco:https-service
```
---
## 📋 Integración con Zabbix
### Script de External Check Actualizado
```bash
#!/bin/bash
# /usr/lib/zabbix/externalscripts/cisco_mac_https_discovery.sh
HOST="$1"
USERNAME="$2"
PASSWORD="$3"
HOSTNAME="${4:-$HOST}"
# Crear JSON payload
JSON_PAYLOAD=$(cat <<EOF
{
"host": "${HOST}",
"username": "${USERNAME}",
"password": "${PASSWORD}",
"hostname": "${HOSTNAME}"
}
EOF
)
# Petición HTTPS segura
curl -k -s -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d "${JSON_PAYLOAD}"
```
### Configuración Zabbix
**Discovery Rule:**
```xml
Name: Cisco MAC HTTPS Discovery
Type: External check
Key: cisco_mac_https_discovery.sh[{HOST.IP},{$CISCO_USER},{$CISCO_PASSWORD},{HOST.NAME}]
Update interval: 5m
```
**Macros requeridas:**
```xml
{$CISCO_USER} = admin
{$CISCO_PASSWORD} = cisco123
```
---
## 🔒 Certificados SSL
### Certificado Autofirmado (Por Defecto)
El servicio genera automáticamente un certificado autofirmado:
```
Subject: /C=ES/ST=Madrid/L=Madrid/O=CiscoMonitor/CN=cisco-mac-monitor
Validity: 365 days
Location: /tmp/cisco_mac_server.crt
```
### Certificado Personalizado (Producción)
```bash
# Con certificados propios
docker run -d -p 8443:8443 \
-v /path/to/cert.pem:/app/cert.pem \
-v /path/to/key.pem:/app/key.pem \
--name cisco-mac-https \
localhost/los-vecinos-de-cisco:https-service \
--ssl-cert /app/cert.pem --ssl-key /app/key.pem
```
### Para Desarrollo (HTTP sin SSL)
```bash
# Solo para testing local
docker run -d -p 8080:8080 --name cisco-mac-http \
localhost/los-vecinos-de-cisco:https-service \
--port 8080
```
---
## 🛡️ Ventajas de Seguridad
### ✅ Antes vs Ahora
| Aspecto | HTTP GET (Anterior) | HTTPS POST (Actual) |
|---------|---------------------|---------------------|
| **Credenciales** | En URL visible | En JSON body cifrado |
| **Logs** | Password en logs | Solo hostname en logs |
| **Transporte** | Texto plano | Cifrado TLS/SSL |
| **Cache** | URLs cacheable | POST no cacheable |
| **Historial** | Queda en historial | No queda en historial |
| **Proxies** | Credenciales visibles | Cifrado end-to-end |
### 📊 Logs Seguros
**Logs del servicio ahora:**
```
2025-11-13 08:41:46,921 - cisco_mac_monitor - INFO - Conectando a 10.150.226.19 con algoritmos compatibles
2025-11-13 08:41:46,921 - cisco_mac_monitor - INFO - Conexión exitosa a 10.150.226.19
2025-11-13 08:41:47,627 - __main__ - INFO - Monitorización completada para 10.150.226.19
```
❌ **NO aparecen passwords**
❌ **NO aparecen usernames**
✅ **Solo hostname/IP para auditoría**
---
## 🧪 Testing y Validación
### Test Automatizado
```bash
# Ejecutar suite completa de tests
./test_https_api.sh
```
### Test Manual
```bash
# 1. Health check
curl -k "https://localhost:8443/health"
# 2. Métricas
curl -k "https://localhost:8443/metrics"
# 3. Dispositivo real
curl -k -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d '{"host":"10.150.226.19","username":"itops","password":"Tr4!D0r3s"}'
# 4. Test de error (credenciales incorrectas)
curl -k -X POST "https://localhost:8443/mac-monitor" \
-H "Content-Type: application/json" \
-d '{"host":"10.150.226.19","username":"wrong","password":"wrong"}'
```
### Validación de Seguridad
```bash
# Verificar que GET no funciona para credenciales
curl -k "https://localhost:8443/mac-monitor?host=10.150.226.19&username=test&password=test"
# Debería devolver error 405 "Método no permitido"
# Verificar SSL
openssl s_client -connect localhost:8443 -servername cisco-mac-monitor
```
---
## 🎯 Comparación Final
### 🚀 Beneficios del Upgrade a HTTPS POST
1. **🔒 Seguridad**: Credenciales cifradas, no en logs
2. **📊 Compliance**: Cumple estándares de seguridad
3. **🛡️ Auditoría**: Logs limpios y seguros
4. **🌐 Estándar**: Sigue mejores prácticas REST
5. **🔧 Flexibilidad**: JSON estructurado vs query string
6. **📈 Escalabilidad**: Más parámetros sin límites de URL
### ⚡ Rendimiento Mantenido
- **Tiempo de respuesta**: <1 segundo (igual que antes)
- **Throughput**: Múltiples peticiones concurrentes
- **Recursos**: Mismo footprint de memoria
- **Compatibilidad**: Mantiene algoritmos SSH legacy
---
## 🎉 Estado Final
✅ **HTTPS seguro operativo**
✅ **Certificado autofirmado funcionando**
✅ **POST con JSON body implementado**
✅ **Logs limpios sin credenciales**
✅ **Compatibilidad SSH legacy mantenida**
✅ **Tests completos pasando**
**¡El servicio está 100% listo para producción con máxima seguridad!** 🛡️🚀
-240
View File
@@ -1,240 +0,0 @@
# Cisco MAC Monitor - HTTP Service Guide
## 🌐 Servicio HTTP Deployado
¡El servicio HTTP está funcionando perfectamente! Ya no necesitas pasar parámetros por línea de comandos.
### 📊 Estado Actual
```bash
# Imagen construida:
localhost/los-vecinos-de-cisco:http-service
# Contenedor corriendo:
docker ps | grep cisco-mac-http
# Logs en tiempo real:
docker logs -f cisco-mac-http
```
---
## 🚀 Uso del Servicio HTTP
### Endpoints Disponibles
#### 1. **Monitorización MAC** (Principal)
```http
GET /mac-monitor?host=<IP>&username=<USER>&password=<PASS>[&hostname=<NAME>][&port=<PORT>][&timeout=<SEC>]
```
**Ejemplo:**
```bash
curl "http://localhost:8080/mac-monitor?host=10.150.226.19&username=itops&password=Tr4%21D0r3s"
```
#### 2. **Health Check**
```http
GET /health
```
**Ejemplo:**
```bash
curl "http://localhost:8080/health"
```
**Respuesta:**
```json
{
"status": "healthy",
"service": "cisco-mac-monitor",
"timestamp": "2025-11-13T08:31:20.504819",
"version": "2.0-http"
}
```
#### 3. **Métricas**
```http
GET /metrics
```
**Ejemplo:**
```bash
curl "http://localhost:8080/metrics"
```
---
## 🐳 Gestión del Contenedor
### Iniciar el Servicio
```bash
# Opción 1: Con mapeo de puertos estándar (recomendado)
docker run -d -p 8080:8080 --name cisco-mac-http localhost/los-vecinos-de-cisco:http-service
# Opción 2: Con acceso directo a red (para dispositivos locales)
docker run -d --network host --name cisco-mac-http localhost/los-vecinos-de-cisco:http-service
# Opción 3: Con docker-compose
docker-compose -f docker-compose.http.yml up -d
```
### Comandos de Gestión
```bash
# Ver logs en tiempo real
docker logs -f cisco-mac-http
# Ver estado
docker ps | grep cisco-mac-http
# Reiniciar servicio
docker restart cisco-mac-http
# Detener servicio
docker stop cisco-mac-http && docker rm cisco-mac-http
```
### Reconstruir Imagen
```bash
# Reconstruir después de cambios
docker build --platform linux/amd64 -f Dockerfile.http -t localhost/los-vecinos-de-cisco:http-service .
# Recrear contenedor
docker stop cisco-mac-http && docker rm cisco-mac-http
docker run -d -p 8080:8080 --name cisco-mac-http localhost/los-vecinos-de-cisco:http-service
```
---
## 📝 Logs del Servicio
### Logs Informativos (stderr)
El servicio mantiene **todos** los logs informativos que tenías antes:
```
2025-11-13 08:31:57,395 - cisco_mac_monitor - INFO - Conectando a 10.150.226.19 con algoritmos compatibles
2025-11-13 08:31:58,374 - cisco_mac_monitor - INFO - Conexión exitosa a 10.150.226.19
2025-11-13 08:31:58,820 - cisco_mac_monitor - INFO - Obtenidas 117 entradas de tabla MAC
2025-11-13 08:31:58,821 - cisco_mac_monitor - INFO - Formateados datos para dispositivo 10.150.226.19: 3 puertos, 117 MACs
2025-11-13 08:31:58,821 - cisco_mac_monitor - INFO - Desconectado de 10.150.226.19
2025-11-13 08:31:58,822 - __main__ - INFO - Monitorización completada para 10.150.226.19
```
### Respuesta JSON (stdout/HTTP)
El JSON se devuelve **limpio** en la respuesta HTTP, sin mezclarse con logs.
---
## 🔧 Integración con Zabbix
### Cambio en External Scripts
**Antes (comando directo):**
```bash
docker run --rm --network host localhost/los-vecinos-de-cisco:develop \
--host "$HOST" --username "$USERNAME" --password "$PASSWORD"
```
**Ahora (petición HTTP):**
```bash
curl -s "http://localhost:8080/mac-monitor?host=$HOST&username=$USERNAME&password=$PASSWORD"
```
### Script de Integración Actualizado
```bash
#!/bin/bash
# /usr/lib/zabbix/externalscripts/cisco_mac_http_discovery.sh
HOST="$1"
USERNAME="$2"
PASSWORD="$3"
HOSTNAME="${4:-$HOST}"
# URL encode password si es necesario
PASSWORD_ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PASSWORD'))")
# Petición al servicio HTTP
curl -s "http://localhost:8080/mac-monitor?host=$HOST&username=$USERNAME&password=$PASSWORD_ENCODED&hostname=$HOSTNAME"
```
### Configuración Zabbix
**Discovery Rule:**
```xml
Name: Cisco MAC HTTP Discovery
Type: External check
Key: cisco_mac_http_discovery.sh[{HOST.IP},{$CISCO_USER},{$CISCO_PASSWORD},{HOST.NAME}]
Update interval: 5m
```
---
## 🎯 Ventajas del Servicio HTTP
### ✅ Beneficios
1. **Rendimiento**: Servidor persistente, sin overhead de inicio de contenedor
2. **Escalabilidad**: Múltiples peticiones concurrentes
3. **Simplicidad**: Peticiones HTTP estándar
4. **Logs Separados**: JSON limpio en respuesta, logs informativos en contenedor
5. **Healthchecks**: Monitorización del servicio incluida
6. **RESTful**: Fácil integración desde cualquier sistema
### 📊 Comparación
| Aspecto | Comando Anterior | Servicio HTTP |
|---------|------------------|---------------|
| **Inicio** | ~2-3s por ejecución | <100ms por petición |
| **Concurrencia** | 1 por vez | Múltiples simultáneas |
| **Logs** | Mezclados con JSON | Separados |
| **Integración** | Solo Zabbix | Universal (HTTP) |
| **Debugging** | Difícil | Fácil con curl |
---
## 🧪 Testing Completo
### Test Manual
```bash
# 1. Health check
curl "http://localhost:8080/health"
# 2. Métricas
curl "http://localhost:8080/metrics"
# 3. Test real con dispositivo
curl "http://localhost:8080/mac-monitor?host=10.150.226.19&username=itops&password=Tr4%21D0r3s"
# 4. Test con parámetros adicionales
curl "http://localhost:8080/mac-monitor?host=10.150.226.19&username=itops&password=Tr4%21D0r3s&hostname=SWITCH-CORE&timeout=45"
```
### Test de Rendimiento
```bash
# Test de múltiples peticiones concurrentes
for i in {1..5}; do
curl -s "http://localhost:8080/mac-monitor?host=10.150.226.19&username=itops&password=Tr4%21D0r3s" &
done
wait
```
### Monitorización de Logs
```bash
# Ver logs en tiempo real durante testing
docker logs -f cisco-mac-http
```
---
## 🔮 Próximos Pasos
1. **Producción**: Desplegar en servidor de monitorización
2. **SSL/TLS**: Añadir HTTPS si es necesario
3. **Autenticación**: API keys si se requiere seguridad adicional
4. **Load Balancer**: Para alta disponibilidad
5. **Métricas**: Integrar con Prometheus/Grafana
---
**El servicio HTTP está 100% operativo y listo para producción! 🚀**
-122
View File
@@ -1,122 +0,0 @@
# RESUMEN DE IMPLEMENTACIÓN COMPLETADA
## ✅ FUNCIONALIDADES IMPLEMENTADAS
### 1. **Compatibilidad SSH con dispositivos Cisco legacy**
- ✅ Algoritmos SSH compatibles con IOS 12.x/15.x
- ✅ `diffie-hellman-group1-sha1` y `ssh-rsa` habilitados
- ✅ Timeout de conexión configurable
### 2. **Servicio HTTPS seguro**
- ✅ Migración de HTTP a HTTPS con SSL autofirmado
- ✅ Endpoint POST con JSON body (no más credenciales en URL)
- ✅ Puerto 8443 con certificado SSL automático
- ✅ Endpoints: `/mac-monitor`, `/health`, `/metrics`
### 3. **🎯 NUEVA FUNCIONALIDAD: Mostrar TODAS las interfaces**
- ✅ **Detección de todas las interfaces** mediante `show interfaces status`
- ✅ **Puertos vacíos mostrados con MAC_COUNT=0**
- ✅ **Puertos con MACs mostrados normalmente**
- ✅ **Formato Zabbix LLD compatible** con interfaces sin MACs
### 4. **Gestión de dependencias mejorada**
- ✅ requirements.txt optimizado con solo librerías esenciales:
- paramiko>=2.12.0,<4.0
- cryptography>=3.4.8,<42.0
- PyYAML>=5.0,<7.0
- ✅ Dockerfile.http actualizado para usar requirements.txt
### 5. **Contenedorización Docker**
- ✅ Rocky Linux 8 como base
- ✅ Usuario no privilegiado (monitor:1000)
- ✅ Health checks automáticos
- ✅ Plataforma linux/amd64 compatible
---
## 📊 PRUEBA DE FUNCIONALIDAD EXITOSA
### Resultados de la prueba con datos simulados:
```
=== RESULTADOS DE LA PRUEBA ===
Total de puertos detectados: 7
- Fa0/1: 2 MACs conectadas ✅
- Fa0/2: 2 MACs conectadas ✅
- Fa0/3: 0 MACs (puerto vacío) ⚪
- Fa0/4: 0 MACs (puerto vacío) ⚪
- Fa0/5: 0 MACs (puerto vacío) ⚪
- Gi0/1: 3 MACs conectadas ✅
- Gi0/2: 0 MACs (puerto vacío) ⚪
Puertos con MACs: 3
Puertos vacíos: 4 ← 🎯 OBJETIVO LOGRADO
```
### Salida Zabbix LLD:
```json
{
"data": [
{
"{#DEVICE_HOSTNAME}": "cisco-test-device",
"{#PORT_NAME}": "Fa0/3",
"{#MAC_COUNT}": 0,
"{#VLANS}": "",
"{#MAC_ADDRESSES}": "[]"
}
],
"total_ports": 7,
"ports_with_macs": 3,
"ports_without_macs": 4
}
```
---
## 🔧 MÉTODOS IMPLEMENTADOS
### CiscoMacTableParser - Nuevos métodos:
- `_get_interface_list()` - Ejecuta 'show interfaces status'
- `_parse_interface_list()` - Parsea la salida de interfaces
- `_is_physical_interface()` - Filtra interfaces físicas
- `detected_interfaces` - Lista de interfaces detectadas
### ZabbixLLDFormatter - Mejorado:
- `format_for_zabbix(mac_entries, all_interfaces)` - Acepta lista de interfaces
- Incluye puertos sin MACs con MAC_COUNT=0
- Estadísticas en el JSON: total_ports, ports_with_macs, ports_without_macs
---
## 🚀 USO DEL SERVICIO
### Construcción y ejecución:
```bash
# Construir imagen
docker build --platform linux/amd64 -t cisco-mac-monitor:https -f Dockerfile.http .
# Ejecutar contenedor
docker run -d --name cisco-mac-https -p 8443:8443 cisco-mac-monitor:https
# Realizar petición
curl -k -X POST https://localhost:8443/mac-monitor \
-H "Content-Type: application/json" \
-d '{
"host": "10.150.226.19",
"username": "admin",
"password": "password",
"use_legacy_ssh": true
}'
```
---
## 🎯 RESULTADO FINAL
**OBJETIVO CUMPLIDO**: El sistema ahora muestra **TODAS** las interfaces del switch, incluyendo aquellas sin dispositivos conectados (MAC_COUNT=0), proporcionando visibilidad completa del estado de los puertos de red para monitorización con Zabbix.
**Beneficios**:
- ✅ **Visibilidad completa** de todos los puertos
- ✅ **Compatibilidad total** con dispositivos Cisco legacy
- ✅ **Seguridad mejorada** con HTTPS y POST
- ✅ **Gestión eficiente** de dependencias
- ✅ **Contenedorización robusta** lista para producción
-8
View File
@@ -1,8 +0,0 @@
rol: necesito que adquieras el rol de un experto en dispositivos de red de la marca, cisco, con conocimientos de Python
necesidad: cliente necesita poder monitorizar los dispositivos que hay conectados en cada una de las bocas de sus dispositivos de red de la marca cisco. El cliente nos ha proporcionado un inventario de modelos. Ellos necesitan saber qué direcciones Mac hay conectadas por cada una de las bocas de Red.
misión: necesitamos montar un pequeño script en Python, el cual se encargue de conectarse a un dispositivo de red dado, y obtener la información que necesita el cliente. este dispositivo de red dado será proporcionado a través de la linea de comandos. las credenciales de acceso al dispositivo también serán proporcionadas a través de la linea de comandos.
la salida de esta información ha de estar en formato json. la información extraída será consumida por Zabbix. A través de un LLD de una plantilla de Zabbix que ya se creará más adelante, esta alertará en caso de que haya alguna MAC no autorizada conectada a alguna de las bocas de red o si hay cambios en las MACs conectadas. Necesitamos que el script sea capaz de conectarse al dispositivo de red mediante SSH. El script correrá en un contenedor docker en el que ya estará instalado Python y las librerías necesarias para la conexión SSH.
La imagen base deberá ser ROCKY LINUX 8. La información que se necesita extraer de cada dispositivo ha de ser en json como decía antes, pero ha de estar preparada para que Zabbix pueda consumirla a través de un LLD, por lo que la estructura del json ha de ser la adecuada.
-382
View File
@@ -1,382 +0,0 @@
# Cisco Single MAC Monitor para Zabbix LLD
## 📋 Descripción
**Cisco Single MAC Monitor** es una versión simplificada del monitor de direcciones MAC que se enfoca en **un dispositivo individual por ejecución**. Recibe los parámetros del dispositivo (IP, credenciales) por línea de comandos y genera salida JSON compatible con **Zabbix Low Level Discovery (LLD)**.
### Características principales:
- ✅ **Un dispositivo por ejecución** - Parámetros por línea de comandos
- ✅ **Compatible** con modelos Cisco C2960, C3560, C3560E, C2960X, C3560CX y Catalyst
- ✅ **Conexión SSH** segura
- ✅ **Formato JSON** optimizado para Zabbix LLD
- ✅ **Contenedor Docker** ligero basado en Rocky Linux 8
- ✅ **JSON más manejable** - No hay riesgo de JSON gigante
- ✅ **Integración sencilla** con Zabbix External Scripts
---
## 🚀 Inicio Rápido
### 1. Construir la imagen Docker
```bash
# Usar Dockerfile específico para versión single
docker build -f Dockerfile -t cisco-mac-monitor .
```
### 2. Ejecutar para un dispositivo
```bash
# Ejemplo básico
curl -k -X POST https://localhost:8443/mac-monitor -H "Content-Type: application/json" -d '{"host": "10.150.226.19", "username": "itops", "password": "Tr4!D0r3s", "use_legacy_ssh": true}'
```
### ⚠️ **Manejo de Contraseñas con Caracteres Especiales**
```bash
# ✅ CORRECTO: Sin escape para caracteres como !
curl -k -X POST https://localhost:8443/mac-monitor -H "Content-Type: application/json" -d '{"host": "10.150.226.19", "username": "itops", "password": "Tr4!D0r3s", "use_legacy_ssh": true}'
# ✅ CORRECTO: Para contraseñas con comillas, usar comillas dobles externas
curl -k -X POST https://localhost:8443/mac-monitor -H "Content-Type: application/json" -d "{\"host\": \"10.150.226.19\", \"username\": \"itops\", \"password\": \"Pass\\\"word\", \"use_legacy_ssh\": true}"
# ✅ CORRECTO: Para contraseñas muy complejas, usar archivo
echo '{"host": "10.150.226.19", "username": "itops", "password": "C0mpl3x!P@ss#w0rd$", "use_legacy_ssh": true}' > payload.json
curl -k -X POST https://localhost:8443/mac-monitor -H "Content-Type: application/json" -d @payload.json
# ❌ INCORRECTO: No usar \ para escapar ! en JSON
curl -k -X POST https://localhost:8443/mac-monitor -H "Content-Type: application/json" -d '{"password": "Tr4\!D0r3s"}' # ERROR!
```
### 3. Integración con Zabbix
```bash
# Copiar script de integración
sudo cp zabbix_single_integration.sh /usr/lib/zabbix/externalscripts/
sudo chmod +x /usr/lib/zabbix/externalscripts/zabbix_single_integration.sh
sudo chown zabbix:zabbix /usr/lib/zabbix/externalscripts/zabbix_single_integration.sh
# Probar manualmente
/usr/lib/zabbix/externalscripts/zabbix_single_integration.sh 192.168.1.10 admin cisco123
```
---
## 📁 Archivos de la Versión Simplificada
```
cisco-mac-monitor/
├── cisco_single_mac_monitor.py # Script principal simplificado
├── Dockerfile.single # Imagen Docker optimizada
├── docker-compose.single.yml # Compose simplificado
├── zabbix_single_integration.sh # Script para Zabbix External Scripts
├── example_single_output.json # Ejemplo de salida JSON
└── README_single.md # Esta documentación
```
---
## ⚙️ Uso Detallado
### Parámetros de línea de comandos
```bash
python3 cisco_single_mac_monitor.py [OPTIONS]
Argumentos requeridos:
--host/-H <IP> IP o hostname del dispositivo Cisco
--username/-u <USER> Usuario SSH
--password/-p <PASS> Contraseña SSH
Argumentos opcionales:
--port/-P <PORT> Puerto SSH (default: 22)
--timeout/-t <SEC> Timeout en segundos (default: 30)
--hostname/-n <NAME> Nombre del dispositivo (default: usa --host)
--debug/-d Modo debug
--quiet/-q Modo silencioso (solo JSON)
```
### Ejemplos de uso
```bash
# Básico
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123
# Puerto SSH personalizado
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -P 2222
# Con timeout personalizado
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -t 60
# Modo silencioso (solo JSON por stdout)
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -q
# Con nombre personalizado del dispositivo
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -n SW-CORE-001
```
---
## 🐳 Uso con Docker
### Construir imagen
```bash
# Opción 1: Docker directo
docker build -f Dockerfile.single -t cisco-single-mac-monitor .
# Opción 2: Docker Compose
docker-compose -f docker-compose.single.yml build
```
### Ejecutar contenedor
```bash
# Ejecución básica
docker run --rm --network host cisco-single-mac-monitor \
--host 192.168.1.10 --username admin --password cisco123
# Con volumen para logs (opcional)
docker run --rm --network host -v /tmp:/tmp cisco-single-mac-monitor \
--host 192.168.1.10 --username admin --password cisco123 --debug
# Modo interactivo para debugging
docker run --rm -it --network host cisco-single-mac-monitor --help
```
---
## 📊 Integración con Zabbix
### Formato JSON de salida
```json
{
"data": [
{
"{#DEVICE_HOSTNAME}": "SW-CORE-001",
"{#PORT_NAME}": "Fa0/1",
"{#MAC_COUNT}": 2,
"{#VLANS}": "10,20",
"{#MAC_ADDRESSES}": "[{\"mac\": \"aa:bb:cc:dd:ee:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
}
],
"timestamp": "2024-10-24T14:30:00.123456",
"device_hostname": "SW-CORE-001",
"total_ports": 3,
"total_macs": 6
}
```
### Configuración en Zabbix
**1. Discovery Rule por dispositivo:**
```xml
Name: Cisco MAC Discovery for {HOST.NAME}
Type: External check
Key: cisco_single_mac_discovery.sh[{HOST.IP},{$CISCO_USER},{$CISCO_PASSWORD},{HOST.NAME}]
Update interval: 5m
```
**2. Macros requeridas (nivel de host o template):**
```xml
{$CISCO_USER} = admin
{$CISCO_PASSWORD} = cisco123
```
**3. Item prototypes:**
- `cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
- `cisco.mac.addresses[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
- `cisco.mac.vlans[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
**4. Trigger prototypes:**
- MAC count changed
- Too many MACs on port
- New MAC addresses detected
### Script de External Check
```bash
#!/bin/bash
# /usr/lib/zabbix/externalscripts/cisco_single_mac_discovery.sh
HOST="$1"
USERNAME="$2"
PASSWORD="$3"
HOSTNAME="$4"
docker run --rm --network host cisco-single-mac-monitor \
--host "$HOST" \
--username "$USERNAME" \
--password "$PASSWORD" \
--hostname "$HOSTNAME" \
--quiet
```
---
## 🔧 Ventajas de la Versión Simplificada
### ✅ Ventajas
1. **JSON Manejable**: Un dispositivo = Un JSON pequeño
2. **Escalabilidad**: Cada dispositivo se procesa independientemente
3. **Flexibilidad**: Diferentes credenciales por dispositivo
4. **Paralelización**: Zabbix puede ejecutar múltiples checks en paralelo
5. **Debugging Fácil**: Probar un dispositivo específico
6. **Recursos Mínimos**: Contenedor ligero y rápido
### 📊 Comparación con versión completa
| Aspecto | Versión Single | Versión Completa |
|---------|----------------|------------------|
| JSON por ejecución | Pequeño (1 dispositivo) | Grande (N dispositivos) |
| Configuración | Línea de comandos | Archivo YAML |
| Flexibilidad | Alta (por dispositivo) | Media (global) |
| Debugging | Fácil | Complejo |
| Recursos | Mínimos | Moderados |
| Uso en Zabbix | External check directo | Requires data processing |
---
## 🛠️ Desarrollo y Testing
### Ejecutar sin Docker
```bash
# Instalar dependencias
pip3 install paramiko PyYAML
# Ejecutar directamente
python3 cisco_single_mac_monitor.py \
--host 192.168.1.10 \
--username admin \
--password cisco123 \
--debug
```
### Testing del script de integración
```bash
# Test de dependencias
./zabbix_single_integration.sh test
# Test manual
./zabbix_single_integration.sh 192.168.1.10 admin cisco123 SW-TEST
# Test con hostname personalizado
./zabbix_single_integration.sh 192.168.1.10 admin cisco123 SW-CORE-001
```
### Validar JSON
```bash
# Ejecutar y validar JSON
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -q | jq .
# Verificar estructura Zabbix LLD
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 -q | jq '.data[0]'
```
---
## 🔍 Troubleshooting
### Problemas comunes
**1. Error de conexión SSH:**
```bash
# Probar conexión manual
ssh admin@192.168.1.10
# Debug del script
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 --debug
```
**2. JSON vacío:**
```bash
# Verificar comando en dispositivo
ssh admin@192.168.1.10
show mac address-table
# Verificar logs
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 --debug
```
**3. Imagen Docker no encontrada:**
```bash
# Verificar imagen
docker images | grep cisco-single-mac
# Reconstruir si es necesario
docker build -f Dockerfile.single -t cisco-single-mac-monitor .
```
### Logs útiles
```bash
# Logs del script (van por stderr)
python3 cisco_single_mac_monitor.py -H 192.168.1.10 -u admin -p cisco123 2>debug.log
# Logs del contenedor
docker run --rm --network host cisco-single-mac-monitor \
--host 192.168.1.10 --username admin --password cisco123 --debug 2>container.log
```
---
## 📈 Monitorización en Producción
### Configuración recomendada en Zabbix
1. **Por dispositivo de red**:
- Crear macro `{$CISCO_USER}` y `{$CISCO_PASSWORD}`
- Asignar template "Cisco MAC Monitor"
- Discovery rule se ejecuta cada 5 minutos
2. **Alertas importantes**:
- Cambio en número de MACs por puerto
- Nueva MAC no autorizada
- Puerto con demasiadas MACs (>5)
3. **Dashboards**:
- Gráfico de MACs por dispositivo
- Top 10 puertos con más MACs
- Alertas recientes de MACs
---
## 🔒 Seguridad
### Mejores prácticas
1. **Credenciales**:
- Usar macros de Zabbix para passwords
- Crear usuario específico en switches
- Rotar credenciales regularmente
2. **Red**:
- Ejecutar en VLAN de management
- Configurar ACLs en switches
- Monitorizar accesos SSH
3. **Contenedor**:
- Usuario no privilegiado
- Sin volúmenes innecesarios
- Imagen base actualizada
---
## 📄 Licencia
Este proyecto está bajo licencia MIT.
---
**Versión simplificada optimizada para uso individual por dispositivo** 🎯
-370
View File
@@ -1,370 +0,0 @@
#!/usr/bin/env python3
"""
Cisco MAC Address Monitor - Single Device Version
=================================================
Script simplificado para monitorizar direcciones MAC en un dispositivo Cisco específico.
Recibe parámetros por línea de comandos y genera salida JSON para Zabbix LLD.
Compatible con modelos C2960, C3560, C3560E, C2960X, C3560CX y Catalyst series.
Uso:
python3 cisco_single_mac_monitor.py --host <IP> --username <USER> --password <PASS>
Autor: Sistema de Monitorización de Red
Fecha: Octubre 2024
"""
import json
import logging
import argparse
import paramiko
import re
import sys
from datetime import datetime
from typing import List, Dict, Any
from dataclasses import dataclass
@dataclass
class MacEntry:
"""Entrada de tabla MAC"""
vlan: str
mac_address: str
type: str
port: str
class CiscoSSHConnector:
"""Conector SSH para dispositivos Cisco"""
def __init__(self, host: str, username: str, password: str, port: int = 22, timeout: int = 30):
self.host = host
self.username = username
self.password = password
self.port = port
self.timeout = timeout
self.ssh_client = None
self.logger = logging.getLogger(__name__)
def connect(self) -> bool:
"""Establece conexión SSH con el dispositivo"""
try:
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.logger.info(f"Conectando a {self.host}")
self.ssh_client.connect(
hostname=self.host,
port=self.port,
username=self.username,
password=self.password,
timeout=self.timeout,
look_for_keys=False,
allow_agent=False
)
self.logger.info(f"Conexión exitosa a {self.host}")
return True
except Exception as e:
self.logger.error(f"Error conectando a {self.host}: {str(e)}")
return False
def execute_command(self, command: str) -> str:
"""Ejecuta comando en el dispositivo"""
if not self.ssh_client:
raise Exception("No hay conexión SSH activa")
try:
self.logger.debug(f"Ejecutando comando: {command}")
stdin, stdout, stderr = self.ssh_client.exec_command(command)
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if error:
self.logger.warning(f"Error en comando '{command}': {error}")
return output
except Exception as e:
self.logger.error(f"Error ejecutando comando '{command}': {str(e)}")
raise
def disconnect(self):
"""Cierra la conexión SSH"""
if self.ssh_client:
self.ssh_client.close()
self.logger.info(f"Desconectado de {self.host}")
class CiscoMacTableParser:
"""Parser para tabla MAC de dispositivos Cisco"""
def __init__(self):
self.logger = logging.getLogger(__name__)
def get_mac_table(self, ssh_connector: CiscoSSHConnector) -> List[MacEntry]:
"""Obtiene la tabla MAC del dispositivo"""
try:
# Comando para obtener tabla MAC (compatible con IOS 12.x y 15.x)
command = "show mac address-table"
output = ssh_connector.execute_command(command)
mac_entries = self._parse_mac_table_output(output)
self.logger.info(f"Obtenidas {len(mac_entries)} entradas de tabla MAC")
return mac_entries
except Exception as e:
self.logger.error(f"Error obteniendo tabla MAC: {str(e)}")
return []
def _parse_mac_table_output(self, output: str) -> List[MacEntry]:
"""Parsea la salida del comando show mac address-table"""
mac_entries = []
# Patrones para diferentes formatos de salida de IOS
patterns = [
# IOS 12.x y 15.x estándar
r'^\s*(\d+)\s+([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})\s+(\w+)\s+(.+)$',
# Formato alternativo con guiones
r'^\s*(\d+)\s+([0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2})\s+(\w+)\s+(.+)$',
# Formato con dos puntos
r'^\s*(\d+)\s+([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})\s+(\w+)\s+(.+)$'
]
lines = output.split('\n')
for line in lines:
line = line.strip()
if not line or line.startswith('-') or 'VLAN' in line.upper() or 'MAC Address' in line:
continue
for pattern in patterns:
match = re.match(pattern, line)
if match:
vlan = match.group(1)
mac_address = self._normalize_mac_address(match.group(2))
mac_type = match.group(3)
port = match.group(4).strip()
# Filtrar MACs del CPU y otras no relevantes
if not self._is_valid_port(port):
continue
mac_entries.append(MacEntry(
vlan=vlan,
mac_address=mac_address,
type=mac_type,
port=port
))
break
return mac_entries
def _normalize_mac_address(self, mac: str) -> str:
"""Normaliza formato de dirección MAC"""
# Remover todos los separadores y convertir a minúsculas
clean_mac = re.sub(r'[.:-]', '', mac.lower())
# Formatear como XX:XX:XX:XX:XX:XX
if len(clean_mac) == 12:
return ':'.join([clean_mac[i:i+2] for i in range(0, 12, 2)])
return mac
def _is_valid_port(self, port: str) -> bool:
"""Verifica si el puerto es válido para monitorización"""
# Excluir puertos del CPU, uplinks management, etc.
exclude_patterns = [
r'cpu',
r'switch',
r'router',
r'null',
r'drop',
r'punt'
]
port_lower = port.lower()
for pattern in exclude_patterns:
if re.search(pattern, port_lower):
return False
return True
class ZabbixLLDFormatter:
"""Formateador de datos para Zabbix Low Level Discovery"""
def __init__(self, device_hostname: str):
self.device_hostname = device_hostname
self.logger = logging.getLogger(__name__)
def format_for_zabbix(self, mac_entries: List[MacEntry]) -> Dict[str, Any]:
"""Formatea datos para Zabbix LLD"""
discovery_data = []
timestamp = datetime.now().isoformat()
# Agrupar MACs por puerto
ports_data = {}
for entry in mac_entries:
port = entry.port
if port not in ports_data:
ports_data[port] = {
"port_name": port,
"device_hostname": self.device_hostname,
"mac_addresses": [],
"mac_count": 0,
"vlans": set()
}
ports_data[port]["mac_addresses"].append({
"mac": entry.mac_address,
"vlan": entry.vlan,
"type": entry.type
})
ports_data[port]["vlans"].add(entry.vlan)
# Crear entradas para LLD
for port, data in ports_data.items():
data["mac_count"] = len(data["mac_addresses"])
data["vlans"] = list(data["vlans"])
discovery_item = {
"{#DEVICE_HOSTNAME}": self.device_hostname,
"{#PORT_NAME}": port,
"{#MAC_COUNT}": data["mac_count"],
"{#VLANS}": ",".join(data["vlans"]),
"{#MAC_ADDRESSES}": json.dumps(data["mac_addresses"]),
"{#LAST_UPDATE}": timestamp
}
discovery_data.append(discovery_item)
result = {
"data": discovery_data,
"timestamp": timestamp,
"device_hostname": self.device_hostname,
"total_ports": len(discovery_data),
"total_macs": len(mac_entries)
}
self.logger.info(f"Formateados datos para dispositivo {self.device_hostname}: {len(discovery_data)} puertos, {len(mac_entries)} MACs")
return result
def setup_logging(debug: bool = False) -> logging.Logger:
"""Configura el sistema de logging"""
level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(
level=level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stderr)
]
)
return logging.getLogger(__name__)
def main():
"""Función principal"""
parser = argparse.ArgumentParser(
description='Monitor de direcciones MAC para dispositivo Cisco individual',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Ejemplos de uso:
%(prog)s --host 192.168.1.10 --username admin --password cisco123
%(prog)s -H 192.168.1.10 -u admin -p cisco123 --port 2222
%(prog)s -H 192.168.1.10 -u admin -p cisco123 --debug
%(prog)s -H 192.168.1.10 -u admin -p cisco123 --hostname SW-CORE-001
Salida:
El script genera JSON en formato Zabbix LLD por stdout.
Los logs van por stderr.
"""
)
# Argumentos requeridos
parser.add_argument('--host', '-H', required=True,
help='Dirección IP o hostname del dispositivo Cisco')
parser.add_argument('--username', '-u', required=True,
help='Usuario para conexión SSH')
parser.add_argument('--password', '-p', required=True,
help='Contraseña para conexión SSH')
# Argumentos opcionales
parser.add_argument('--port', '-P', type=int, default=22,
help='Puerto SSH (default: 22)')
parser.add_argument('--timeout', '-t', type=int, default=30,
help='Timeout de conexión en segundos (default: 30)')
parser.add_argument('--hostname', '-n',
help='Nombre del dispositivo para identificación (default: usa --host)')
parser.add_argument('--debug', '-d', action='store_true',
help='Habilitar modo debug')
parser.add_argument('--quiet', '-q', action='store_true',
help='Modo silencioso (solo JSON por stdout)')
args = parser.parse_args()
# Configurar logging
if not args.quiet:
logger = setup_logging(args.debug)
else:
# En modo silencioso, solo errores críticos
logging.basicConfig(level=logging.CRITICAL)
logger = logging.getLogger(__name__)
# Determinar hostname del dispositivo
device_hostname = args.hostname or args.host
# Inicializar componentes
ssh_connector = CiscoSSHConnector(
host=args.host,
username=args.username,
password=args.password,
port=args.port,
timeout=args.timeout
)
mac_parser = CiscoMacTableParser()
zabbix_formatter = ZabbixLLDFormatter(device_hostname)
try:
# Conectar al dispositivo
if not ssh_connector.connect():
logger.error("No se pudo establecer conexión SSH")
sys.exit(1)
# Obtener tabla MAC
mac_entries = mac_parser.get_mac_table(ssh_connector)
if not mac_entries:
logger.warning("No se obtuvieron entradas de tabla MAC")
# Formatear para Zabbix
zabbix_data = zabbix_formatter.format_for_zabbix(mac_entries)
# Generar salida JSON
json_output = json.dumps(zabbix_data, indent=2, ensure_ascii=False)
print(json_output)
if not args.quiet:
logger.info("Proceso completado exitosamente")
except Exception as e:
logger.error(f"Error durante la ejecución: {str(e)}")
sys.exit(1)
finally:
ssh_connector.disconnect()
if __name__ == '__main__':
main()
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env python3
import logging
logging.basicConfig(level=logging.DEBUG)
from cisco_mac_monitor import CiscoSSHConnector, CiscoMacTableParser
ssh = CiscoSSHConnector('10.150.226.19', 'itops', 'Tr4!D0r3s')
parser = CiscoMacTableParser()
print("Conectando...")
ssh.connect()
print("Ejecutando comando...")
output = ssh.execute_command('show mac address-table')
print('OUTPUT LENGTH:', len(output))
print("\nParseando...")
mac_entries = parser._parse_mac_table_output(output)
print(f"MAC ENTRIES FOUND: {len(mac_entries)}")
for i, entry in enumerate(mac_entries[:3]):
print(f"Entry {i+1}: VLAN={entry.vlan}, MAC={entry.mac_address}, Type={entry.type}, Port={entry.port}")
ssh.disconnect()
print("Desconectado.")
-16
View File
@@ -1,16 +0,0 @@
#!/usr/bin/env python3
import logging
logging.basicConfig(level=logging.DEBUG)
from cisco_mac_monitor import CiscoSSHConnector, CiscoMacTableParser
ssh = CiscoSSHConnector('10.150.226.19', 'itops', 'Tr4!D0r3s')
print("Conectando...")
ssh.connect()
print("Ejecutando comando...")
output = ssh.execute_command('show mac address-table')
print('OUTPUT LENGTH:', len(output))
print('FIRST 300 CHARS:', repr(output[:300]))
if len(output) > 300:
print('LAST 100 CHARS:', repr(output[-100:]))
ssh.disconnect()
print("Desconectado.")
-51
View File
@@ -1,51 +0,0 @@
# Docker Compose para Cisco MAC Monitor HTTP Service
# ==================================================
services:
cisco-mac-http:
build:
context: .
dockerfile: Dockerfile.http
platforms:
- linux/amd64
image: cisco-mac-monitor-http:latest
container_name: cisco-mac-monitor-http
# Puerto HTTP
ports:
- "8080:8080"
# Red del host para acceso a dispositivos locales
network_mode: host
# Variables de entorno
environment:
- HTTP_HOST=0.0.0.0
- HTTP_PORT=8080
# Límites de recursos
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
# Reinicio automático
restart: unless-stopped
# Logging del contenedor
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Ejemplo de uso:
# docker-compose -f docker-compose.http.yml up -d
#
# Test:
# curl "http://localhost:8080/health"
# curl "http://localhost:8080/mac-monitor?host=192.168.1.10&username=admin&password=cisco123"
-39
View File
@@ -1,39 +0,0 @@
# Docker Compose para Cisco Single MAC Monitor
# ============================================
services:
cisco-mac-monitor:
build:
context: .
dockerfile: Dockerfile
platforms:
- linux/amd64
image: cisco-mac-monitor:latest
container_name: cisco-mac-monitor
# Red del host para acceso a dispositivos locales
network_mode: host
# Límites de recursos
deploy:
resources:
limits:
cpus: '0.2'
memory: 128M
reservations:
cpus: '0.05'
memory: 32M
# Logging del contenedor
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "2"
# Ejemplo de uso en comentarios:
# Para ejecutar el contenedor con parámetros:
# docker run --rm --network host cisco-mac-monitor \
# --host 192.168.1.10 \
# --username admin \
# --password cisco123
-342
View File
@@ -1,342 +0,0 @@
#!/bin/bash
# Script de prueba para Cisco MAC Monitor
# ======================================
set -e
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Contadores
TESTS_PASSED=0
TESTS_FAILED=0
# Función para logging
log() {
echo -e "${BLUE}[TEST]${NC} $1"
}
success() {
echo -e "${GREEN}[PASS]${NC} $1"
((TESTS_PASSED++))
}
error() {
echo -e "${RED}[FAIL]${NC} $1"
((TESTS_FAILED++))
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
# Test 1: Verificar archivos necesarios
test_single_files() {
log "Verificando archivos necesarios..."
local files=(
"cisco_mac_monitor.py"
"Dockerfile"
"docker-compose.yml"
"zabbix_integration.sh"
"example_output.json"
"README.md"
)
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
success "Archivo encontrado: $file"
else
error "Archivo faltante: $file"
fi
done
}
# Test 2: Validar sintaxis del script principal
test_single_python_syntax() {
log "Validando sintaxis del script simplificado..."
if command -v python3 >/dev/null 2>&1; then
if python3 -m py_compile cisco_mac_monitor.py; then
success "Sintaxis de Python válida (script simplificado)"
else
error "Error de sintaxis en script simplificado"
fi
else
warn "Python3 no disponible para test de sintaxis"
fi
}
# Test 3: Probar ayuda del script
test_script_help() {
log "Probando función de ayuda del script..."
if command -v python3 >/dev/null 2>&1; then
if python3 cisco_mac_monitor.py --help >/dev/null 2>&1; then
success "Función de ayuda funciona correctamente"
else
error "Error en función de ayuda"
fi
else
warn "Python3 no disponible para test de ayuda"
fi
}
# Test 4: Validar Dockerfile simplificado
test_single_dockerfile() {
log "Validando Dockerfile simplificado..."
if command -v docker >/dev/null 2>&1; then
if docker build -f Dockerfile -t cisco-mac-monitor-test . >/dev/null 2>&1; then
success "Dockerfile válido - imagen construida"
# Limpiar imagen de test
docker rmi cisco-mac-monitor-test >/dev/null 2>&1 || true
else
error "Error en construcción de Dockerfile"
fi
else
warn "Docker no disponible para test de Dockerfile"
fi
}
# Test 5: Validar docker-compose simplificado
test_single_docker_compose() {
log "Validando docker-compose.single.yml..."
if command -v docker-compose >/dev/null 2>&1; then
if docker-compose config >/dev/null 2>&1; then
success "docker-compose.yml válido"
else
error "Error en docker-compose.yml"
fi
else
warn "docker-compose no disponible para test"
fi
}
# Test 6: Validar JSON de ejemplo simplificado
test_single_example_json() {
log "Validando JSON de ejemplo simplificado..."
if [[ -f "example_output.json" ]]; then
if command -v python3 >/dev/null 2>&1; then
if python3 -m json.tool example_output.json >/dev/null 2>&1; then
success "JSON de ejemplo válido"
# Verificar estructura específica de Zabbix LLD
if python3 -c "
import json
with open('example_output.json', 'r') as f:
data = json.load(f)
# Verificar estructura básica
if 'data' not in data:
print('Missing data array')
exit(1)
if not isinstance(data['data'], list):
print('data should be an array')
exit(1)
# Verificar macros de Zabbix LLD en primer elemento
if len(data['data']) > 0:
item = data['data'][0]
required_macros = ['{#DEVICE_HOSTNAME}', '{#PORT_NAME}', '{#MAC_COUNT}', '{#VLANS}', '{#MAC_ADDRESSES}', '{#LAST_UPDATE}']
for macro in required_macros:
if macro not in item:
print(f'Missing macro: {macro}')
exit(1)
print('Zabbix LLD structure valid')
"; then
success "Estructura Zabbix LLD válida en JSON de ejemplo"
else
error "Estructura Zabbix LLD inválida en JSON de ejemplo"
fi
else
error "JSON de ejemplo inválido"
fi
else
warn "Python3 no disponible para validación de JSON"
fi
else
error "Archivo example_output.json no encontrado"
fi
}
# Test 7: Validar script de integración con Zabbix
test_zabbix_integration_script() {
log "Validando script de integración con Zabbix..."
if [[ -f "zabbix_integration.sh" ]]; then
if [[ -x "zabbix_integration.sh" ]]; then
success "Script de integración tiene permisos de ejecución"
# Probar función de ayuda
if ./zabbix_integration.sh help >/dev/null 2>&1; then
success "Función de ayuda del script de integración funciona"
else
warn "Problema con función de ayuda del script de integración"
fi
else
warn "Script de integración no tiene permisos de ejecución"
chmod +x zabbix_integration.sh
success "Permisos de ejecución agregados al script de integración"
fi
else
error "Script de integración no encontrado"
fi
}
# Test 8: Validar que la imagen simplificada es más pequeña
test_image_size_comparison() {
log "Comparando tamaño de imágenes..."
if command -v docker >/dev/null 2>&1; then
# Construir ambas imágenes
log "Construyendo imagen simplificada..."
if docker build -f Dockerfile.single -t cisco-single-test:single . >/dev/null 2>&1; then
local single_size=$(docker images cisco-single-test:single --format "{{.Size}}")
success "Imagen simplificada construida: $single_size"
# Limpiar imagen de test
docker rmi cisco-single-test:single >/dev/null 2>&1 || true
else
warn "No se pudo construir imagen simplificada para comparación"
fi
else
warn "Docker no disponible para comparación de tamaños"
fi
}
# Test 9: Probar script sin parámetros (debe mostrar error útil)
test_parameter_validation() {
log "Probando validación de parámetros..."
if command -v python3 >/dev/null 2>&1; then
# Script debe fallar sin parámetros pero mostrar mensaje útil
if ! python3 cisco_mac_monitor.py >/dev/null 2>&1; then
success "Script valida parámetros requeridos correctamente"
else
warn "Script no valida parámetros requeridos"
fi
# Probar con parámetros incompletos
if ! python3 cisco_mac_monitor.py --host 192.168.1.10 >/dev/null 2>&1; then
success "Script valida parámetros incompletos correctamente"
else
warn "Script no valida parámetros incompletos"
fi
else
warn "Python3 no disponible para test de validación"
fi
}
# Test 10: Verificar documentación simplificada
test_single_documentation() {
log "Verificando documentación simplificada..."
if [[ -f "README.md" ]]; then
local readme_size=$(wc -c < README.md)
if [[ $readme_size -gt 500 ]]; then
success "README.md presente y completo ($readme_size bytes)"
else
warn "README.md muy pequeño ($readme_size bytes)"
fi
else
error "README.md faltante"
fi
}
# Test de integración con contenedor simplificado
test_single_container_integration() {
log "Ejecutando test de integración con contenedor simplificado..."
if command -v docker >/dev/null 2>&1; then
# Construir imagen
if docker build -f Dockerfile -t cisco-mac-test . >/dev/null 2>&1; then
success "Imagen Docker construida correctamente"
# Test de help en contenedor
if docker run --rm cisco-mac-test --help >/dev/null 2>&1; then
success "Test de help en contenedor exitoso"
else
error "Test de help en contenedor falló"
fi
# Limpiar
docker rmi cisco-mac-test >/dev/null 2>&1 || true
else
error "Error construyendo imagen para test de integración"
fi
else
warn "Docker no disponible para test de integración"
fi
}
# Función principal
main() {
echo "=============================================="
echo " Cisco MAC Monitor - Test Suite"
echo "=============================================="
echo
# Verificar que estamos en el directorio correcto
if [[ ! -f "cisco_mac_monitor.py" ]]; then
error "No se encontró cisco_mac_monitor.py. ¿Estás en el directorio correcto?"
exit 1
fi
# Ejecutar tests específicos para versión simplificada
test_single_files
test_single_python_syntax
test_script_help
test_parameter_validation
test_single_example_json
test_zabbix_integration_script
test_single_documentation
# Tests que requieren herramientas externas
if command -v docker >/dev/null 2>&1; then
test_single_dockerfile
test_single_container_integration
test_image_size_comparison
else
warn "Docker no disponible - saltando tests de Docker"
fi
if command -v docker-compose >/dev/null 2>&1; then
test_single_docker_compose
else
warn "docker-compose no disponible - saltando test"
fi
# Resumen
echo
echo "=============================================="
echo " Resumen de Tests"
echo "=============================================="
echo -e "${GREEN}Tests exitosos: $TESTS_PASSED${NC}"
echo -e "${RED}Tests fallidos: $TESTS_FAILED${NC}"
echo
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "${GREEN}🎉 Todos los tests pasaron exitosamente!${NC}"
echo
echo "La versión está lista para usar:"
echo "1. Construir imagen: docker build -t cisco-mac-monitor ."
echo "2. Ejecutar: docker run --rm --network host cisco-mac-monitor --host <IP> --username <USER> --password <PASS>"
echo "3. Integrar con Zabbix usando zabbix_integration.sh"
exit 0
else
echo -e "${RED}❌ Algunos tests fallaron. Revisa los errores arriba.${NC}"
exit 1
fi
}
# Ejecutar tests
main "$@"
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python3
"""
Prueba para verificar que se muestren todos los puertos, incluidos los sin MACs conectadas.
"""
import requests
import json
import ssl
import urllib3
# Deshabilitar advertencias SSL
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def test_all_ports():
"""Prueba que el servicio muestre todos los puertos, no solo los con MACs"""
# Configuración de la prueba
url = "https://localhost:8443/api/mac-table"
payload = {
"cisco_ip": "10.150.226.19",
"ssh_username": "ruser",
"ssh_password": "pass123",
"use_legacy_ssh": True
}
headers = {
"Content-Type": "application/json"
}
try:
print("Realizando petición HTTPS POST al servicio...")
print(f"URL: {url}")
print(f"Payload: {json.dumps(payload, indent=2)}")
response = requests.post(
url,
json=payload,
headers=headers,
verify=False,
timeout=30
)
print(f"\nRespuesta HTTP: {response.status_code}")
print(f"Headers de respuesta: {dict(response.headers)}")
if response.status_code == 200:
data = response.json()
print(f"\nRespuesta exitosa:")
print(json.dumps(data, indent=2))
# Analizar puertos
if 'data' in data:
ports_with_macs = 0
ports_without_macs = 0
total_ports = len(data['data'])
for item in data['data']:
interface = item.get('{#INTERFACE}', '')
mac_count = item.get('MAC_COUNT', 0)
if mac_count > 0:
ports_with_macs += 1
else:
ports_without_macs += 1
print(f"\n=== ANÁLISIS DE PUERTOS ===")
print(f"Total de puertos detectados: {total_ports}")
print(f"Puertos con MACs conectadas: {ports_with_macs}")
print(f"Puertos sin MACs (vacíos): {ports_without_macs}")
# Mostrar algunos ejemplos
print(f"\n=== EJEMPLOS DE PUERTOS ===")
for i, item in enumerate(data['data'][:10]): # Solo los primeros 10
interface = item.get('{#INTERFACE}', '')
mac_count = item.get('MAC_COUNT', 0)
mac_addresses = item.get('MAC_ADDRESSES', '')
print(f"{i+1}. {interface}: {mac_count} MACs")
if mac_count > 0:
print(f" MACs: {mac_addresses[:100]}...") # Truncar para legibilidad
# Verificar que hay puertos sin MACs
if ports_without_macs > 0:
print(f"\n✅ ÉXITO: Se detectaron {ports_without_macs} puertos vacíos (MAC_COUNT=0)")
else:
print(f"\n⚠️ ADVERTENCIA: No se detectaron puertos vacíos")
else:
print("❌ Error: No se encontró 'data' en la respuesta")
else:
print(f"❌ Error HTTP {response.status_code}")
print(f"Mensaje: {response.text}")
except requests.exceptions.ConnectionError as e:
print(f"❌ Error de conexión: {e}")
print("Asegúrate de que el contenedor esté ejecutándose en puerto 8443")
except Exception as e:
print(f"❌ Error inesperado: {e}")
if __name__ == "__main__":
test_all_ports()
-182
View File
@@ -1,182 +0,0 @@
#!/usr/bin/env python3
"""
Test script para verificar compatibilidad SSH con dispositivos Cisco antiguos
===========================================================================
Este script valida que:
1. Las importaciones funcionen correctamente
2. La clase CiscoSSHConnector se pueda instanciar
3. Los algoritmos de compatibilidad se configuren correctamente
4. No hay errores de sintaxis en el código
Uso:
python3 test_compatibility.py
"""
import sys
import os
def test_imports():
"""Test de importaciones básicas"""
print("🔍 Testing importaciones...")
try:
import json
import logging
import argparse
import re
import sys
from datetime import datetime
from typing import List, Dict, Any
print("✅ Importaciones estándar OK")
except ImportError as e:
print(f"❌ Error en importaciones estándar: {e}")
return False
try:
import paramiko
print("✅ Paramiko disponible")
except ImportError as e:
print(f"⚠️ Paramiko no instalado: {e}")
print(" Para testing completo, instalar con: pip install paramiko")
return "paramiko_missing"
return True
def test_cisco_monitor_syntax():
"""Test de sintaxis del módulo principal"""
print("\n🔍 Testing sintaxis de cisco_mac_monitor.py...")
try:
# Compilar archivo para verificar sintaxis
with open('cisco_mac_monitor.py', 'r') as f:
source_code = f.read()
compile(source_code, 'cisco_mac_monitor.py', 'exec')
print("✅ Sintaxis de cisco_mac_monitor.py OK")
return True
except SyntaxError as e:
print(f"❌ Error de sintaxis: {e}")
print(f" Línea {e.lineno}: {e.text}")
return False
except FileNotFoundError:
print("❌ Archivo cisco_mac_monitor.py no encontrado")
return False
except Exception as e:
print(f"❌ Error inesperado: {e}")
return False
def test_class_instantiation():
"""Test de instanciación de clases (si paramiko disponible)"""
print("\n🔍 Testing instanciación de clases...")
try:
import paramiko
except ImportError:
print("⚠️ Saltando test (paramiko no disponible)")
return True
try:
# Import del módulo principal
sys.path.insert(0, '.')
from cisco_mac_monitor import CiscoSSHConnector, CiscoMacTableParser, ZabbixLLDFormatter
# Test CiscoSSHConnector
connector = CiscoSSHConnector("test.example.com", "test", "test")
print("✅ CiscoSSHConnector se puede instanciar")
# Test CiscoMacTableParser
parser = CiscoMacTableParser()
print("✅ CiscoMacTableParser se puede instanciar")
# Test ZabbixLLDFormatter
formatter = ZabbixLLDFormatter("test-device")
print("✅ ZabbixLLDFormatter se puede instanciar")
return True
except Exception as e:
print(f"❌ Error instanciando clases: {e}")
return False
def test_ssh_compatibility_config():
"""Test de configuración de compatibilidad SSH"""
print("\n🔍 Testing configuración de compatibilidad SSH...")
try:
import paramiko
except ImportError:
print("⚠️ Saltando test (paramiko no disponible)")
return True
try:
# Test de configuración Transport
transport = paramiko.Transport(('dummy', 22))
security_opts = transport.get_security_options()
# Verificar algoritmos originales
original_kex = list(security_opts.kex)
original_host_keys = list(security_opts.key_types)
# Aplicar configuración de compatibilidad
if 'diffie-hellman-group1-sha1' not in security_opts.kex:
security_opts.kex = ['diffie-hellman-group1-sha1'] + list(security_opts.kex)
print("✅ diffie-hellman-group1-sha1 añadido a KEX")
if 'ssh-rsa' not in security_opts.key_types:
security_opts.key_types = ['ssh-rsa'] + list(security_opts.key_types)
print("✅ ssh-rsa añadido a host key types")
# Verificar que se añadieron
assert 'diffie-hellman-group1-sha1' in security_opts.kex
assert 'ssh-rsa' in security_opts.key_types
print("✅ Configuración de compatibilidad SSH aplicada correctamente")
transport.close()
return True
except Exception as e:
print(f"❌ Error configurando SSH compatibility: {e}")
return False
def main():
"""Función principal de testing"""
print("=" * 60)
print("🧪 CISCO MAC MONITOR - COMPATIBILITY TEST")
print("=" * 60)
# Test 1: Imports
import_result = test_imports()
if import_result is False:
print("\n❌ FAILED: Importaciones básicas fallaron")
return 1
# Test 2: Syntax
if not test_cisco_monitor_syntax():
print("\n❌ FAILED: Errores de sintaxis encontrados")
return 1
# Test 3: Class instantiation (si paramiko disponible)
if import_result is not "paramiko_missing":
if not test_class_instantiation():
print("\n❌ FAILED: Error instanciando clases")
return 1
# Test 4: SSH compatibility config
if not test_ssh_compatibility_config():
print("\n❌ FAILED: Error configurando compatibilidad SSH")
return 1
print("\n" + "=" * 60)
if import_result == "paramiko_missing":
print("✅ PASSED (con limitaciones): Tests básicos completados")
print("📝 Para testing completo, instalar: pip install paramiko")
else:
print("✅ PASSED: Todos los tests completados exitosamente")
print("=" * 60)
return 0
if __name__ == '__main__':
exit(main())
-49
View File
@@ -1,49 +0,0 @@
#!/bin/bash
# Script de ejemplo para usar el nuevo servicio HTTPS con POST
# ============================================================
# Configuración
SERVER_URL="https://localhost:8443"
HOST="10.150.226.29"
USERNAME="itops"
PASSWORD='Tr4!D0r3s'
echo "🔒 Cisco MAC Monitor - Ejemplo HTTPS POST"
echo "=========================================="
# Test 1: Health check
echo "1. Testing health check..."
curl -k -s "${SERVER_URL}/health" | python3 -m json.tool
echo -e "\n"
# Test 2: Métricas
echo "2. Testing metrics..."
curl -k -s "${SERVER_URL}/metrics" | python3 -m json.tool
echo -e "\n"
# Test 3: Monitorización MAC con POST seguro
echo "3. Testing MAC monitoring (POST seguro)..."
# Crear JSON payload
JSON_PAYLOAD=$(cat <<EOF
{
"host": "${HOST}",
"username": "${USERNAME}",
"password": "${PASSWORD}",
"hostname": "SW-CORE-${HOST}",
"timeout": 30
}
EOF
)
echo "JSON Payload enviado:"
echo "${JSON_PAYLOAD}" | python3 -m json.tool
echo -e "\nRespuesta del servidor:"
curl -k -s -X POST "${SERVER_URL}/mac-monitor" \
-H "Content-Type: application/json" \
-d "${JSON_PAYLOAD}" | python3 -m json.tool
echo -e "\n✅ Test completado!"
-115
View File
@@ -1,115 +0,0 @@
#!/usr/bin/env python3
"""
Test script para verificar la funcionalidad de mostrar todas las interfaces
usando datos simulados cuando no hay acceso al dispositivo real.
"""
import sys
import os
# Añadir el directorio actual al path para importar los módulos
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from cisco_mac_monitor import CiscoMacTableParser, ZabbixLLDFormatter
def test_all_interfaces_functionality():
"""Prueba que todas las interfaces se muestren, incluso sin MACs"""
print("=== PRUEBA DE FUNCIONALIDAD: TODAS LAS INTERFACES ===\n")
# Simular salida de 'show interfaces status'
mock_interface_output = """
Port Name Status Vlan Duplex Speed Type
Fa0/1 connected 1 full 100 10/100BaseTX
Fa0/2 Servidor-Core connected 10 full 100 10/100BaseTX
Fa0/3 notconnect 1 auto auto 10/100BaseTX
Fa0/4 notconnect 1 auto auto 10/100BaseTX
Fa0/5 notconnect 1 auto auto 10/100BaseTX
Gi0/1 Uplink-Router connected trunk full 1000 1000BaseTX
Gi0/2 notconnect 1 auto auto 1000BaseTX
"""
# Simular salida de tabla MAC (solo algunas interfaces tienen MACs)
mock_mac_output = """
1 0021.9b5a.4f10 DYNAMIC Fa0/1
1 001b.2f89.3c21 DYNAMIC Fa0/1
10 00e0.4c36.7f89 DYNAMIC Fa0/2
10 0050.5682.1c45 DYNAMIC Fa0/2
1 0019.bbf7.4a21 DYNAMIC Gi0/1
1 0025.90e4.b832 DYNAMIC Gi0/1
1 0018.8b99.5f71 DYNAMIC Gi0/1
"""
# Crear instancias de las clases
parser = CiscoMacTableParser()
formatter = ZabbixLLDFormatter("cisco-test-device")
print("1. Parseando interfaces detectadas:")
print(" - Simulando 'show interfaces status'")
# Simular el parseo de interfaces
interfaces = parser._parse_interface_list(mock_interface_output)
print(f" - Interfaces detectadas: {len(interfaces)}")
for interface in interfaces:
print(f" * {interface}")
print(f"\n2. Parseando tabla MAC:")
print(" - Simulando 'show mac address-table'")
# Parsear tabla MAC usando el método privado
mac_entries = parser._parse_mac_table_output(mock_mac_output)
print(f" - Entradas MAC encontradas: {len(mac_entries)}")
for entry in mac_entries:
print(f" * VLAN {entry.vlan} - {entry.mac_address} en {entry.port}")
print(f"\n3. Formateando para Zabbix (con todas las interfaces):")
# Formatear para Zabbix con todas las interfaces
zabbix_data = formatter.format_for_zabbix(mac_entries, interfaces)
print(f" - Estructura de datos Zabbix: {type(zabbix_data)}")
print(f" - Claves disponibles: {list(zabbix_data.keys()) if isinstance(zabbix_data, dict) else 'No es dict'}")
if 'data' in zabbix_data and isinstance(zabbix_data['data'], list) and len(zabbix_data['data']) > 0:
print(f" - Primer elemento: {zabbix_data['data'][0]}")
print(f" - Claves del primer elemento: {list(zabbix_data['data'][0].keys())}")
# Analizar resultados
ports_with_macs = 0
ports_without_macs = 0
for item in zabbix_data['data']:
interface = item['{#PORT_NAME}']
mac_count = item['{#MAC_COUNT}']
if mac_count > 0:
ports_with_macs += 1
print(f" ✅ {interface}: {mac_count} MACs conectadas")
else:
ports_without_macs += 1
print(f" ⚪ {interface}: 0 MACs (puerto vacío)")
print(f"\n=== RESUMEN DE RESULTADOS ===")
print(f"Total de puertos: {len(zabbix_data['data'])}")
print(f"Puertos con MACs: {ports_with_macs}")
print(f"Puertos vacíos: {ports_without_macs}")
# Mostrar algunos ejemplos del JSON Zabbix
print(f"\n=== EJEMPLO DE SALIDA ZABBIX LLD ===")
import json
print(json.dumps(zabbix_data, indent=2)[:1000] + "..." if len(str(zabbix_data)) > 1000 else json.dumps(zabbix_data, indent=2))
# Verificar que la funcionalidad funciona
if ports_without_macs > 0:
print(f"\n🎉 ¡ÉXITO! La funcionalidad funciona correctamente:")
print(f" - Se detectaron {ports_without_macs} puertos vacíos con MAC_COUNT=0")
print(f" - Se detectaron {ports_with_macs} puertos con MACs conectadas")
print(f" - Total de puertos mostrados: {len(zabbix_data['data'])}")
return True
else:
print(f"\n❌ Error: No se detectaron puertos vacíos")
return False
if __name__ == "__main__":
success = test_all_interfaces_functionality()
sys.exit(0 if success else 1)
-71
View File
@@ -1,71 +0,0 @@
#!/bin/bash
# Script para probar manejo de contraseñas con caracteres especiales
# Uso: ./test_special_chars.sh
echo "🔧 PRUEBA DE MANEJO DE CARACTERES ESPECIALES EN CONTRASEÑAS"
echo "============================================================"
# URL del servicio
URL="https://localhost:8443/mac-monitor"
HEADERS="Content-Type: application/json"
echo ""
echo "1. 📝 Probando contraseña con ! (sin escape)"
echo " Password: Tr4!D0r3s"
RESPONSE1=$(curl -s -k -X POST "$URL" -H "$HEADERS" -d '{"host": "10.150.226.19", "username": "itops", "password": "Tr4!D0r3s", "use_legacy_ssh": true}')
if [[ $RESPONSE1 == *"JSON inválido"* ]]; then
echo " ❌ ERROR: $RESPONSE1"
else
echo " ✅ JSON válido - Conectando..."
fi
echo ""
echo "2. 📝 Probando contraseña con @ y # y $"
echo " Password: P@ssw0rd#123$"
RESPONSE2=$(curl -s -k -X POST "$URL" -H "$HEADERS" -d '{"host": "10.150.226.19", "username": "itops", "password": "P@ssw0rd#123$", "use_legacy_ssh": true}')
if [[ $RESPONSE2 == *"JSON inválido"* ]]; then
echo " ❌ ERROR: $RESPONSE2"
else
echo " ✅ JSON válido - Conectando..."
fi
echo ""
echo "3. 📝 Probando contraseña con espacios"
echo " Password: 'Mi Contraseña 123!'"
RESPONSE3=$(curl -s -k -X POST "$URL" -H "$HEADERS" -d '{"host": "10.150.226.19", "username": "itops", "password": "Mi Contraseña 123!", "use_legacy_ssh": true}')
if [[ $RESPONSE3 == *"JSON inválido"* ]]; then
echo " ❌ ERROR: $RESPONSE3"
else
echo " ✅ JSON válido - Conectando..."
fi
echo ""
echo "4. 📝 Probando contraseña con comillas (usando archivo temporal)"
echo " Password: Pass\"word"
echo '{"host": "10.150.226.19", "username": "itops", "password": "Pass\"word", "use_legacy_ssh": true}' > /tmp/test_payload.json
RESPONSE4=$(curl -s -k -X POST "$URL" -H "$HEADERS" -d @/tmp/test_payload.json)
rm -f /tmp/test_payload.json
if [[ $RESPONSE4 == *"JSON inválido"* ]]; then
echo " ❌ ERROR: $RESPONSE4"
else
echo " ✅ JSON válido - Conectando..."
fi
echo ""
echo "5. ❌ Probando escape incorrecto de !"
echo " Password: Tr4\\!D0r3s (incorrecto)"
RESPONSE5=$(curl -s -k -X POST "$URL" -H "$HEADERS" -d '{"host": "10.150.226.19", "username": "itops", "password": "Tr4\!D0r3s", "use_legacy_ssh": true}')
if [[ $RESPONSE5 == *"JSON inválido"* ]]; then
echo " ✅ Error esperado: $RESPONSE5"
else
echo " ❌ No debería funcionar: $RESPONSE5"
fi
echo ""
echo "📋 RESUMEN:"
echo " - Los caracteres ! @ # $ % & * + = funcionan sin escape en JSON"
echo " - Los espacios funcionan normalmente dentro de comillas"
echo " - Solo necesitan escape: \" \\ y caracteres de control \\n \\t \\r"
echo " - NO usar \\! para escapar exclamación en JSON"
echo ""
echo "✅ Pruebas completadas"
-157
View File
@@ -1,157 +0,0 @@
#!/bin/bash
# Script de integración simplificado para Zabbix External Scripts
# ==============================================================
#
# Este script debe colocarse en:
# /usr/lib/zabbix/externalscripts/cisco_single_mac_discovery.sh
#
# Y configurar permisos:
# chmod +x /usr/lib/zabbix/externalscripts/cisco_single_mac_discovery.sh
# chown zabbix:zabbix /usr/lib/zabbix/externalscripts/cisco_single_mac_discovery.sh
# Configuración
CONTAINER_IMAGE="cisco-mac-monitor:latest"
TIMEOUT=60
# Función de logging para stderr
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >&2
}
error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2
exit 1
}
# Función para ejecutar discovery en un dispositivo específico
run_single_device_discovery() {
local host="$1"
local username="$2"
local password="$3"
local hostname="$4"
# Validar parámetros
if [[ -z "$host" ]] || [[ -z "$username" ]] || [[ -z "$password" ]]; then
error "Parámetros requeridos: host, username, password"
fi
log "Ejecutando discovery para dispositivo: $host"
# Construir comando Docker
local docker_cmd=(
docker run --rm --network host
"$CONTAINER_IMAGE"
--host "$host"
--username "$username"
--password "$password"
--quiet
)
# Agregar hostname si se proporciona
if [[ -n "$hostname" ]]; then
docker_cmd+=(--hostname "$hostname")
fi
# Ejecutar con timeout
local json_data
if ! json_data=$(timeout "$TIMEOUT" "${docker_cmd[@]}" 2>/dev/null); then
error "Falló la ejecución de discovery para $host (timeout: ${TIMEOUT}s)"
fi
# Validar JSON
if [[ -z "$json_data" ]]; then
error "No se obtuvieron datos del dispositivo $host"
fi
# Validar formato JSON básico
if ! echo "$json_data" | python3 -m json.tool >/dev/null 2>&1; then
error "Datos no están en formato JSON válido para $host"
fi
# Retornar datos
echo "$json_data"
}
# Función para mostrar ayuda
show_help() {
cat << EOF
Cisco Single MAC Discovery - Zabbix Integration Script
USAGE:
$0 <HOST> <USERNAME> <PASSWORD> [HOSTNAME]
PARAMETERS:
HOST IP address or hostname of Cisco device
USERNAME SSH username
PASSWORD SSH password
HOSTNAME Optional device hostname for identification
EXAMPLES:
# Básico
$0 192.168.1.10 admin cisco123
# Con hostname personalizado
$0 192.168.1.10 admin cisco123 SW-CORE-001
# Para usar en Zabbix Discovery Rule
cisco_mac_discovery.sh {HOST.IP} {$USER} {$PASSWORD} {HOST.NAME}
ZABBIX CONFIGURATION:
Discovery Rule:
- Name: Cisco Device MAC Discovery
- Type: External check
- Key: cisco_mac_discovery.sh[{HOST.IP},{$CISCO_USER},{$CISCO_PASSWORD},{HOST.NAME}]
- Update interval: 5m
Required Macros:
- {$CISCO_USER} : SSH username
- {$CISCO_PASSWORD} : SSH password
EOF
}
# Verificar que Docker esté disponible
check_dependencies() {
if ! command -v docker >/dev/null 2>&1; then
error "Docker no está instalado o no está en PATH"
fi
if ! command -v python3 >/dev/null 2>&1; then
error "Python3 no está instalado o no está en PATH"
fi
# Verificar que la imagen existe
if ! docker image inspect "$CONTAINER_IMAGE" >/dev/null 2>&1; then
error "Imagen Docker no encontrada: $CONTAINER_IMAGE"
fi
}
# Función principal
main() {
case "${1:-}" in
"help"|"--help"|"-h"|"")
show_help
exit 0
;;
"test")
check_dependencies
log "Dependencias verificadas correctamente"
exit 0
;;
esac
# Verificar dependencias
check_dependencies
# Obtener parámetros
local host="$1"
local username="$2"
local password="$3"
local hostname="$4"
# Ejecutar discovery
run_single_device_discovery "$host" "$username" "$password" "$hostname"
}
# Ejecutar función principal con todos los argumentos
main "$@"
-82
View File
@@ -1,82 +0,0 @@
# Dockerfile para Cisco MAC Monitor
# Basado en Rocky Linux 8 con Python y dependencias SSH
FROM rockylinux:8
# Metadatos
LABEL maintainer="Equipo de Monitorización de Red"
LABEL description="Monitor de direcciones MAC para dispositivos Cisco - Compatible con Zabbix LLD"
LABEL version="1.0"
# Variables de entorno
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8
# Actualizar sistema e instalar dependencias base
RUN dnf update -y && \
dnf install -y \
python3 \
python3-pip \
python3-devel \
openssh-clients \
git \
gcc \
openssl-devel \
libffi-devel \
curl \
vim \
procps-ng \
iproute \
iputils \
net-tools && \
dnf clean all
# Crear usuario no privilegiado para ejecutar la aplicación
RUN useradd -m -u 1000 -s /bin/bash monitor && \
mkdir -p /app /config /logs /data && \
chown -R monitor:monitor /app /config /logs /data
# Instalar dependencias Python
COPY requirements.txt /tmp/requirements.txt
RUN pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir -r /tmp/requirements.txt && \
rm /tmp/requirements.txt
# Cambiar al usuario no privilegiado
USER monitor
WORKDIR /app
# Copiar archivos de la aplicación
COPY --chown=monitor:monitor cisco_mac_monitor.py /app/
COPY --chown=monitor:monitor config.yaml /config/config.yaml
# Crear directorios necesarios
RUN mkdir -p /app/scripts /app/utils
# Script de entrada personalizable
COPY --chown=monitor:monitor entrypoint.sh /app/entrypoint.sh
# Permisos de ejecución
USER root
RUN chmod +x /app/entrypoint.sh /app/cisco_mac_monitor.py
USER monitor
# Volúmenes para persistencia
VOLUME ["/config", "/logs", "/data"]
# Puertos expuestos (si fuera necesario para futuras extensiones)
EXPOSE 8080
# Variables de entorno por defecto
ENV CONFIG_FILE=/config/config.yaml
ENV LOG_LEVEL=INFO
ENV MONITORING_INTERVAL=5
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python3 /app/cisco_mac_monitor.py --config ${CONFIG_FILE} --single-run || exit 1
# Comando por defecto
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["continuous"]
-453
View File
@@ -1,453 +0,0 @@
# Cisco MAC Monitor para Zabbix LLD
## 📋 Descripción
**Cisco MAC Monitor** es una solución completa para monitorizar direcciones MAC conectadas a puertos de switches Cisco. Está diseñado específicamente para integrarse con **Zabbix Low Level Discovery (LLD)** y detectar cambios en dispositivos conectados a la red.
### Características principales:
- ✅ **Compatibilidad total** con modelos Cisco C2960, C3560, C3560E, C2960X, C3560CX y Catalyst series
- ✅ **Conexión SSH** segura a dispositivos de red
- ✅ **Formato JSON** optimizado para Zabbix LLD
- ✅ **Monitorización continua** configurable por intervalos
- ✅ **Contenedor Docker** basado en Rocky Linux 8
- ✅ **Detección automática** de cambios en tabla MAC
- ✅ **Filtros avanzados** para puertos y VLANs específicas
- ✅ **Logging completo** y manejo robusto de errores
---
## 🚀 Inicio Rápido
### 1. Preparar configuración
```bash
# Clonar o descargar los archivos
mkdir cisco-mac-monitor
cd cisco-mac-monitor
# Copiar archivos del proyecto
# - cisco_mac_monitor.py
# - config.yaml
# - Dockerfile
# - docker-compose.yml
# - requirements.txt
# - entrypoint.sh
```
### 2. Configurar dispositivos
Edita el archivo `config.yaml` con tus dispositivos:
```yaml
devices:
- hostname: "SW-PRINCIPAL-001"
ip: "192.168.1.10"
username: "admin"
password: "tu_password"
- hostname: "SW-SECUNDARIO-001"
ip: "192.168.1.11"
username: "admin"
password: "tu_password"
```
### 3. Construir y ejecutar
```bash
# Opción 1: Docker Compose (Recomendado)
docker-compose up --build -d
# Opción 2: Docker tradicional
docker build -t cisco-mac-monitor .
docker run -v $(pwd)/config:/config -v $(pwd)/data:/data cisco-mac-monitor
```
### 4. Verificar funcionamiento
```bash
# Ver logs en tiempo real
docker-compose logs -f
# Ejecutar una prueba única
docker-compose exec cisco-mac-monitor single-run
# Probar configuración
docker-compose exec cisco-mac-monitor test-config
```
---
## 📁 Estructura del Proyecto
```
cisco-mac-monitor/
├── cisco_mac_monitor.py # Script principal de Python
├── config.yaml # Configuración de dispositivos
├── requirements.txt # Dependencias de Python
├── Dockerfile # Imagen de contenedor
├── docker-compose.yml # Orquestación de contenedores
├── entrypoint.sh # Script de entrada del contenedor
├── example_output.json # Ejemplo de salida JSON
├── modelos.csv # Inventario de dispositivos (referencia)
└── README.md # Esta documentación
```
---
## ⚙️ Configuración Detallada
### Archivo `config.yaml`
```yaml
# Configuración de monitorización
monitoring:
interval_minutes: 5 # Intervalo entre monitoreos
output_file: "/data/cisco_mac_data.json" # Archivo de salida
log_level: "INFO" # Nivel de logging
log_file: "/logs/cisco_mac_monitor.log" # Archivo de log
# Dispositivos de red
devices:
- hostname: "SW-CORE-001"
ip: "192.168.1.5"
username: "admin"
password: "cisco123"
port: 22
timeout: 30
- hostname: "SW-ACCESS-001"
ip: "192.168.1.10"
username: "admin"
password: "cisco123"
# Filtros opcionales
filters:
exclude_ports:
- "Gi0/1" # Uplinks
- "Gi0/2"
exclude_vlans:
- "999" # VLAN de management
exclude_macs:
- "00:1a:2b:3c:4d:5e" # Servidores conocidos
# Configuración para Zabbix
zabbix:
custom_macros:
discovery_rule: "cisco.mac.discovery"
item_prefix: "cisco.mac"
```
### Variables de entorno del contenedor
| Variable | Default | Descripción |
|----------|---------|-------------|
| `CONFIG_FILE` | `/config/config.yaml` | Ruta al archivo de configuración |
| `LOG_LEVEL` | `INFO` | Nivel de logging (DEBUG, INFO, WARNING, ERROR) |
| `MONITORING_INTERVAL` | `5` | Intervalo en minutos (sobrescribe config.yaml) |
---
## 🐳 Uso con Docker
### Comandos básicos
```bash
# Monitorización continua (por defecto)
docker run -v $(pwd)/config:/config cisco-mac-monitor
# Ejecución única
docker run -v $(pwd)/config:/config cisco-mac-monitor single-run
# Probar configuración
docker run -v $(pwd)/config:/config cisco-mac-monitor test-config
# Probar conexiones SSH
docker run -v $(pwd)/config:/config cisco-mac-monitor test-ssh
# Shell interactivo para debugging
docker run -it -v $(pwd)/config:/config cisco-mac-monitor shell
```
### Volúmenes recomendados
```yaml
volumes:
- ./config:/config:ro # Configuración (solo lectura)
- ./logs:/logs # Logs persistentes
- ./data:/data # Datos de salida
```
---
## 📊 Integración con Zabbix
### Formato JSON de salida
El script genera JSON en el formato requerido por Zabbix LLD:
```json
{
"data": [
{
"{#DEVICE_HOSTNAME}": "SW-PB-001",
"{#PORT_NAME}": "Fa0/1",
"{#MAC_COUNT}": 2,
"{#VLANS}": "10,20",
"{#MAC_ADDRESSES}": "[{\"mac\": \"aa:bb:cc:dd:ee:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
}
],
"timestamp": "2024-10-24T14:30:00.123456",
"total_devices": 3,
"total_ports": 15
}
```
### Configuración en Zabbix
1. **Crear Discovery Rule:**
- **Name:** `Cisco MAC Discovery`
- **Type:** `External check`
- **Key:** `cisco.mac.discovery`
- **Update interval:** `5m`
2. **Item prototypes:**
- `cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
- `cisco.mac.list[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
- `cisco.mac.vlans[{#DEVICE_HOSTNAME},{#PORT_NAME}]`
3. **Trigger prototypes:**
- MAC count changes
- New unauthorized MAC detected
- MAC disappeared from port
### Script de integración para Zabbix
```bash
#!/bin/bash
# /usr/lib/zabbix/externalscripts/cisco_mac_discovery.sh
docker exec cisco-mac-monitor single-run > /dev/null 2>&1
cat /data/cisco_mac_data.json
```
---
## 🔧 Compatibilidad de Dispositivos
### Modelos soportados (basado en `modelos.csv`):
| Modelo | IOS Version | Estado |
|--------|-------------|--------|
| C2960 | 12.2(35)SE5+ | ✅ Soportado |
| C2960X | 15.2(2)E3+ | ✅ Soportado |
| C2960S | 12.2(55)SE5+ | ✅ Soportado |
| C3560 | 12.2(25)SEB4+ | ✅ Soportado |
| C3560E | 12.2(55)SE3+ | ✅ Soportado |
| C3560CX | 15.2(4)E6+ | ✅ Soportado |
| Catalyst 3K | 16.6.5+ | ✅ Soportado |
### Comandos utilizados:
- `show mac address-table` - Obtener tabla MAC completa
- Compatible con IOS 12.x, 15.x y 16.x
---
## 🛠️ Desarrollo y Personalización
### Ejecutar en modo desarrollo
```bash
# Instalar dependencias
pip3 install -r requirements.txt
# Ejecutar directamente
python3 cisco_mac_monitor.py --config config.yaml --single-run
# Debug mode
python3 cisco_mac_monitor.py --config config.yaml --single-run --debug
```
### Estructura del código
```python
# Clases principales:
CiscoSSHConnector # Manejo de conexiones SSH
CiscoMacTableParser # Parsing de tabla MAC
ZabbixLLDFormatter # Formateo para Zabbix
CiscoMacMonitor # Orquestador principal
```
### Agregar nuevo modelo de dispositivo
1. Editar `CiscoMacTableParser._parse_mac_table_output()`
2. Agregar nuevo patrón regex para el formato de salida
3. Probar con `test-ssh` command
---
## 🔍 Troubleshooting
### Problemas comunes
**1. Error de conexión SSH:**
```bash
# Probar conectividad
docker-compose exec cisco-mac-monitor test-ssh
# Verificar credenciales en config.yaml
# Verificar conectividad de red del contenedor
```
**2. Formato JSON incorrecto:**
```bash
# Verificar salida
docker-compose exec cisco-mac-monitor single-run
cat data/cisco_mac_data.json | jq .
```
**3. No se detectan MACs:**
```bash
# Verificar comando en dispositivo
ssh admin@192.168.1.10
show mac address-table
# Verificar filtros en config.yaml
```
**4. Container no inicia:**
```bash
# Verificar logs
docker-compose logs cisco-mac-monitor
# Verificar configuración
docker-compose exec cisco-mac-monitor test-config
```
### Logs útiles
```bash
# Logs del contenedor
docker-compose logs -f cisco-mac-monitor
# Logs internos de la aplicación
docker-compose exec cisco-mac-monitor cat /logs/cisco_mac_monitor.log
# Debug completo
docker-compose exec cisco-mac-monitor tail -f /logs/cisco_mac_monitor.log
```
---
## 📈 Monitorización y Alertas
### Métricas disponibles
- **MAC Count por puerto:** Número de MACs conectadas
- **VLAN Distribution:** Distribución de VLANs por puerto
- **MAC Changes:** Cambios en tabla MAC
- **Device Connectivity:** Estado de conexión a dispositivos
### Alertas recomendadas
1. **Nuevo dispositivo conectado** (MAC no autorizada)
2. **Dispositivo desconectado** (MAC desaparecida)
3. **Puerto saturado** (demasiadas MACs)
4. **Error de conectividad** con switch
5. **Cambio en VLAN** de dispositivo conocido
---
## 🔒 Seguridad
### Mejores prácticas
1. **Credenciales:**
- Usar variables de entorno para passwords
- Configurar usuarios SSH específicos para monitorización
- Rotar credenciales regularmente
2. **Red:**
- Usar VLAN de management dedicada
- Configurar ACLs para acceso SSH
- Monitorizar accesos SSH en logs de switches
3. **Contenedor:**
- Ejecutar como usuario no privilegiado
- Montar volúmenes como solo lectura cuando sea posible
- Actualizar imagen base regularmente
### Configuración SSH en switches Cisco
```cisco
! Crear usuario para monitorización
username monitor privilege 1 password cisco123
! Configurar acceso SSH
line vty 0 15
login local
transport input ssh
! ACL para acceso (opcional)
access-list 99 permit 192.168.1.100
line vty 0 15
access-class 99 in
```
---
## 🤝 Contribución
### Cómo contribuir
1. Fork del repositorio
2. Crear branch para feature: `git checkout -b feature/nueva-funcionalidad`
3. Commit cambios: `git commit -am 'Agregar nueva funcionalidad'`
4. Push al branch: `git push origin feature/nueva-funcionalidad`
5. Crear Pull Request
### Reportar bugs
Incluir en el reporte:
- Versión del script
- Modelo y versión IOS del dispositivo
- Logs relevantes
- Configuración utilizada (sin credenciales)
---
## 📄 Licencia
Este proyecto está bajo licencia MIT. Ver archivo `LICENSE` para más detalles.
---
## 👥 Soporte
- **Documentación:** Este README
- **Logs:** `/logs/cisco_mac_monitor.log`
- **Ejemplos:** `example_output.json`
- **Issues:** GitHub Issues
---
## 🏷️ Versiones
### v1.0.0 (Actual)
- ✅ Soporte inicial para switches Cisco
- ✅ Integración con Zabbix LLD
- ✅ Contenedor Docker con Rocky Linux 8
- ✅ Monitorización continua configurable
- ✅ Sistema de filtros avanzado
### Roadmap v1.1.0
- 🔄 Soporte para SNMPv3
- 🔄 API REST para consultas
- 🔄 Dashboard web integrado
- 🔄 Soporte para múltiples vendors (HP, Juniper)
---
**Desarrollado para entornos de producción con switches Cisco** 🚀
-41
View File
@@ -1,41 +0,0 @@
# Archivos de la Versión Completa (OLD)
Esta carpeta contiene todos los archivos de la **versión completa original** del Cisco MAC Monitor, que manejaba múltiples dispositivos simultáneamente con configuración YAML.
## ⚠️ Versión Obsoleta
Estos archivos corresponden al prompt original que requería:
- Configuración YAML para múltiples dispositivos
- Sistema de configuración temporal
- Monitorización continua en un solo contenedor
- JSON grande con todos los dispositivos
## 📁 Archivos contenidos:
- **`cisco_mac_monitor.py`** - Script principal de la versión completa
- **`config.yaml`** - Archivo de configuración YAML con múltiples dispositivos
- **`Dockerfile`** - Imagen Docker completa con entrypoint
- **`docker-compose.yml`** - Compose para la versión completa
- **`entrypoint.sh`** - Script de entrada con múltiples modos
- **`example_output.json`** - Ejemplo de JSON con múltiples dispositivos
- **`README.md`** - Documentación completa de la versión original
- **`test_suite.sh`** - Suite de pruebas para la versión completa
- **`zabbix_integration.sh`** - Integración Zabbix para versión completa
- **`zabbix_template_examples.md`** - Plantillas detalladas de Zabbix
## 🔄 Versión Actual
La versión actual (en el directorio padre) es la **versión simplificada** que cumple con el prompt actualizado:
- Un dispositivo por ejecución
- Parámetros por línea de comandos
- JSON pequeño y manejable
- Contenedor ligero
## 🗂️ Si necesitas la versión completa
Puedes recuperar cualquier archivo de esta carpeta si en el futuro necesitas funcionalidades de la versión completa.
---
**Fecha de archivado:** 24 de octubre de 2024
**Motivo:** Cambio de requerimientos del prompt - Simplificación a un dispositivo por ejecución
-423
View File
@@ -1,423 +0,0 @@
#!/usr/bin/env python3
"""
Cisco MAC Address Monitor for Zabbix LLD
========================================
Script para monitorizar direcciones MAC conectadas a puertos de switches Cisco.
Compatible con modelos C2960, C3560, C3560E, C2960X, C3560CX y Catalyst series.
Genera salida JSON compatible con Zabbix Low Level Discovery.
Autor: Sistema de Monitorización de Red
Fecha: Octubre 2024
"""
import json
import logging
import time
import yaml
import paramiko
import re
import sys
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
@dataclass
class DeviceConfig:
"""Configuración de dispositivo de red"""
hostname: str
ip: str
username: str
password: str
port: int = 22
device_type: str = "cisco_ios"
timeout: int = 30
@dataclass
class MacEntry:
"""Entrada de tabla MAC"""
vlan: str
mac_address: str
type: str
port: str
class CiscoSSHConnector:
"""Conector SSH para dispositivos Cisco"""
def __init__(self, device_config: DeviceConfig):
self.device_config = device_config
self.ssh_client = None
self.logger = logging.getLogger(__name__)
def connect(self) -> bool:
"""Establece conexión SSH con el dispositivo"""
try:
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.logger.info(f"Conectando a {self.device_config.hostname} ({self.device_config.ip})")
self.ssh_client.connect(
hostname=self.device_config.ip,
port=self.device_config.port,
username=self.device_config.username,
password=self.device_config.password,
timeout=self.device_config.timeout,
look_for_keys=False,
allow_agent=False
)
self.logger.info(f"Conexión exitosa a {self.device_config.hostname}")
return True
except Exception as e:
self.logger.error(f"Error conectando a {self.device_config.hostname}: {str(e)}")
return False
def execute_command(self, command: str) -> str:
"""Ejecuta comando en el dispositivo"""
if not self.ssh_client:
raise Exception("No hay conexión SSH activa")
try:
self.logger.debug(f"Ejecutando comando: {command}")
stdin, stdout, stderr = self.ssh_client.exec_command(command)
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if error:
self.logger.warning(f"Error en comando '{command}': {error}")
return output
except Exception as e:
self.logger.error(f"Error ejecutando comando '{command}': {str(e)}")
raise
def disconnect(self):
"""Cierra la conexión SSH"""
if self.ssh_client:
self.ssh_client.close()
self.logger.info(f"Desconectado de {self.device_config.hostname}")
class CiscoMacTableParser:
"""Parser para tabla MAC de dispositivos Cisco"""
def __init__(self):
self.logger = logging.getLogger(__name__)
def get_mac_table(self, ssh_connector: CiscoSSHConnector) -> List[MacEntry]:
"""Obtiene la tabla MAC del dispositivo"""
try:
# Comando para obtener tabla MAC (compatible con IOS 12.x y 15.x)
command = "show mac address-table"
output = ssh_connector.execute_command(command)
mac_entries = self._parse_mac_table_output(output)
self.logger.info(f"Obtenidas {len(mac_entries)} entradas de tabla MAC")
return mac_entries
except Exception as e:
self.logger.error(f"Error obteniendo tabla MAC: {str(e)}")
return []
def _parse_mac_table_output(self, output: str) -> List[MacEntry]:
"""Parsea la salida del comando show mac address-table"""
mac_entries = []
# Patrones para diferentes formatos de salida de IOS
# Formato típico: VLAN MAC Address Type Ports
patterns = [
# IOS 12.x y 15.x estándar
r'^\s*(\d+)\s+([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})\s+(\w+)\s+(.+)$',
# Formato alternativo con guiones
r'^\s*(\d+)\s+([0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2})\s+(\w+)\s+(.+)$',
# Formato con dos puntos
r'^\s*(\d+)\s+([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})\s+(\w+)\s+(.+)$'
]
lines = output.split('\n')
for line in lines:
line = line.strip()
if not line or line.startswith('-') or 'VLAN' in line.upper() or 'MAC Address' in line:
continue
for pattern in patterns:
match = re.match(pattern, line)
if match:
vlan = match.group(1)
mac_address = self._normalize_mac_address(match.group(2))
mac_type = match.group(3)
port = match.group(4).strip()
# Filtrar MACs del CPU y otras no relevantes
if not self._is_valid_port(port):
continue
mac_entries.append(MacEntry(
vlan=vlan,
mac_address=mac_address,
type=mac_type,
port=port
))
break
return mac_entries
def _normalize_mac_address(self, mac: str) -> str:
"""Normaliza formato de dirección MAC"""
# Remover todos los separadores y convertir a minúsculas
clean_mac = re.sub(r'[.:-]', '', mac.lower())
# Formatear como XX:XX:XX:XX:XX:XX
if len(clean_mac) == 12:
return ':'.join([clean_mac[i:i+2] for i in range(0, 12, 2)])
return mac
def _is_valid_port(self, port: str) -> bool:
"""Verifica si el puerto es válido para monitorización"""
# Excluir puertos del CPU, uplinks management, etc.
exclude_patterns = [
r'cpu',
r'switch',
r'router',
r'null',
r'drop',
r'punt'
]
port_lower = port.lower()
for pattern in exclude_patterns:
if re.search(pattern, port_lower):
return False
return True
class ZabbixLLDFormatter:
"""Formateador de datos para Zabbix Low Level Discovery"""
def __init__(self):
self.logger = logging.getLogger(__name__)
def format_for_zabbix(self, device_data: Dict[str, List[MacEntry]]) -> Dict[str, Any]:
"""Formatea datos para Zabbix LLD"""
discovery_data = []
timestamp = datetime.now().isoformat()
for hostname, mac_entries in device_data.items():
# Agrupar MACs por puerto
ports_data = {}
for entry in mac_entries:
port = entry.port
if port not in ports_data:
ports_data[port] = {
"port_name": port,
"device_hostname": hostname,
"mac_addresses": [],
"mac_count": 0,
"vlans": set()
}
ports_data[port]["mac_addresses"].append({
"mac": entry.mac_address,
"vlan": entry.vlan,
"type": entry.type
})
ports_data[port]["vlans"].add(entry.vlan)
# Crear entradas para LLD
for port, data in ports_data.items():
data["mac_count"] = len(data["mac_addresses"])
data["vlans"] = list(data["vlans"])
discovery_item = {
"{#DEVICE_HOSTNAME}": hostname,
"{#PORT_NAME}": port,
"{#MAC_COUNT}": data["mac_count"],
"{#VLANS}": ",".join(data["vlans"]),
"{#MAC_ADDRESSES}": json.dumps(data["mac_addresses"]),
"{#LAST_UPDATE}": timestamp
}
discovery_data.append(discovery_item)
result = {
"data": discovery_data,
"timestamp": timestamp,
"total_devices": len(device_data),
"total_ports": len(discovery_data)
}
self.logger.info(f"Formateados datos para {len(device_data)} dispositivos, {len(discovery_data)} puertos")
return result
class CiscoMacMonitor:
"""Monitor principal para dispositivos Cisco"""
def __init__(self, config_file: str = "config.yaml"):
self.config_file = config_file
self.config = None
self.devices = []
self.logger = self._setup_logging()
self.mac_parser = CiscoMacTableParser()
self.zabbix_formatter = ZabbixLLDFormatter()
def _setup_logging(self) -> logging.Logger:
"""Configura el sistema de logging"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/tmp/cisco_mac_monitor.log'),
logging.StreamHandler(sys.stdout)
]
)
return logging.getLogger(__name__)
def load_config(self) -> bool:
"""Carga configuración desde archivo YAML"""
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = yaml.safe_load(f)
# Validar configuración
if 'devices' not in self.config:
raise ValueError("No se encontró sección 'devices' en configuración")
if 'monitoring' not in self.config:
raise ValueError("No se encontró sección 'monitoring' en configuración")
# Crear objetos DeviceConfig
self.devices = []
for device_data in self.config['devices']:
device = DeviceConfig(**device_data)
self.devices.append(device)
self.logger.info(f"Configuración cargada: {len(self.devices)} dispositivos")
return True
except Exception as e:
self.logger.error(f"Error cargando configuración: {str(e)}")
return False
def monitor_single_device(self, device: DeviceConfig) -> List[MacEntry]:
"""Monitoriza un dispositivo individual"""
ssh_connector = CiscoSSHConnector(device)
mac_entries = []
try:
if ssh_connector.connect():
mac_entries = self.mac_parser.get_mac_table(ssh_connector)
except Exception as e:
self.logger.error(f"Error monitorizando {device.hostname}: {str(e)}")
finally:
ssh_connector.disconnect()
return mac_entries
def monitor_all_devices(self) -> Dict[str, List[MacEntry]]:
"""Monitoriza todos los dispositivos configurados"""
all_data = {}
for device in self.devices:
self.logger.info(f"Monitorizando dispositivo: {device.hostname}")
mac_entries = self.monitor_single_device(device)
all_data[device.hostname] = mac_entries
return all_data
def run_monitoring_cycle(self) -> Dict[str, Any]:
"""Ejecuta un ciclo completo de monitorización"""
self.logger.info("Iniciando ciclo de monitorización")
# Monitorizar todos los dispositivos
device_data = self.monitor_all_devices()
# Formatear para Zabbix
zabbix_data = self.zabbix_formatter.format_for_zabbix(device_data)
self.logger.info("Ciclo de monitorización completado")
return zabbix_data
def run_continuous_monitoring(self):
"""Ejecuta monitorización continua según configuración"""
if not self.config:
self.logger.error("No hay configuración cargada")
return
interval = self.config['monitoring'].get('interval_minutes', 5)
output_file = self.config['monitoring'].get('output_file', '/tmp/cisco_mac_data.json')
self.logger.info(f"Iniciando monitorización continua cada {interval} minutos")
while True:
try:
# Ejecutar ciclo de monitorización
zabbix_data = self.run_monitoring_cycle()
# Guardar datos
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(zabbix_data, f, indent=2, ensure_ascii=False)
self.logger.info(f"Datos guardados en: {output_file}")
# Esperar hasta el siguiente ciclo
time.sleep(interval * 60)
except KeyboardInterrupt:
self.logger.info("Monitorización detenida por usuario")
break
except Exception as e:
self.logger.error(f"Error en ciclo de monitorización: {str(e)}")
time.sleep(60) # Esperar 1 minuto antes de reintentar
def main():
"""Función principal"""
import argparse
parser = argparse.ArgumentParser(description='Monitor de direcciones MAC para dispositivos Cisco')
parser.add_argument('--config', '-c', default='config.yaml', help='Archivo de configuración')
parser.add_argument('--single-run', '-s', action='store_true', help='Ejecutar una sola vez')
parser.add_argument('--output', '-o', help='Archivo de salida para modo single-run')
args = parser.parse_args()
monitor = CiscoMacMonitor(args.config)
if not monitor.load_config():
sys.exit(1)
if args.single_run:
# Ejecución única
zabbix_data = monitor.run_monitoring_cycle()
output_file = args.output or '/tmp/cisco_mac_data.json'
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(zabbix_data, f, indent=2, ensure_ascii=False)
print(f"Datos guardados en: {output_file}")
else:
# Monitorización continua
monitor.run_continuous_monitoring()
if __name__ == '__main__':
main()
-93
View File
@@ -1,93 +0,0 @@
# Configuración para Cisco MAC Monitor
# ===================================
# Configuración de monitorización
monitoring:
# Intervalo de monitorización en minutos
interval_minutes: 5
# Archivo donde se guardarán los datos JSON para Zabbix
output_file: "/tmp/cisco_mac_data.json"
# Nivel de logging (DEBUG, INFO, WARNING, ERROR)
log_level: "INFO"
# Archivo de log
log_file: "/tmp/cisco_mac_monitor.log"
# Configuración de dispositivos de red
devices:
# Switch Cisco Catalyst 2960 - Planta Baja
- hostname: "SW-PB-001"
ip: "192.168.1.10"
username: "admin"
password: "cisco123"
port: 22
device_type: "cisco_ios"
timeout: 30
# Switch Cisco Catalyst 2960 - Primera Planta
- hostname: "SW-P1-001"
ip: "192.168.1.11"
username: "admin"
password: "cisco123"
port: 22
device_type: "cisco_ios"
timeout: 30
# Switch Cisco Catalyst 3560 - Core
- hostname: "SW-CORE-001"
ip: "192.168.1.5"
username: "admin"
password: "cisco123"
port: 22
device_type: "cisco_ios"
timeout: 30
# Switch Cisco Catalyst 2960X - DMZ
- hostname: "SW-DMZ-001"
ip: "192.168.100.10"
username: "admin"
password: "cisco123"
port: 22
device_type: "cisco_ios"
timeout: 30
# Configuración de filtros (opcional)
filters:
# Excluir puertos específicos del monitoreo
exclude_ports:
- "Gi0/1" # Uplink al core
- "Gi0/2" # Uplink redundante
# Excluir VLANs específicas
exclude_vlans:
- "999" # VLAN de management
# Excluir MACs específicas (ejemplo: servidores conocidos)
exclude_macs:
- "00:1a:2b:3c:4d:5e" # Servidor principal
# Configuración específica para Zabbix
zabbix:
# Macros personalizadas para LLD
custom_macros:
discovery_rule: "cisco.mac.discovery"
item_prefix: "cisco.mac"
# Configuración de metadatos adicionales
metadata:
include_device_info: true
include_vlan_info: true
include_port_status: true
# Configuración de alertas (para futuro uso)
alerts:
# Habilitar detección de cambios en MACs
detect_mac_changes: true
# Habilitar detección de MACs no autorizadas
detect_unauthorized_macs: true
# Archivo con MACs autorizadas (opcional)
authorized_macs_file: "/config/authorized_macs.txt"
-79
View File
@@ -1,79 +0,0 @@
# Docker Compose para Cisco MAC Monitor
# ====================================
version: '3.8'
services:
cisco-mac-monitor:
build:
context: .
dockerfile: Dockerfile
image: cisco-mac-monitor:latest
container_name: cisco-mac-monitor
# Reiniciar automáticamente
restart: unless-stopped
# Variables de entorno
environment:
- CONFIG_FILE=/config/config.yaml
- LOG_LEVEL=INFO
- MONITORING_INTERVAL=5
- TZ=Europe/Madrid
# Volúmenes para persistencia
volumes:
# Configuración (REQUERIDO)
- ./config:/config:ro
# Logs (opcional)
- ./logs:/logs
# Datos de salida (opcional)
- ./data:/data
# Zona horaria del host
- /etc/localtime:/etc/localtime:ro
# Red del host para acceso a dispositivos locales
network_mode: host
# Límites de recursos
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
# Healthcheck
healthcheck:
test: ["CMD", "python3", "/app/cisco_mac_monitor.py", "--config", "/config/config.yaml", "--single-run"]
interval: 5m
timeout: 30s
retries: 3
start_period: 30s
# Logging del contenedor
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Redes (opcional si no usas host)
networks:
cisco-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
# Volúmenes con nombre (alternativo)
volumes:
cisco-logs:
driver: local
cisco-data:
driver: local
-302
View File
@@ -1,302 +0,0 @@
#!/bin/bash
# Entrypoint script para Cisco MAC Monitor
# ========================================
set -e
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Función para logging
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" >&2
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1"
}
success() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] SUCCESS:${NC} $1"
}
# Verificar archivo de configuración
check_config() {
if [[ ! -f "${CONFIG_FILE}" ]]; then
error "Archivo de configuración no encontrado: ${CONFIG_FILE}"
error "Asegúrese de montar el volumen /config con config.yaml"
exit 1
fi
log "Usando archivo de configuración: ${CONFIG_FILE}"
}
# Crear directorios necesarios
setup_directories() {
log "Configurando directorios..."
# Crear directorios si no existen
mkdir -p /logs /data /tmp
# Verificar permisos
if [[ ! -w /logs ]]; then
warn "Sin permisos de escritura en /logs, usando /tmp"
export LOG_DIR=/tmp
else
export LOG_DIR=/logs
fi
if [[ ! -w /data ]]; then
warn "Sin permisos de escritura en /data, usando /tmp"
export DATA_DIR=/tmp
else
export DATA_DIR=/data
fi
success "Directorios configurados correctamente"
}
# Validar conectividad de red
test_network() {
log "Verificando conectividad de red..."
# Verificar si podemos resolver DNS
if ! nslookup google.com > /dev/null 2>&1; then
warn "No se puede resolver DNS externo, verificando conectividad local..."
fi
# Extraer IPs de dispositivos del config para test básico
if command -v python3 > /dev/null 2>&1 && [[ -f "${CONFIG_FILE}" ]]; then
python3 -c "
import yaml
try:
with open('${CONFIG_FILE}', 'r') as f:
config = yaml.safe_load(f)
for device in config.get('devices', []):
ip = device.get('ip')
if ip:
print(f'Testing connectivity to {ip}...')
import subprocess
result = subprocess.run(['ping', '-c', '1', '-W', '3', ip],
capture_output=True, timeout=5)
if result.returncode == 0:
print(f'✓ {ip} reachable')
else:
print(f'✗ {ip} not reachable')
except Exception as e:
print(f'Error testing network connectivity: {e}')
"
fi
}
# Función para mostrar ayuda
show_help() {
cat << EOF
Cisco MAC Monitor - Contenedor Docker
USAGE:
docker run cisco-mac-monitor [COMMAND] [OPTIONS]
COMMANDS:
continuous Ejecutar monitorización continua (por defecto)
single-run Ejecutar una sola vez y salir
test-config Validar archivo de configuración
test-ssh Probar conexiones SSH a dispositivos
shell Abrir shell interactivo
help Mostrar esta ayuda
VARIABLES DE ENTORNO:
CONFIG_FILE Ruta al archivo de configuración (default: /config/config.yaml)
LOG_LEVEL Nivel de logging (default: INFO)
MONITORING_INTERVAL Intervalo de monitorización en minutos (default: 5)
VOLÚMENES:
/config Directorio de configuración (contiene config.yaml)
/logs Directorio de logs
/data Directorio de datos de salida
EJEMPLOS:
# Ejecutar monitorización continua
docker run -v /host/config:/config cisco-mac-monitor
# Ejecutar una sola vez
docker run -v /host/config:/config cisco-mac-monitor single-run
# Probar configuración
docker run -v /host/config:/config cisco-mac-monitor test-config
EOF
}
# Función para probar configuración
test_config() {
log "Validando configuración..."
if ! python3 -c "
import yaml
import sys
try:
with open('${CONFIG_FILE}', 'r') as f:
config = yaml.safe_load(f)
# Validar estructura básica
if 'devices' not in config:
print('ERROR: Sección devices no encontrada')
sys.exit(1)
if 'monitoring' not in config:
print('ERROR: Sección monitoring no encontrada')
sys.exit(1)
# Validar dispositivos
devices = config['devices']
if not devices:
print('ERROR: No hay dispositivos configurados')
sys.exit(1)
print(f'✓ Configuración válida: {len(devices)} dispositivos')
for i, device in enumerate(devices):
hostname = device.get('hostname', f'device-{i}')
ip = device.get('ip')
username = device.get('username')
if not ip:
print(f'ERROR: Device {hostname} sin IP')
sys.exit(1)
if not username:
print(f'ERROR: Device {hostname} sin username')
sys.exit(1)
print(f' - {hostname} ({ip})')
except Exception as e:
print(f'ERROR: {e}')
sys.exit(1)
"; then
error "Configuración inválida"
exit 1
fi
success "Configuración válida"
}
# Función para probar conexiones SSH
test_ssh() {
log "Probando conexiones SSH a dispositivos..."
python3 << EOF
import yaml
import paramiko
import sys
try:
with open('${CONFIG_FILE}', 'r') as f:
config = yaml.safe_load(f)
devices = config.get('devices', [])
success_count = 0
for device in devices:
hostname = device.get('hostname', 'unknown')
ip = device.get('ip')
username = device.get('username')
password = device.get('password')
port = device.get('port', 22)
print(f'Probando {hostname} ({ip})...', end=' ')
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
hostname=ip,
port=port,
username=username,
password=password,
timeout=10,
look_for_keys=False,
allow_agent=False
)
ssh.close()
print('✓ CONECTADO')
success_count += 1
except Exception as e:
print(f'✗ ERROR: {e}')
print(f'\\nResultado: {success_count}/{len(devices)} dispositivos conectados')
if success_count == 0:
sys.exit(1)
except Exception as e:
print(f'ERROR: {e}')
sys.exit(1)
EOF
}
# Función principal
main() {
log "Iniciando Cisco MAC Monitor..."
# Configurar directorios
setup_directories
# Verificar configuración
check_config
# Procesar comando
COMMAND=${1:-continuous}
case "${COMMAND}" in
"continuous")
log "Modo: Monitorización continua"
test_config
warn "Iniciando monitorización continua - Presiona Ctrl+C para detener"
exec python3 /app/cisco_mac_monitor.py --config "${CONFIG_FILE}"
;;
"single-run")
log "Modo: Ejecución única"
test_config
exec python3 /app/cisco_mac_monitor.py --config "${CONFIG_FILE}" --single-run --output "${DATA_DIR}/cisco_mac_data.json"
;;
"test-config")
test_config
success "Configuración OK"
;;
"test-ssh")
test_config
test_ssh
;;
"test-network")
test_network
;;
"shell")
log "Abriendo shell interactivo..."
exec /bin/bash
;;
"help"|"--help"|"-h")
show_help
;;
*)
error "Comando desconocido: ${COMMAND}"
show_help
exit 1
;;
esac
}
# Manejo de señales
trap 'log "Recibida señal de terminación, cerrando..."; exit 0' SIGTERM SIGINT
# Ejecutar función principal
main "$@"
-39
View File
@@ -1,39 +0,0 @@
{
"data": [
{
"{#DEVICE_HOSTNAME}": "SW-PB-001",
"{#PORT_NAME}": "Fa0/1",
"{#MAC_COUNT}": 2,
"{#VLANS}": "10,20",
"{#MAC_ADDRESSES}": "[{\"mac\": \"aa:bb:cc:dd:ee:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}, {\"mac\": \"aa:bb:cc:dd:ee:02\", \"vlan\": \"20\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
},
{
"{#DEVICE_HOSTNAME}": "SW-PB-001",
"{#PORT_NAME}": "Fa0/2",
"{#MAC_COUNT}": 1,
"{#VLANS}": "10",
"{#MAC_ADDRESSES}": "[{\"mac\": \"bb:cc:dd:ee:ff:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
},
{
"{#DEVICE_HOSTNAME}": "SW-P1-001",
"{#PORT_NAME}": "Gi0/1",
"{#MAC_COUNT}": 3,
"{#VLANS}": "10,30",
"{#MAC_ADDRESSES}": "[{\"mac\": \"cc:dd:ee:ff:00:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}, {\"mac\": \"cc:dd:ee:ff:00:02\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}, {\"mac\": \"cc:dd:ee:ff:00:03\", \"vlan\": \"30\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
},
{
"{#DEVICE_HOSTNAME}": "SW-CORE-001",
"{#PORT_NAME}": "Gi0/1",
"{#MAC_COUNT}": 5,
"{#VLANS}": "10,20,30",
"{#MAC_ADDRESSES}": "[{\"mac\": \"dd:ee:ff:00:11:01\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}, {\"mac\": \"dd:ee:ff:00:11:02\", \"vlan\": \"20\", \"type\": \"DYNAMIC\"}, {\"mac\": \"dd:ee:ff:00:11:03\", \"vlan\": \"30\", \"type\": \"DYNAMIC\"}, {\"mac\": \"dd:ee:ff:00:11:04\", \"vlan\": \"10\", \"type\": \"DYNAMIC\"}, {\"mac\": \"dd:ee:ff:00:11:05\", \"vlan\": \"20\", \"type\": \"DYNAMIC\"}]",
"{#LAST_UPDATE}": "2024-10-24T14:30:00.123456"
}
],
"timestamp": "2024-10-24T14:30:00.123456",
"total_devices": 3,
"total_ports": 4
}
-348
View File
@@ -1,348 +0,0 @@
#!/bin/bash
# Script de prueba para Cisco MAC Monitor
# ======================================
set -e
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Contadores
TESTS_PASSED=0
TESTS_FAILED=0
# Función para logging
log() {
echo -e "${BLUE}[TEST]${NC} $1"
}
success() {
echo -e "${GREEN}[PASS]${NC} $1"
((TESTS_PASSED++))
}
error() {
echo -e "${RED}[FAIL]${NC} $1"
((TESTS_FAILED++))
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
# Test 1: Verificar archivos necesarios
test_files() {
log "Verificando archivos necesarios..."
local files=(
"cisco_mac_monitor.py"
"config.yaml"
"requirements.txt"
"Dockerfile"
"docker-compose.yml"
"entrypoint.sh"
"README.md"
)
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
success "Archivo encontrado: $file"
else
error "Archivo faltante: $file"
fi
done
}
# Test 2: Validar sintaxis Python
test_python_syntax() {
log "Validando sintaxis de Python..."
if command -v python3 >/dev/null 2>&1; then
if python3 -m py_compile cisco_mac_monitor.py; then
success "Sintaxis de Python válida"
else
error "Error de sintaxis en Python"
fi
else
warn "Python3 no disponible para test de sintaxis"
fi
}
# Test 3: Validar archivo YAML
test_yaml_syntax() {
log "Validando sintaxis de YAML..."
if command -v python3 >/dev/null 2>&1; then
if python3 -c "
import yaml
try:
with open('config.yaml', 'r') as f:
yaml.safe_load(f)
print('YAML syntax valid')
except Exception as e:
print(f'YAML error: {e}')
exit(1)
"; then
success "Sintaxis de YAML válida"
else
error "Error de sintaxis en YAML"
fi
else
warn "Python3 no disponible para test de YAML"
fi
}
# Test 4: Validar Dockerfile
test_dockerfile() {
log "Validando Dockerfile..."
if command -v docker >/dev/null 2>&1; then
if docker build -t cisco-mac-monitor-test . >/dev/null 2>&1; then
success "Dockerfile válido - imagen construida"
# Limpiar imagen de test
docker rmi cisco-mac-monitor-test >/dev/null 2>&1 || true
else
error "Error en construcción de Dockerfile"
fi
else
warn "Docker no disponible para test de Dockerfile"
fi
}
# Test 5: Validar docker-compose
test_docker_compose() {
log "Validando docker-compose.yml..."
if command -v docker-compose >/dev/null 2>&1; then
if docker-compose config >/dev/null 2>&1; then
success "docker-compose.yml válido"
else
error "Error en docker-compose.yml"
fi
else
warn "docker-compose no disponible para test"
fi
}
# Test 6: Verificar dependencias Python
test_python_deps() {
log "Verificando dependencias de Python..."
if command -v pip3 >/dev/null 2>&1; then
# Crear entorno virtual temporal
local temp_venv=$(mktemp -d)
python3 -m venv "$temp_venv" >/dev/null 2>&1
if source "$temp_venv/bin/activate" && pip install -r requirements.txt >/dev/null 2>&1; then
success "Dependencias de Python instalables"
else
error "Error instalando dependencias de Python"
fi
# Limpiar entorno temporal
rm -rf "$temp_venv"
deactivate 2>/dev/null || true
else
warn "pip3 no disponible para test de dependencias"
fi
}
# Test 7: Validar permisos de entrypoint
test_entrypoint_permissions() {
log "Verificando permisos de entrypoint.sh..."
if [[ -x "entrypoint.sh" ]]; then
success "entrypoint.sh tiene permisos de ejecución"
else
warn "entrypoint.sh no tiene permisos de ejecución"
chmod +x entrypoint.sh
success "Permisos de ejecución agregados a entrypoint.sh"
fi
}
# Test 8: Validar estructura de configuración
test_config_structure() {
log "Validando estructura de configuración..."
if command -v python3 >/dev/null 2>&1; then
if python3 -c "
import yaml
try:
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# Verificar secciones requeridas
required_sections = ['devices', 'monitoring']
for section in required_sections:
if section not in config:
print(f'Missing section: {section}')
exit(1)
# Verificar estructura de devices
devices = config['devices']
if not isinstance(devices, list) or len(devices) == 0:
print('Devices section must be a non-empty list')
exit(1)
# Verificar campos requeridos en devices
required_device_fields = ['hostname', 'ip', 'username', 'password']
for i, device in enumerate(devices):
for field in required_device_fields:
if field not in device:
print(f'Device {i} missing field: {field}')
exit(1)
print('Configuration structure valid')
except Exception as e:
print(f'Configuration error: {e}')
exit(1)
"; then
success "Estructura de configuración válida"
else
error "Error en estructura de configuración"
fi
else
warn "Python3 no disponible para test de configuración"
fi
}
# Test 9: Validar JSON de ejemplo
test_example_json() {
log "Validando JSON de ejemplo..."
if [[ -f "example_output.json" ]]; then
if command -v python3 >/dev/null 2>&1; then
if python3 -m json.tool example_output.json >/dev/null 2>&1; then
success "JSON de ejemplo válido"
else
error "JSON de ejemplo inválido"
fi
else
warn "Python3 no disponible para validación de JSON"
fi
else
warn "Archivo example_output.json no encontrado"
fi
}
# Test 10: Verificar documentación
test_documentation() {
log "Verificando documentación..."
if [[ -f "README.md" ]]; then
local readme_size=$(wc -c < README.md)
if [[ $readme_size -gt 1000 ]]; then
success "README.md presente y completo"
else
warn "README.md muy pequeño (${readme_size} bytes)"
fi
else
error "README.md faltante"
fi
if [[ -f "zabbix_template_examples.md" ]]; then
success "Documentación de Zabbix presente"
else
warn "Documentación de Zabbix faltante"
fi
}
# Test de integración básica
test_integration() {
log "Ejecutando test de integración básica..."
if command -v docker >/dev/null 2>&1; then
# Construir imagen
if docker build -t cisco-mac-monitor-test . >/dev/null 2>&1; then
success "Imagen Docker construida correctamente"
# Test de configuración en contenedor
if docker run --rm cisco-mac-monitor-test test-config >/dev/null 2>&1; then
success "Test de configuración en contenedor exitoso"
else
error "Test de configuración en contenedor falló"
fi
# Limpiar
docker rmi cisco-mac-monitor-test >/dev/null 2>&1 || true
else
error "Error construyendo imagen para test de integración"
fi
else
warn "Docker no disponible para test de integración"
fi
}
# Función principal
main() {
echo "=========================================="
echo " Cisco MAC Monitor - Test Suite"
echo "=========================================="
echo
# Verificar que estamos en el directorio correcto
if [[ ! -f "cisco_mac_monitor.py" ]]; then
error "No se encontró cisco_mac_monitor.py. ¿Estás en el directorio correcto?"
exit 1
fi
# Ejecutar tests
test_files
test_python_syntax
test_yaml_syntax
test_config_structure
test_entrypoint_permissions
test_example_json
test_documentation
# Tests que requieren herramientas externas
if command -v docker >/dev/null 2>&1; then
test_dockerfile
test_integration
else
warn "Docker no disponible - saltando tests de Docker"
fi
if command -v docker-compose >/dev/null 2>&1; then
test_docker_compose
else
warn "docker-compose no disponible - saltando test"
fi
if command -v pip3 >/dev/null 2>&1; then
test_python_deps
else
warn "pip3 no disponible - saltando test de dependencias"
fi
# Resumen
echo
echo "=========================================="
echo " Resumen de Tests"
echo "=========================================="
echo -e "${GREEN}Tests exitosos: $TESTS_PASSED${NC}"
echo -e "${RED}Tests fallidos: $TESTS_FAILED${NC}"
echo
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "${GREEN}🎉 Todos los tests pasaron exitosamente!${NC}"
echo
echo "El proyecto está listo para usar:"
echo "1. Edita config.yaml con tus dispositivos"
echo "2. Ejecuta: docker-compose up --build -d"
echo "3. Verifica logs: docker-compose logs -f"
exit 0
else
echo -e "${RED}❌ Algunos tests fallaron. Revisa los errores arriba.${NC}"
exit 1
fi
}
# Ejecutar tests
main "$@"
-132
View File
@@ -1,132 +0,0 @@
#!/bin/bash
# Script de integración para Zabbix External Scripts
# ==================================================
#
# Este script debe colocarse en:
# /usr/lib/zabbix/externalscripts/cisco_mac_discovery.sh
#
# Y configurar permisos:
# chmod +x /usr/lib/zabbix/externalscripts/cisco_mac_discovery.sh
# chown zabbix:zabbix /usr/lib/zabbix/externalscripts/cisco_mac_discovery.sh
# Configuración
CONTAINER_NAME="cisco-mac-monitor"
DATA_FILE="/data/cisco_mac_data.json"
TIMEOUT=60
# Función de logging
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >&2
}
error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2
exit 1
}
# Verificar que el contenedor existe y está corriendo
check_container() {
if ! docker ps --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
error "Contenedor ${CONTAINER_NAME} no está corriendo"
fi
}
# Ejecutar discovery y obtener datos
run_discovery() {
log "Ejecutando discovery en contenedor ${CONTAINER_NAME}"
# Ejecutar single-run en el contenedor
if ! timeout ${TIMEOUT} docker exec ${CONTAINER_NAME} single-run >/dev/null 2>&1; then
error "Falló la ejecución de discovery (timeout: ${TIMEOUT}s)"
fi
# Verificar que el archivo de datos existe
if ! docker exec ${CONTAINER_NAME} test -f ${DATA_FILE}; then
error "Archivo de datos no encontrado: ${DATA_FILE}"
fi
# Obtener y validar JSON
local json_data
json_data=$(docker exec ${CONTAINER_NAME} cat ${DATA_FILE})
if [[ -z "${json_data}" ]]; then
error "Archivo de datos está vacío"
fi
# Validar formato JSON básico
if ! echo "${json_data}" | python3 -m json.tool >/dev/null 2>&1; then
error "Datos no están en formato JSON válido"
fi
# Retornar datos
echo "${json_data}"
}
# Función principal
main() {
case "${1:-discovery}" in
"discovery"|"")
check_container
run_discovery
;;
"status")
if docker ps --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
echo "Container ${CONTAINER_NAME} is running"
exit 0
else
echo "Container ${CONTAINER_NAME} is not running"
exit 1
fi
;;
"logs")
docker logs --tail 50 ${CONTAINER_NAME}
;;
"help"|"-h"|"--help")
cat << EOF
Cisco MAC Discovery - Zabbix Integration Script
USAGE:
$0 [COMMAND]
COMMANDS:
discovery Ejecutar discovery y retornar JSON (default)
status Verificar estado del contenedor
logs Mostrar logs recientes del contenedor
help Mostrar esta ayuda
EJEMPLOS:
# Usar en Zabbix Discovery Rule
cisco_mac_discovery.sh
# Verificar estado
cisco_mac_discovery.sh status
# Ver logs para troubleshooting
cisco_mac_discovery.sh logs
CONFIGURACIÓN ZABBIX:
Discovery Rule:
- Name: Cisco MAC Discovery
- Type: External check
- Key: cisco_mac_discovery.sh
- Update interval: 5m
EOF
;;
*)
error "Comando desconocido: ${1}. Usa 'help' para ver opciones disponibles."
;;
esac
}
# Verificar dependencias
if ! command -v docker >/dev/null 2>&1; then
error "Docker no está instalado o no está en PATH"
fi
if ! command -v python3 >/dev/null 2>&1; then
error "Python3 no está instalado o no está en PATH"
fi
# Ejecutar función principal
main "$@"
-308
View File
@@ -1,308 +0,0 @@
# Plantilla de Zabbix para Cisco MAC Monitor
# ==========================================
## Discovery Rule
```xml
<discovery_rule>
<name>Cisco MAC Discovery</name>
<type>EXTERNAL</type>
<key>cisco_mac_discovery.sh</key>
<delay>5m</delay>
<description>Descubre puertos de switches Cisco con direcciones MAC conectadas</description>
<item_prototypes>
<item_prototype>
<name>MAC Count on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<type>CALCULATED</type>
<key>cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}]</key>
<delay>0</delay>
<value_type>UNSIGNED</value_type>
<params>{#MAC_COUNT}</params>
<description>Número de direcciones MAC en el puerto</description>
</item_prototype>
<item_prototype>
<name>MAC Addresses on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<type>CALCULATED</type>
<key>cisco.mac.addresses[{#DEVICE_HOSTNAME},{#PORT_NAME}]</key>
<delay>0</delay>
<value_type>TEXT</value_type>
<params>{#MAC_ADDRESSES}</params>
<description>Lista de direcciones MAC en formato JSON</description>
</item_prototype>
<item_prototype>
<name>VLANs on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<type>CALCULATED</type>
<key>cisco.mac.vlans[{#DEVICE_HOSTNAME},{#PORT_NAME}]</key>
<delay>0</delay>
<value_type>TEXT</value_type>
<params>{#VLANS}</params>
<description>VLANs presentes en el puerto</description>
</item_prototype>
<item_prototype>
<name>Last Update {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<type>CALCULATED</type>
<key>cisco.mac.lastupdate[{#DEVICE_HOSTNAME},{#PORT_NAME}]</key>
<delay>0</delay>
<value_type>TEXT</value_type>
<params>{#LAST_UPDATE}</params>
<description>Timestamp de última actualización</description>
</item_prototype>
</item_prototypes>
<trigger_prototypes>
<trigger_prototype>
<expression>{Template Cisco MAC:cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}].change()}&gt;0</expression>
<name>MAC count changed on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<priority>INFO</priority>
<description>El número de direcciones MAC en el puerto ha cambiado</description>
</trigger_prototype>
<trigger_prototype>
<expression>{Template Cisco MAC:cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}].last()}&gt;5</expression>
<name>Too many MACs on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<priority>WARNING</priority>
<description>Demasiadas direcciones MAC en un solo puerto (posible loop o hub)</description>
</trigger_prototype>
<trigger_prototype>
<expression>{Template Cisco MAC:cisco.mac.addresses[{#DEVICE_HOSTNAME},{#PORT_NAME}].change()}&gt;0</expression>
<name>MAC addresses changed on {#DEVICE_HOSTNAME} port {#PORT_NAME}</name>
<priority>INFO</priority>
<description>Las direcciones MAC en el puerto han cambiado</description>
</trigger_prototype>
</trigger_prototypes>
<graph_prototypes>
<graph_prototype>
<name>MAC Count - {#DEVICE_HOSTNAME} {#PORT_NAME}</name>
<graph_items>
<graph_item>
<item>cisco.mac.count[{#DEVICE_HOSTNAME},{#PORT_NAME}]</item>
</graph_item>
</graph_items>
</graph_prototype>
</graph_prototypes>
</discovery_rule>
```
## Items Adicionales (nivel de host)
```xml
<!-- Item para estadísticas generales -->
<item>
<name>Cisco MAC Monitor - Total Devices</name>
<type>DEPENDENT</type>
<key>cisco.mac.total_devices</key>
<master_item>cisco_mac_discovery.sh</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$.total_devices</params>
</step>
</preprocessing>
<description>Número total de dispositivos monitorizados</description>
</item>
<item>
<name>Cisco MAC Monitor - Total Ports</name>
<type>DEPENDENT</type>
<key>cisco.mac.total_ports</key>
<master_item>cisco_mac_discovery.sh</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$.total_ports</params>
</step>
</preprocessing>
<description>Número total de puertos con MACs</description>
</item>
<item>
<name>Cisco MAC Monitor - Last Timestamp</name>
<type>DEPENDENT</type>
<key>cisco.mac.timestamp</key>
<master_item>cisco_mac_discovery.sh</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$.timestamp</params>
</step>
</preprocessing>
<description>Timestamp de la última ejecución</description>
</item>
```
## Triggers de Host
```xml
<trigger>
<expression>{Template Cisco MAC:cisco.mac.timestamp.nodata(15m)}&gt;0</expression>
<name>Cisco MAC Monitor - No data received</name>
<priority>WARNING</priority>
<description>No se han recibido datos del monitor MAC en los últimos 15 minutos</description>
</trigger>
<trigger>
<expression>{Template Cisco MAC:cisco.mac.total_devices.last()}&lt;1</expression>
<name>Cisco MAC Monitor - No devices monitored</name>
<priority>HIGH</priority>
<description>No hay dispositivos siendo monitorizados</description>
</trigger>
```
## Macros de Template
```xml
<macros>
<macro>
<name>{$CISCO.MAC.MAX_PER_PORT}</name>
<value>5</value>
<description>Máximo número de MACs permitidas por puerto</description>
</macro>
<macro>
<name>{$CISCO.MAC.DATA_TIMEOUT}</name>
<value>15m</value>
<description>Timeout para considerar datos obsoletos</description>
</macro>
<macro>
<name>{$CISCO.MAC.DISCOVERY_INTERVAL}</name>
<value>5m</value>
<description>Intervalo de discovery</description>
</macro>
</macros>
```
## Scripts de Actions
### Script para notificaciones con detalles de MACs
```bash
#!/bin/bash
# Script para enviar detalles de cambios MAC
DEVICE="{ALERT.SUBJECT}"
PORT="{ITEM.KEY1}"
MAC_DATA="{ITEM.VALUE}"
# Formatear mensaje
MESSAGE="
Cambio detectado en tabla MAC:
Dispositivo: ${DEVICE}
Puerto: ${PORT}
Timestamp: $(date)
Direcciones MAC actuales:
${MAC_DATA}
"
# Enviar notificación (ejemplo con email)
echo "${MESSAGE}" | mail -s "Alerta MAC - ${DEVICE}" admin@empresa.com
```
### Script para análisis de MACs no autorizadas
```python
#!/usr/bin/env python3
import json
import sys
# Cargar lista de MACs autorizadas
authorized_macs = {
"aa:bb:cc:dd:ee:01": "Servidor Principal",
"aa:bb:cc:dd:ee:02": "Workstation Admin",
# ... más MACs autorizadas
}
# Obtener datos del item
mac_data = sys.argv[1] if len(sys.argv) > 1 else "{}"
try:
macs = json.loads(mac_data)
unauthorized = []
for mac_entry in macs:
mac = mac_entry.get('mac')
if mac and mac not in authorized_macs:
unauthorized.append({
'mac': mac,
'vlan': mac_entry.get('vlan'),
'type': mac_entry.get('type')
})
if unauthorized:
print(f"MACs no autorizadas detectadas: {len(unauthorized)}")
for mac in unauthorized:
print(f" - {mac['mac']} (VLAN {mac['vlan']})")
else:
print("Todas las MACs están autorizadas")
except Exception as e:
print(f"Error procesando datos MAC: {e}")
```
## Configuración de Web Scenarios
```xml
<web_scenario>
<name>Cisco MAC Monitor Container Health</name>
<delay>5m</delay>
<steps>
<step>
<name>Check Container Status</name>
<url>http://localhost:8080/health</url>
<status_codes>200</status_codes>
<required>OK</required>
</step>
</steps>
</web_scenario>
```
## Dashboard Widget
```json
{
"type": "graph",
"name": "Cisco MAC Activity",
"fields": {
"source_type": "1",
"itemid": {
"cisco.mac.total_ports": "Total Ports with MACs"
}
}
}
```
## Media Types para Alertas
### Webhook para Slack
```json
{
"name": "Cisco MAC Slack Alert",
"type": "webhook",
"parameters": {
"url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"channel": "#network-alerts",
"message": "🚨 Cisco MAC Alert\nDevice: {ALERT.SUBJECT}\nMessage: {ALERT.MESSAGE}\nTime: {DATE} {TIME}"
}
}
```
### Webhook para Teams
```json
{
"name": "Cisco MAC Teams Alert",
"type": "webhook",
"parameters": {
"url": "https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK",
"title": "Cisco MAC Monitor Alert",
"text": "Device: {ALERT.SUBJECT}\\nStatus: {TRIGGER.STATUS}\\nMessage: {ALERT.MESSAGE}"
}
}
```
Regular → Executable
View File
Regular → Executable
View File