vamp-log-analyzer 2.2__tar.gz → 2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vamp-log-analyzer
3
- Version: 2.2
3
+ Version: 2.3
4
4
  Summary: Log analysis and threat correlation engine for authorized security reviews
5
5
  Author-email: VampSecure Studios <contact@vampsecurestudios.com>
6
6
  License: AGPL-3.0-only
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
  <p align="center">
27
27
  <img src="https://img.shields.io/badge/python-3.9%2B-blue?logo=python&logoColor=white" alt="Python 3.9+"/>
28
28
  <img src="https://img.shields.io/badge/stdlib%20only-no%20deps-brightgreen" alt="stdlib only"/>
29
- <img src="https://img.shields.io/badge/version-2.2-orange" alt="v2.2"/>
29
+ <img src="https://img.shields.io/badge/version-2.3-orange" alt="v2.3"/>
30
30
  <img src="https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey" alt="Platform"/>
31
31
  <img src="https://img.shields.io/badge/license-AGPL--3.0-green" alt="License AGPL-3.0"/>
32
32
  <img src="https://img.shields.io/badge/VampSecure-Labs-magenta" alt="VampSecure Labs"/>
@@ -77,6 +77,7 @@ La detección del tipo de log es automática (`--type auto`, predeterminada).
77
77
  - **[v2.1] NarrativeGenerator** — genera narrativa forense en español vía Ollama (local) o Claude API (cloud)
78
78
  - **[v2.1] StreamingAnalyzer** — monitorización en tiempo real de logs en crecimiento (modo `tail -f` con detección live)
79
79
  - **[v2.2] Motor de reglas Sigma** — carga y evalúa reglas Sigma (YAML) sobre el stream de eventos; `--sigma-rules FICHERO_O_DIR` acepta un fichero `.yml` o un directorio de reglas; genera hallazgos `SIGMA-*` con el id/título/nivel de la regla original; campo-map configurable para adaptar logs no estándar
80
+ - **[v2.3] Vigilancia Wazuh API en tiempo real** — `--watch-wazuh-api HOST[:PUERTO]` conecta a la API REST Wazuh (JWT), sondea `/alerts` periódicamente y escribe alertas en NDJSON (`--wazuh-output`); cierra el loop purple de VampPurple (P7) sin ingesta manual
80
81
 
81
82
  ## Requisitos
82
83
 
@@ -512,6 +513,7 @@ VampSecure Studios no asume responsabilidad por el uso no autorizado de esta her
512
513
 
513
514
  | Versión | Cambios principales |
514
515
  |---------|---------------------|
516
+ | v2.3 | Vigilancia Wazuh API en tiempo real — `--watch-wazuh-api`, JWT, NDJSON, VampPurple P7 |
515
517
  | v2.2 | Motor de reglas Sigma — `--sigma-rules`, SigmaLoader + SigmaEngine, hallazgos `SIGMA-*` |
516
518
  | v2.1 | ScopeConfig, BaselineProfiler, STIX 2.1, EvidencePackager, NarrativeGenerator, StreamingAnalyzer |
517
519
  | v2.0 | 25 detectores FORA, multi-fuente, enriquecimiento GeoIP, MITRE ATT&CK |
@@ -4,7 +4,7 @@
4
4
  <p align="center">
5
5
  <img src="https://img.shields.io/badge/python-3.9%2B-blue?logo=python&logoColor=white" alt="Python 3.9+"/>
6
6
  <img src="https://img.shields.io/badge/stdlib%20only-no%20deps-brightgreen" alt="stdlib only"/>
7
- <img src="https://img.shields.io/badge/version-2.2-orange" alt="v2.2"/>
7
+ <img src="https://img.shields.io/badge/version-2.3-orange" alt="v2.3"/>
8
8
  <img src="https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey" alt="Platform"/>
9
9
  <img src="https://img.shields.io/badge/license-AGPL--3.0-green" alt="License AGPL-3.0"/>
10
10
  <img src="https://img.shields.io/badge/VampSecure-Labs-magenta" alt="VampSecure Labs"/>
@@ -55,6 +55,7 @@ La detección del tipo de log es automática (`--type auto`, predeterminada).
55
55
  - **[v2.1] NarrativeGenerator** — genera narrativa forense en español vía Ollama (local) o Claude API (cloud)
56
56
  - **[v2.1] StreamingAnalyzer** — monitorización en tiempo real de logs en crecimiento (modo `tail -f` con detección live)
57
57
  - **[v2.2] Motor de reglas Sigma** — carga y evalúa reglas Sigma (YAML) sobre el stream de eventos; `--sigma-rules FICHERO_O_DIR` acepta un fichero `.yml` o un directorio de reglas; genera hallazgos `SIGMA-*` con el id/título/nivel de la regla original; campo-map configurable para adaptar logs no estándar
58
+ - **[v2.3] Vigilancia Wazuh API en tiempo real** — `--watch-wazuh-api HOST[:PUERTO]` conecta a la API REST Wazuh (JWT), sondea `/alerts` periódicamente y escribe alertas en NDJSON (`--wazuh-output`); cierra el loop purple de VampPurple (P7) sin ingesta manual
58
59
 
59
60
  ## Requisitos
60
61
 
@@ -490,6 +491,7 @@ VampSecure Studios no asume responsabilidad por el uso no autorizado de esta her
490
491
 
491
492
  | Versión | Cambios principales |
492
493
  |---------|---------------------|
494
+ | v2.3 | Vigilancia Wazuh API en tiempo real — `--watch-wazuh-api`, JWT, NDJSON, VampPurple P7 |
493
495
  | v2.2 | Motor de reglas Sigma — `--sigma-rules`, SigmaLoader + SigmaEngine, hallazgos `SIGMA-*` |
494
496
  | v2.1 | ScopeConfig, BaselineProfiler, STIX 2.1, EvidencePackager, NarrativeGenerator, StreamingAnalyzer |
495
497
  | v2.0 | 25 detectores FORA, multi-fuente, enriquecimiento GeoIP, MITRE ATT&CK |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vamp-log-analyzer"
7
- version = "2.2"
7
+ version = "2.3"
8
8
  description = "Log analysis and threat correlation engine for authorized security reviews"
9
9
  readme = "README.md"
10
10
  license = { text = "AGPL-3.0-only" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vamp-log-analyzer
3
- Version: 2.2
3
+ Version: 2.3
4
4
  Summary: Log analysis and threat correlation engine for authorized security reviews
5
5
  Author-email: VampSecure Studios <contact@vampsecurestudios.com>
6
6
  License: AGPL-3.0-only
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
  <p align="center">
27
27
  <img src="https://img.shields.io/badge/python-3.9%2B-blue?logo=python&logoColor=white" alt="Python 3.9+"/>
28
28
  <img src="https://img.shields.io/badge/stdlib%20only-no%20deps-brightgreen" alt="stdlib only"/>
29
- <img src="https://img.shields.io/badge/version-2.2-orange" alt="v2.2"/>
29
+ <img src="https://img.shields.io/badge/version-2.3-orange" alt="v2.3"/>
30
30
  <img src="https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey" alt="Platform"/>
31
31
  <img src="https://img.shields.io/badge/license-AGPL--3.0-green" alt="License AGPL-3.0"/>
32
32
  <img src="https://img.shields.io/badge/VampSecure-Labs-magenta" alt="VampSecure Labs"/>
@@ -77,6 +77,7 @@ La detección del tipo de log es automática (`--type auto`, predeterminada).
77
77
  - **[v2.1] NarrativeGenerator** — genera narrativa forense en español vía Ollama (local) o Claude API (cloud)
78
78
  - **[v2.1] StreamingAnalyzer** — monitorización en tiempo real de logs en crecimiento (modo `tail -f` con detección live)
79
79
  - **[v2.2] Motor de reglas Sigma** — carga y evalúa reglas Sigma (YAML) sobre el stream de eventos; `--sigma-rules FICHERO_O_DIR` acepta un fichero `.yml` o un directorio de reglas; genera hallazgos `SIGMA-*` con el id/título/nivel de la regla original; campo-map configurable para adaptar logs no estándar
80
+ - **[v2.3] Vigilancia Wazuh API en tiempo real** — `--watch-wazuh-api HOST[:PUERTO]` conecta a la API REST Wazuh (JWT), sondea `/alerts` periódicamente y escribe alertas en NDJSON (`--wazuh-output`); cierra el loop purple de VampPurple (P7) sin ingesta manual
80
81
 
81
82
  ## Requisitos
82
83
 
@@ -512,6 +513,7 @@ VampSecure Studios no asume responsabilidad por el uso no autorizado de esta her
512
513
 
513
514
  | Versión | Cambios principales |
514
515
  |---------|---------------------|
516
+ | v2.3 | Vigilancia Wazuh API en tiempo real — `--watch-wazuh-api`, JWT, NDJSON, VampPurple P7 |
515
517
  | v2.2 | Motor de reglas Sigma — `--sigma-rules`, SigmaLoader + SigmaEngine, hallazgos `SIGMA-*` |
516
518
  | v2.1 | ScopeConfig, BaselineProfiler, STIX 2.1, EvidencePackager, NarrativeGenerator, StreamingAnalyzer |
517
519
  | v2.0 | 25 detectores FORA, multi-fuente, enriquecimiento GeoIP, MITRE ATT&CK |
@@ -95,7 +95,10 @@ import sys
95
95
  import tempfile
96
96
  import textwrap
97
97
  import time
98
+ import base64
99
+ import ssl
98
100
  import urllib.error
101
+ import urllib.parse
99
102
  import urllib.request
100
103
  import uuid
101
104
  import xml.etree.ElementTree as ET
@@ -112,7 +115,7 @@ except ImportError:
112
115
  # VERSIÓN Y METADATOS
113
116
  # ============================================================
114
117
 
115
- VERSION = "2.2"
118
+ VERSION = "2.3"
116
119
  TOOL = "vamp-log-analyzer"
117
120
  FINDING_PREFIX = "FORA"
118
121
 
@@ -4352,6 +4355,155 @@ def print_summary(report: Report) -> None:
4352
4355
  print(f"{'═' * 60}\n")
4353
4356
 
4354
4357
 
4358
+ # ---------------------------------------------------------------------------
4359
+ # Modo --watch-wazuh-api (VampPurple P7 — loop purple sin ingesta manual)
4360
+ # ---------------------------------------------------------------------------
4361
+
4362
+ def _wazuh_api_request(
4363
+ base_url: str,
4364
+ path: str,
4365
+ token: str,
4366
+ params: Optional[dict] = None,
4367
+ verify_ssl: bool = True,
4368
+ ) -> dict:
4369
+ """Realiza una petición GET autenticada a la API REST de Wazuh."""
4370
+ url = base_url.rstrip("/") + path
4371
+ if params:
4372
+ url += "?" + urllib.parse.urlencode(params)
4373
+ ctx = ssl.create_default_context() if verify_ssl else ssl.create_default_context()
4374
+ if not verify_ssl:
4375
+ ctx.check_hostname = False
4376
+ ctx.verify_mode = ssl.CERT_NONE
4377
+ req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
4378
+ with urllib.request.urlopen(req, context=ctx, timeout=15) as resp:
4379
+ return json.loads(resp.read().decode())
4380
+
4381
+
4382
+ def _wazuh_api_auth(base_url: str, user: str, password: str, verify_ssl: bool) -> str:
4383
+ """Autentica contra la API Wazuh y devuelve el JWT token."""
4384
+ url = base_url.rstrip("/") + "/security/user/authenticate"
4385
+ creds = base64.b64encode(f"{user}:{password}".encode()).decode()
4386
+ ctx = ssl.create_default_context() if verify_ssl else ssl.create_default_context()
4387
+ if not verify_ssl:
4388
+ ctx.check_hostname = False
4389
+ ctx.verify_mode = ssl.CERT_NONE
4390
+ req = urllib.request.Request(
4391
+ url, method="POST",
4392
+ headers={"Authorization": f"Basic {creds}"},
4393
+ )
4394
+ try:
4395
+ with urllib.request.urlopen(req, context=ctx, timeout=15) as resp:
4396
+ data = json.loads(resp.read().decode())
4397
+ return data["data"]["token"]
4398
+ except urllib.error.HTTPError as e:
4399
+ body = e.read().decode(errors="replace")
4400
+ raise RuntimeError(f"Autenticación Wazuh fallida ({e.code}): {body}") from e
4401
+
4402
+
4403
+ def _modo_watch_wazuh_api(args: argparse.Namespace) -> int:
4404
+ """
4405
+ Modo de vigilancia en tiempo real contra la API REST de Wazuh.
4406
+
4407
+ Conecta a la API Wazuh (puerto 55000), obtiene un JWT, y sondea
4408
+ el endpoint /alerts periódicamente para recuperar alertas nuevas.
4409
+ Las alertas se muestran en consola y se escriben en NDJSON si se
4410
+ especifica --wazuh-output, listas para ser consumidas por VampPurple.
4411
+
4412
+ Ctrl+C para detener el bucle.
4413
+ """
4414
+ console = Console()
4415
+ base_url = args.watch_wazuh_api.rstrip("/")
4416
+ if not base_url.startswith("http"):
4417
+ base_url = "https://" + base_url
4418
+ user = getattr(args, "wazuh_user", None) or os.environ.get("WAZUH_USER", "wazuh")
4419
+ password = getattr(args, "wazuh_password", None) or os.environ.get("WAZUH_PASSWORD", "")
4420
+ if not password:
4421
+ import getpass
4422
+ password = getpass.getpass(f" Contraseña Wazuh [{user}]: ")
4423
+ intervalo = int(getattr(args, "wazuh_interval", 30) or 30)
4424
+ min_level = int(getattr(args, "wazuh_min_level", 7) or 7)
4425
+ salida_path = getattr(args, "wazuh_output", None)
4426
+ verify_ssl = not getattr(args, "wazuh_no_verify", False)
4427
+
4428
+ console.print(Panel(
4429
+ f"[bold]API:[/bold] {base_url} · "
4430
+ f"[bold]Nivel mínimo:[/bold] {min_level} · "
4431
+ f"[bold]Intervalo:[/bold] {intervalo}s"
4432
+ + (f"\n[bold]Salida NDJSON:[/bold] {salida_path}" if salida_path else ""),
4433
+ title=f"[bold magenta]{TOOL} v{VERSION} — Modo watch-wazuh-api[/bold magenta]",
4434
+ border_style="magenta",
4435
+ ))
4436
+
4437
+ try:
4438
+ token = _wazuh_api_auth(base_url, user, password, verify_ssl)
4439
+ except Exception as exc:
4440
+ console.print(f"[bold red][!] {exc}[/bold red]")
4441
+ return 2
4442
+
4443
+ token_ts = time.time()
4444
+ TOKEN_TTL_S = 3600 - 60 # refrescar antes de la expiración del JWT (1h por defecto)
4445
+ ya_vistos: set = set()
4446
+ salida_fp = open(salida_path, "a", encoding="utf-8") if salida_path else None # noqa: WPS515
4447
+
4448
+ console.print(f"[green]✓ Autenticado. Sondeando cada {intervalo}s (Ctrl+C para salir)…[/green]")
4449
+
4450
+ try:
4451
+ while True:
4452
+ # Refrescar token si está cerca de expirar
4453
+ if time.time() - token_ts > TOKEN_TTL_S:
4454
+ try:
4455
+ token = _wazuh_api_auth(base_url, user, password, verify_ssl)
4456
+ token_ts = time.time()
4457
+ console.print("[dim]→ Token JWT renovado[/dim]")
4458
+ except Exception as e:
4459
+ console.print(f"[yellow][!] No se pudo renovar el token: {e}[/yellow]")
4460
+
4461
+ try:
4462
+ resp = _wazuh_api_request(
4463
+ base_url, "/alerts",
4464
+ token,
4465
+ params={"q": f"rule.level>={min_level}", "sort": "-timestamp", "limit": 100},
4466
+ verify_ssl=verify_ssl,
4467
+ )
4468
+ except Exception as e:
4469
+ console.print(f"[yellow][!] Error al consultar /alerts: {e}[/yellow]")
4470
+ time.sleep(intervalo)
4471
+ continue
4472
+
4473
+ alertas = (resp.get("data") or {}).get("affected_items") or []
4474
+ nuevas = [a for a in alertas if a.get("id") not in ya_vistos]
4475
+
4476
+ for alerta in reversed(nuevas): # cronológico
4477
+ a_id = alerta.get("id", "")
4478
+ ya_vistos.add(a_id)
4479
+ regla = alerta.get("rule", {})
4480
+ nivel = regla.get("level", 0)
4481
+ descr = regla.get("description", "")
4482
+ agente = alerta.get("agent", {}).get("name", "?")
4483
+ timestamp = alerta.get("timestamp", "")
4484
+ color = "red" if nivel >= 12 else ("yellow" if nivel >= 7 else "dim")
4485
+ console.print(
4486
+ f"[{color}][Nivel {nivel:>2}][/{color}] "
4487
+ f"[bold]{agente}[/bold] {timestamp[:19]} {descr}"
4488
+ )
4489
+ if salida_fp:
4490
+ salida_fp.write(json.dumps(alerta, ensure_ascii=False) + "\n")
4491
+ salida_fp.flush()
4492
+
4493
+ if nuevas:
4494
+ console.print(f"[dim] → {len(nuevas)} alerta(s) nueva(s). Total vistas: {len(ya_vistos)}[/dim]")
4495
+
4496
+ time.sleep(intervalo)
4497
+
4498
+ except KeyboardInterrupt:
4499
+ console.print("\n[yellow]Detenido por el usuario.[/yellow]")
4500
+ finally:
4501
+ if salida_fp:
4502
+ salida_fp.close()
4503
+
4504
+ return 0
4505
+
4506
+
4355
4507
  def main() -> int:
4356
4508
  print_banner()
4357
4509
  parser = argparse.ArgumentParser(
@@ -4437,6 +4589,23 @@ def main() -> int:
4437
4589
  parser.add_argument("--follow", metavar="FICHERO",
4438
4590
  help="Modo streaming: monitorizar fichero en tiempo real (tail -f)")
4439
4591
 
4592
+ # Modo --watch-wazuh-api (VampPurple P7)
4593
+ parser.add_argument("--watch-wazuh-api", metavar="HOST[:PUERTO]", dest="watch_wazuh_api",
4594
+ help="Vigilancia en tiempo real vía API REST Wazuh (puerto 55000). "
4595
+ "Ejemplo: --watch-wazuh-api https://wazuh.empresa.local:55000")
4596
+ parser.add_argument("--wazuh-user", metavar="USUARIO", dest="wazuh_user", default="",
4597
+ help="Usuario API Wazuh (default: var. entorno WAZUH_USER o 'wazuh')")
4598
+ parser.add_argument("--wazuh-password", metavar="CLAVE", dest="wazuh_password", default="",
4599
+ help="Contraseña API Wazuh (default: var. entorno WAZUH_PASSWORD o prompt)")
4600
+ parser.add_argument("--wazuh-interval", metavar="S", dest="wazuh_interval", type=int, default=30,
4601
+ help="Intervalo de sondeo en segundos (default: 30)")
4602
+ parser.add_argument("--wazuh-min-level", metavar="N", dest="wazuh_min_level", type=int, default=7,
4603
+ help="Nivel mínimo de regla Wazuh a reportar (default: 7)")
4604
+ parser.add_argument("--wazuh-output", metavar="FICHERO", dest="wazuh_output",
4605
+ help="Fichero NDJSON de salida para ingesta VampPurple (append)")
4606
+ parser.add_argument("--wazuh-no-verify", action="store_true", dest="wazuh_no_verify",
4607
+ help="Deshabilitar verificación TLS del servidor Wazuh (no recomendado)")
4608
+
4440
4609
  args = parser.parse_args()
4441
4610
 
4442
4611
  # --- Modo --scope-example ---
@@ -4445,10 +4614,16 @@ def main() -> int:
4445
4614
  return 0
4446
4615
 
4447
4616
  # --- Sin argumentos ---
4448
- if not args.paths and not getattr(args, "follow", None):
4617
+ if (not args.paths
4618
+ and not getattr(args, "follow", None)
4619
+ and not getattr(args, "watch_wazuh_api", None)):
4449
4620
  parser.print_help()
4450
4621
  return 0
4451
4622
 
4623
+ # --- Modo --watch-wazuh-api (VampPurple P7) ---
4624
+ if getattr(args, "watch_wazuh_api", None):
4625
+ return _modo_watch_wazuh_api(args)
4626
+
4452
4627
  # --- Modo --follow (streaming, no necesita paths obligatorios) ---
4453
4628
  if getattr(args, "follow", None):
4454
4629
  scope: Optional[ScopeConfig] = None
File without changes