vamp-secrets-scanner 2.2__tar.gz → 2.4__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.
- {vamp_secrets_scanner-2.2/vamp_secrets_scanner.egg-info → vamp_secrets_scanner-2.4}/PKG-INFO +15 -2
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/README.md +13 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/pyproject.toml +2 -2
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4/vamp_secrets_scanner.egg-info}/PKG-INFO +15 -2
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.py +397 -2
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/LICENSE +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/setup.cfg +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/SOURCES.txt +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/dependency_links.txt +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/entry_points.txt +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/requires.txt +0 -0
- {vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/top_level.txt +0 -0
{vamp_secrets_scanner-2.2/vamp_secrets_scanner.egg-info → vamp_secrets_scanner-2.4}/PKG-INFO
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vamp-secrets-scanner
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: Static secrets and credentials scanner with git history analysis, Shannon entropy detection
|
|
3
|
+
Version: 2.4
|
|
4
|
+
Summary: Static secrets and credentials scanner with git history analysis, Shannon entropy detection, SARIF 2.1.0 output, Docker runtime scanning and Kubernetes Secrets scanning
|
|
5
5
|
Author-email: VampSecure Studios <contact@vampsecurestudios.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Vampsecure-Labs/vamp-secrets-scanner
|
|
@@ -26,6 +26,7 @@ License-File: LICENSE
|
|
|
26
26
|
Requires-Dist: rich>=13.7.0
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
29
|
+
<!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
|
|
29
30
|
<h1 align="center">vamp-secrets-scanner</h1>
|
|
30
31
|
<p align="center">
|
|
31
32
|
<strong>Static secrets and credential scanner with Git history analysis and SARIF export</strong><br>
|
|
@@ -77,6 +78,13 @@ Standard library only beyond `rich`: `re`, `os`, `math`, `pathlib`, `hashlib`, `
|
|
|
77
78
|
|
|
78
79
|
## Installation
|
|
79
80
|
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install vamp-secrets-scanner
|
|
84
|
+
# o con Homebrew:
|
|
85
|
+
brew install vampsecure-labs/labs/vamp-secrets-scanner
|
|
86
|
+
```
|
|
87
|
+
|
|
80
88
|
```bash
|
|
81
89
|
git clone https://github.com/belky-me/vamp-secrets-scanner.git
|
|
82
90
|
cd vamp-secrets-scanner
|
|
@@ -177,3 +185,8 @@ This tool is part of the **VampSecure Labs Security Toolkit** — a collection o
|
|
|
177
185
|
|
|
178
186
|
© VampSecure Studios — VampSecure Labs Security Research Division
|
|
179
187
|
For authorized security testing only.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Versión
|
|
192
|
+
v2.2 — VampSecure Labs Security Research Division
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
|
|
1
2
|
<h1 align="center">vamp-secrets-scanner</h1>
|
|
2
3
|
<p align="center">
|
|
3
4
|
<strong>Static secrets and credential scanner with Git history analysis and SARIF export</strong><br>
|
|
@@ -49,6 +50,13 @@ Standard library only beyond `rich`: `re`, `os`, `math`, `pathlib`, `hashlib`, `
|
|
|
49
50
|
|
|
50
51
|
## Installation
|
|
51
52
|
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install vamp-secrets-scanner
|
|
56
|
+
# o con Homebrew:
|
|
57
|
+
brew install vampsecure-labs/labs/vamp-secrets-scanner
|
|
58
|
+
```
|
|
59
|
+
|
|
52
60
|
```bash
|
|
53
61
|
git clone https://github.com/belky-me/vamp-secrets-scanner.git
|
|
54
62
|
cd vamp-secrets-scanner
|
|
@@ -149,3 +157,8 @@ This tool is part of the **VampSecure Labs Security Toolkit** — a collection o
|
|
|
149
157
|
|
|
150
158
|
© VampSecure Studios — VampSecure Labs Security Research Division
|
|
151
159
|
For authorized security testing only.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Versión
|
|
164
|
+
v2.2 — VampSecure Labs Security Research Division
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vamp-secrets-scanner"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "Static secrets and credentials scanner with git history analysis, Shannon entropy detection
|
|
7
|
+
version = "2.4"
|
|
8
|
+
description = "Static secrets and credentials scanner with git history analysis, Shannon entropy detection, SARIF 2.1.0 output, Docker runtime scanning and Kubernetes Secrets scanning"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.10"
|
{vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4/vamp_secrets_scanner.egg-info}/PKG-INFO
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vamp-secrets-scanner
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: Static secrets and credentials scanner with git history analysis, Shannon entropy detection
|
|
3
|
+
Version: 2.4
|
|
4
|
+
Summary: Static secrets and credentials scanner with git history analysis, Shannon entropy detection, SARIF 2.1.0 output, Docker runtime scanning and Kubernetes Secrets scanning
|
|
5
5
|
Author-email: VampSecure Studios <contact@vampsecurestudios.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Vampsecure-Labs/vamp-secrets-scanner
|
|
@@ -26,6 +26,7 @@ License-File: LICENSE
|
|
|
26
26
|
Requires-Dist: rich>=13.7.0
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
29
|
+
<!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
|
|
29
30
|
<h1 align="center">vamp-secrets-scanner</h1>
|
|
30
31
|
<p align="center">
|
|
31
32
|
<strong>Static secrets and credential scanner with Git history analysis and SARIF export</strong><br>
|
|
@@ -77,6 +78,13 @@ Standard library only beyond `rich`: `re`, `os`, `math`, `pathlib`, `hashlib`, `
|
|
|
77
78
|
|
|
78
79
|
## Installation
|
|
79
80
|
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install vamp-secrets-scanner
|
|
84
|
+
# o con Homebrew:
|
|
85
|
+
brew install vampsecure-labs/labs/vamp-secrets-scanner
|
|
86
|
+
```
|
|
87
|
+
|
|
80
88
|
```bash
|
|
81
89
|
git clone https://github.com/belky-me/vamp-secrets-scanner.git
|
|
82
90
|
cd vamp-secrets-scanner
|
|
@@ -177,3 +185,8 @@ This tool is part of the **VampSecure Labs Security Toolkit** — a collection o
|
|
|
177
185
|
|
|
178
186
|
© VampSecure Studios — VampSecure Labs Security Research Division
|
|
179
187
|
For authorized security testing only.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Versión
|
|
192
|
+
v2.2 — VampSecure Labs Security Research Division
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# © VampSecure Studios — VampSecure Labs Security Research Division
|
|
2
3
|
"""
|
|
3
4
|
vamp_secrets_scanner.py — Escáner Estático de Secretos y Credenciales
|
|
4
5
|
======================================================================
|
|
@@ -85,6 +86,7 @@ AUTORÍA
|
|
|
85
86
|
from __future__ import annotations
|
|
86
87
|
|
|
87
88
|
import argparse
|
|
89
|
+
import base64
|
|
88
90
|
import hashlib
|
|
89
91
|
import json
|
|
90
92
|
import math
|
|
@@ -109,7 +111,7 @@ from rich.text import Text
|
|
|
109
111
|
# Constantes
|
|
110
112
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
111
113
|
|
|
112
|
-
VERSION = "2.
|
|
114
|
+
VERSION = "2.4"
|
|
113
115
|
TOOL_NAME = "vamp-secrets-scanner"
|
|
114
116
|
|
|
115
117
|
BANNER = r"""
|
|
@@ -118,7 +120,7 @@ __ ___ __ __ ___ ___ ___ ___ _ _ ___ ___ _ _ ___ ___
|
|
|
118
120
|
\ V / _ \| |\/| | _/\__ \ _| (__| |_| | / _|| |__ / _ \| _ \__ \
|
|
119
121
|
\_/_/ \_\_| |_|_| |___/___\___|\___/|_|_\___|____/_/ \_\___/___/
|
|
120
122
|
by Antonio Hernandez "Belky" — VampSecure Studios
|
|
121
|
-
vamp-secrets-scanner v2.
|
|
123
|
+
vamp-secrets-scanner v2.4 · Static Secrets & Git History Scanner
|
|
122
124
|
────────────────────────────────────────────────────────────────────────
|
|
123
125
|
USO EXCLUSIVO EN AUDITORÍAS AUTORIZADAS · El uso no autorizado es ilegal
|
|
124
126
|
"""
|
|
@@ -1491,6 +1493,335 @@ function toggle(row){{
|
|
|
1491
1493
|
console.print(f"[dim] HTML → {path}[/]")
|
|
1492
1494
|
|
|
1493
1495
|
|
|
1496
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1497
|
+
# Escaneo de contenedores Docker en runtime (v2.3)
|
|
1498
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1499
|
+
|
|
1500
|
+
def _docker_disponible() -> bool:
|
|
1501
|
+
"""
|
|
1502
|
+
Comprueba si el binario docker está disponible en el PATH.
|
|
1503
|
+
Retorna True si está disponible, False en caso contrario.
|
|
1504
|
+
"""
|
|
1505
|
+
import shutil as _shutil
|
|
1506
|
+
return _shutil.which("docker") is not None
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
def _obtener_envs_contenedor(container_id: str) -> Optional[List[str]]:
|
|
1510
|
+
"""
|
|
1511
|
+
Obtiene las variables de entorno de un contenedor Docker.
|
|
1512
|
+
|
|
1513
|
+
Intenta primero con 'docker inspect' para leer la configuración estática;
|
|
1514
|
+
si no devuelve variables, recurre a 'docker exec env' como alternativa.
|
|
1515
|
+
|
|
1516
|
+
Parámetros
|
|
1517
|
+
----------
|
|
1518
|
+
container_id : nombre o ID del contenedor en ejecución
|
|
1519
|
+
|
|
1520
|
+
Retorna lista de strings con formato 'CLAVE=valor', o None si falla.
|
|
1521
|
+
"""
|
|
1522
|
+
envs: List[str] = []
|
|
1523
|
+
|
|
1524
|
+
# Método 1: docker inspect (más rápido, no requiere shell en el contenedor)
|
|
1525
|
+
try:
|
|
1526
|
+
result = subprocess.run(
|
|
1527
|
+
["docker", "inspect", "--format", "{{range .Config.Env}}{{println .}}{{end}}", container_id],
|
|
1528
|
+
capture_output=True,
|
|
1529
|
+
text=True,
|
|
1530
|
+
timeout=10,
|
|
1531
|
+
)
|
|
1532
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
1533
|
+
envs = [l for l in result.stdout.splitlines() if l.strip() and "=" in l]
|
|
1534
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
1535
|
+
pass
|
|
1536
|
+
|
|
1537
|
+
# Método 2: docker exec env (alternativa si inspect no devolvió variables)
|
|
1538
|
+
if not envs:
|
|
1539
|
+
try:
|
|
1540
|
+
result = subprocess.run(
|
|
1541
|
+
["docker", "exec", container_id, "env"],
|
|
1542
|
+
capture_output=True,
|
|
1543
|
+
text=True,
|
|
1544
|
+
timeout=10,
|
|
1545
|
+
)
|
|
1546
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
1547
|
+
envs = [l for l in result.stdout.splitlines() if l.strip() and "=" in l]
|
|
1548
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
1549
|
+
pass
|
|
1550
|
+
|
|
1551
|
+
return envs if envs else None
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
def scan_docker_container(container_id: str) -> List[Finding]:
|
|
1555
|
+
"""
|
|
1556
|
+
Escanea las variables de entorno de un contenedor Docker en ejecución.
|
|
1557
|
+
|
|
1558
|
+
Aplica los mismos 77 patrones de SECRET_PATTERNS del scanner estático
|
|
1559
|
+
a cada variable de entorno del contenedor. Los hallazgos se marcan
|
|
1560
|
+
como categoría 'Docker · Runtime Env' con severidad CRITICAL.
|
|
1561
|
+
|
|
1562
|
+
Parámetros
|
|
1563
|
+
----------
|
|
1564
|
+
container_id : nombre o ID del contenedor Docker a escanear
|
|
1565
|
+
|
|
1566
|
+
Retorna lista de Finding con los secretos encontrados en las envs del contenedor.
|
|
1567
|
+
"""
|
|
1568
|
+
if not _docker_disponible():
|
|
1569
|
+
console.print("[yellow] Aviso: 'docker' no encontrado en PATH — omitiendo escaneo de contenedor[/]")
|
|
1570
|
+
return []
|
|
1571
|
+
|
|
1572
|
+
console.print(f"[cyan] Escaneando contenedor Docker: {container_id}[/]")
|
|
1573
|
+
|
|
1574
|
+
envs = _obtener_envs_contenedor(container_id)
|
|
1575
|
+
if envs is None:
|
|
1576
|
+
console.print(f"[yellow] Aviso: no se pudieron obtener variables de entorno del contenedor '{container_id}'[/]")
|
|
1577
|
+
return []
|
|
1578
|
+
|
|
1579
|
+
hallazgos: List[Finding] = []
|
|
1580
|
+
|
|
1581
|
+
for linea in envs:
|
|
1582
|
+
if not linea.strip() or "=" not in linea:
|
|
1583
|
+
continue
|
|
1584
|
+
|
|
1585
|
+
# Escanear la línea de variable de entorno con los patrones habituales
|
|
1586
|
+
for pat in SECRET_PATTERNS:
|
|
1587
|
+
match = pat["compiled"].search(linea)
|
|
1588
|
+
if not match:
|
|
1589
|
+
continue
|
|
1590
|
+
|
|
1591
|
+
valor_raw = match.group(0)
|
|
1592
|
+
extracto = _censor(valor_raw)
|
|
1593
|
+
fp = _fingerprint(valor_raw, "DOCKER_ENV_SECRET")
|
|
1594
|
+
|
|
1595
|
+
# Nombre de la variable de entorno (parte antes del '=')
|
|
1596
|
+
nombre_var = linea.split("=", 1)[0]
|
|
1597
|
+
|
|
1598
|
+
hallazgos.append(Finding(
|
|
1599
|
+
file = f"docker://{container_id}",
|
|
1600
|
+
line_no = 0,
|
|
1601
|
+
pattern = f"DOCKER_ENV_SECRET: {pat['name']}",
|
|
1602
|
+
category = "Docker · Runtime Env",
|
|
1603
|
+
severity = Severity.CRITICAL,
|
|
1604
|
+
preview = extracto,
|
|
1605
|
+
context = [
|
|
1606
|
+
f" Contenedor: {container_id}",
|
|
1607
|
+
f" Variable: {nombre_var}",
|
|
1608
|
+
f" Patrón: {pat['name']}",
|
|
1609
|
+
f" Extracto: {extracto}",
|
|
1610
|
+
],
|
|
1611
|
+
fingerprint = fp,
|
|
1612
|
+
))
|
|
1613
|
+
|
|
1614
|
+
if hallazgos:
|
|
1615
|
+
console.print(
|
|
1616
|
+
f" [bold red]⚠ {len(hallazgos)} secreto(s) encontrado(s) en "
|
|
1617
|
+
f"variables de entorno de '{container_id}'[/]"
|
|
1618
|
+
)
|
|
1619
|
+
else:
|
|
1620
|
+
console.print(f" [green]✔ Sin secretos detectados en las envs de '{container_id}'[/]")
|
|
1621
|
+
|
|
1622
|
+
return hallazgos
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
def scan_all_docker_containers() -> List[Finding]:
|
|
1626
|
+
"""
|
|
1627
|
+
Escanea los contenedores Docker actualmente en ejecución.
|
|
1628
|
+
|
|
1629
|
+
Ejecuta 'docker ps -q' para obtener los IDs de todos los contenedores
|
|
1630
|
+
activos y aplica scan_docker_container() a cada uno.
|
|
1631
|
+
|
|
1632
|
+
Retorna la lista combinada de hallazgos de todos los contenedores.
|
|
1633
|
+
"""
|
|
1634
|
+
if not _docker_disponible():
|
|
1635
|
+
console.print("[yellow] Aviso: 'docker' no encontrado en PATH — omitiendo escaneo de contenedores[/]")
|
|
1636
|
+
return []
|
|
1637
|
+
|
|
1638
|
+
# Obtener lista de IDs de contenedores en ejecución
|
|
1639
|
+
try:
|
|
1640
|
+
result = subprocess.run(
|
|
1641
|
+
["docker", "ps", "-q"],
|
|
1642
|
+
capture_output=True,
|
|
1643
|
+
text=True,
|
|
1644
|
+
timeout=10,
|
|
1645
|
+
)
|
|
1646
|
+
ids = [i.strip() for i in result.stdout.splitlines() if i.strip()]
|
|
1647
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as exc:
|
|
1648
|
+
console.print(f"[red] Error ejecutando 'docker ps': {exc}[/]")
|
|
1649
|
+
return []
|
|
1650
|
+
|
|
1651
|
+
if not ids:
|
|
1652
|
+
console.print("[yellow] No hay contenedores Docker en ejecución.[/]")
|
|
1653
|
+
return []
|
|
1654
|
+
|
|
1655
|
+
console.print(f"[cyan] Escaneando {len(ids)} contenedor(es) Docker en ejecución...[/]")
|
|
1656
|
+
todos_hallazgos: List[Finding] = []
|
|
1657
|
+
|
|
1658
|
+
for cid in ids:
|
|
1659
|
+
todos_hallazgos.extend(scan_docker_container(cid))
|
|
1660
|
+
|
|
1661
|
+
return todos_hallazgos
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
def _scan_string_for_secrets(texto: str) -> List[Dict]:
|
|
1665
|
+
"""
|
|
1666
|
+
Aplica todos los SECRET_PATTERNS a una cadena de texto y devuelve
|
|
1667
|
+
una lista de coincidencias brutas (sin crear objetos Finding).
|
|
1668
|
+
|
|
1669
|
+
Cada elemento del resultado es un dict con las claves:
|
|
1670
|
+
- 'name': nombre del patrón coincidente
|
|
1671
|
+
- 'severity': nivel de severidad del patrón
|
|
1672
|
+
- 'category': categoría del patrón
|
|
1673
|
+
- 'valor': fragmento de texto que coincidió (sin censurar)
|
|
1674
|
+
|
|
1675
|
+
Útil como auxiliar en funciones de escaneo de entornos externos
|
|
1676
|
+
(Docker runtime, Kubernetes Secrets, etc.) donde el contexto
|
|
1677
|
+
de creación del Finding difiere del flujo estático habitual.
|
|
1678
|
+
"""
|
|
1679
|
+
coincidencias: List[Dict] = []
|
|
1680
|
+
for pat in SECRET_PATTERNS:
|
|
1681
|
+
match = pat["compiled"].search(texto)
|
|
1682
|
+
if match:
|
|
1683
|
+
coincidencias.append({
|
|
1684
|
+
"name": pat["name"],
|
|
1685
|
+
"severity": pat["severity"],
|
|
1686
|
+
"category": pat.get("category", "Secret"),
|
|
1687
|
+
"valor": match.group(0),
|
|
1688
|
+
})
|
|
1689
|
+
return coincidencias
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
def _kubectl_disponible() -> bool:
|
|
1693
|
+
"""Comprueba si el binario 'kubectl' está disponible en el PATH."""
|
|
1694
|
+
try:
|
|
1695
|
+
subprocess.run(
|
|
1696
|
+
["kubectl", "version", "--client", "--output=json"],
|
|
1697
|
+
capture_output=True,
|
|
1698
|
+
timeout=5,
|
|
1699
|
+
)
|
|
1700
|
+
return True
|
|
1701
|
+
except (FileNotFoundError, OSError):
|
|
1702
|
+
return False
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
def scan_kubernetes_secrets(namespace: Optional[str] = None) -> List[Finding]:
|
|
1706
|
+
"""
|
|
1707
|
+
Escanea los Kubernetes Secrets del clúster activo buscando credenciales
|
|
1708
|
+
y datos sensibles codificados en base64.
|
|
1709
|
+
|
|
1710
|
+
Parámetros:
|
|
1711
|
+
namespace: si se indica, limita la búsqueda a ese namespace;
|
|
1712
|
+
si es None se escanean todos los namespaces.
|
|
1713
|
+
|
|
1714
|
+
Requiere que 'kubectl' esté disponible en el PATH y que el contexto
|
|
1715
|
+
activo tenga permisos de lectura sobre los Secrets del namespace indicado.
|
|
1716
|
+
|
|
1717
|
+
Retorna una lista de objetos Finding con los secretos detectados.
|
|
1718
|
+
"""
|
|
1719
|
+
if not _kubectl_disponible():
|
|
1720
|
+
console.print("[yellow] Aviso: 'kubectl' no encontrado en PATH — omitiendo escaneo de K8s Secrets[/]")
|
|
1721
|
+
return []
|
|
1722
|
+
|
|
1723
|
+
# Construir comando kubectl
|
|
1724
|
+
if namespace:
|
|
1725
|
+
cmd = ["kubectl", "get", "secret", "-n", namespace, "-o", "json"]
|
|
1726
|
+
scope_label = f"namespace '{namespace}'"
|
|
1727
|
+
else:
|
|
1728
|
+
cmd = ["kubectl", "get", "secret", "--all-namespaces", "-o", "json"]
|
|
1729
|
+
scope_label = "todos los namespaces"
|
|
1730
|
+
|
|
1731
|
+
console.print(f"[cyan] Consultando Kubernetes Secrets ({scope_label})...[/]")
|
|
1732
|
+
|
|
1733
|
+
try:
|
|
1734
|
+
result = subprocess.run(
|
|
1735
|
+
cmd,
|
|
1736
|
+
capture_output=True,
|
|
1737
|
+
text=True,
|
|
1738
|
+
timeout=30,
|
|
1739
|
+
)
|
|
1740
|
+
except subprocess.TimeoutExpired:
|
|
1741
|
+
console.print("[red] Tiempo de espera agotado ejecutando 'kubectl get secret'[/]")
|
|
1742
|
+
return []
|
|
1743
|
+
except (FileNotFoundError, OSError) as exc:
|
|
1744
|
+
console.print(f"[red] Error ejecutando kubectl: {exc}[/]")
|
|
1745
|
+
return []
|
|
1746
|
+
|
|
1747
|
+
if result.returncode != 0:
|
|
1748
|
+
console.print(f"[red] kubectl salió con código {result.returncode}: {result.stderr.strip()[:200]}[/]")
|
|
1749
|
+
return []
|
|
1750
|
+
|
|
1751
|
+
try:
|
|
1752
|
+
data = json.loads(result.stdout)
|
|
1753
|
+
except json.JSONDecodeError as exc:
|
|
1754
|
+
console.print(f"[red] Error parseando salida JSON de kubectl: {exc}[/]")
|
|
1755
|
+
return []
|
|
1756
|
+
|
|
1757
|
+
# Puede ser una lista (--all-namespaces) o un objeto único
|
|
1758
|
+
items = data.get("items", [data]) if "items" in data else [data]
|
|
1759
|
+
hallazgos: List[Finding] = []
|
|
1760
|
+
|
|
1761
|
+
for secret in items:
|
|
1762
|
+
meta = secret.get("metadata", {})
|
|
1763
|
+
ns = meta.get("namespace", "default")
|
|
1764
|
+
name = meta.get("name", "desconocido")
|
|
1765
|
+
tipo = secret.get("type", "")
|
|
1766
|
+
datos = secret.get("data") or {}
|
|
1767
|
+
|
|
1768
|
+
for clave, valor_b64 in datos.items():
|
|
1769
|
+
# Decodificar el valor base64
|
|
1770
|
+
try:
|
|
1771
|
+
valor_decoded = base64.b64decode(valor_b64).decode("utf-8", errors="replace")
|
|
1772
|
+
except Exception:
|
|
1773
|
+
continue
|
|
1774
|
+
|
|
1775
|
+
# Buscar patrones de secreto en el valor decodificado
|
|
1776
|
+
coincidencias = _scan_string_for_secrets(valor_decoded)
|
|
1777
|
+
|
|
1778
|
+
# También escanear la clave (puede revelar el tipo de secreto)
|
|
1779
|
+
clave_coincidencias = _scan_string_for_secrets(clave)
|
|
1780
|
+
|
|
1781
|
+
# Usar el valor decodificado si no hay coincidencias por patrón
|
|
1782
|
+
# pero la clave sugiere que es un secreto
|
|
1783
|
+
claves_sensibles = {"password", "passwd", "secret", "token", "key",
|
|
1784
|
+
"apikey", "api_key", "private_key", "auth"}
|
|
1785
|
+
es_clave_sensible = any(k in clave.lower() for k in claves_sensibles)
|
|
1786
|
+
|
|
1787
|
+
if not coincidencias and es_clave_sensible:
|
|
1788
|
+
# Hallazgo genérico basado en nombre de clave
|
|
1789
|
+
fp = _fingerprint(valor_decoded, f"K8S_SECRET_KEY:{clave}")
|
|
1790
|
+
extracto = _censor(valor_decoded[:120])
|
|
1791
|
+
hallazgos.append(Finding(
|
|
1792
|
+
file=f"k8s://{ns}/{name}",
|
|
1793
|
+
line_no=0,
|
|
1794
|
+
pattern=f"K8S_SECRET_KEY: {clave} (tipo: {tipo})",
|
|
1795
|
+
category="Kubernetes · Secret",
|
|
1796
|
+
severity=Severity.HIGH,
|
|
1797
|
+
preview=extracto,
|
|
1798
|
+
context=[f"namespace={ns}", f"secret={name}", f"tipo={tipo}", f"clave={clave}"],
|
|
1799
|
+
fingerprint=fp,
|
|
1800
|
+
))
|
|
1801
|
+
else:
|
|
1802
|
+
for c in coincidencias:
|
|
1803
|
+
fp = _fingerprint(c["valor"], f"K8S_SECRET:{ns}/{name}/{clave}")
|
|
1804
|
+
extracto = _censor(c["valor"])
|
|
1805
|
+
hallazgos.append(Finding(
|
|
1806
|
+
file=f"k8s://{ns}/{name}",
|
|
1807
|
+
line_no=0,
|
|
1808
|
+
pattern=f"K8S_SECRET: {c['name']} (clave: {clave})",
|
|
1809
|
+
category="Kubernetes · Secret",
|
|
1810
|
+
severity=c["severity"],
|
|
1811
|
+
preview=extracto,
|
|
1812
|
+
context=[f"namespace={ns}", f"secret={name}", f"tipo={tipo}",
|
|
1813
|
+
f"clave={clave}", f"patron={c['name']}"],
|
|
1814
|
+
fingerprint=fp,
|
|
1815
|
+
))
|
|
1816
|
+
|
|
1817
|
+
if hallazgos:
|
|
1818
|
+
console.print(f"[dim] {len(hallazgos)} hallazgo(s) en Kubernetes Secrets[/]")
|
|
1819
|
+
else:
|
|
1820
|
+
console.print("[dim] Sin secretos detectados en Kubernetes Secrets[/]")
|
|
1821
|
+
|
|
1822
|
+
return hallazgos
|
|
1823
|
+
|
|
1824
|
+
|
|
1494
1825
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
1495
1826
|
# CLI
|
|
1496
1827
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1566,6 +1897,27 @@ def parse_args() -> argparse.Namespace:
|
|
|
1566
1897
|
"El baseline se crea si no existe (default: .vamp-secrets-baseline.json)."
|
|
1567
1898
|
))
|
|
1568
1899
|
|
|
1900
|
+
docker = p.add_argument_group("Escaneo Docker en runtime (v2.3)")
|
|
1901
|
+
docker.add_argument("--scan-container", metavar="NAME_OR_ID",
|
|
1902
|
+
dest="scan_container", default=None,
|
|
1903
|
+
help="Escanear variables de entorno de un contenedor Docker en ejecución. "
|
|
1904
|
+
"Requiere que 'docker' esté disponible en el PATH.")
|
|
1905
|
+
docker.add_argument("--scan-all-containers", action="store_true",
|
|
1906
|
+
dest="scan_all_containers",
|
|
1907
|
+
help="Escanear todos los contenedores Docker actualmente en ejecución "
|
|
1908
|
+
"('docker ps -q'). Requiere que 'docker' esté disponible en el PATH.")
|
|
1909
|
+
|
|
1910
|
+
k8s = p.add_argument_group("Escaneo Kubernetes Secrets (v2.4)")
|
|
1911
|
+
k8s.add_argument("--k8s", action="store_true",
|
|
1912
|
+
dest="k8s",
|
|
1913
|
+
help="Escanear los Kubernetes Secrets del clúster activo (todos los namespaces). "
|
|
1914
|
+
"Requiere que 'kubectl' esté disponible en el PATH y que el contexto "
|
|
1915
|
+
"activo tenga permisos de lectura sobre los Secrets.")
|
|
1916
|
+
k8s.add_argument("--k8s-namespace", metavar="NS",
|
|
1917
|
+
dest="k8s_namespace", default=None,
|
|
1918
|
+
help="Limitar el escaneo de K8s Secrets a un namespace concreto "
|
|
1919
|
+
"(implica --k8s). Ej: --k8s-namespace production")
|
|
1920
|
+
|
|
1569
1921
|
ci = p.add_argument_group("Integración CI/CD")
|
|
1570
1922
|
ci.add_argument("--install-hook", action="store_true",
|
|
1571
1923
|
help="Instalar hook pre-commit git en el directorio objetivo y salir")
|
|
@@ -2016,6 +2368,49 @@ def main() -> None:
|
|
|
2016
2368
|
console.print("[dim] Sin misconfiguraciones Vault detectadas[/]\n")
|
|
2017
2369
|
all_findings.extend(vault_findings)
|
|
2018
2370
|
|
|
2371
|
+
# ── Fase 2d: escaneo de contenedores Docker en runtime (v2.3) ────────────
|
|
2372
|
+
_contenedor_a_escanear = getattr(args, "scan_container", None)
|
|
2373
|
+
_escanear_todos = getattr(args, "scan_all_containers", False)
|
|
2374
|
+
|
|
2375
|
+
if _contenedor_a_escanear:
|
|
2376
|
+
console.print("[bold cyan] FASE 2d[/] — Escaneando contenedor Docker en runtime...")
|
|
2377
|
+
docker_findings = scan_docker_container(_contenedor_a_escanear)
|
|
2378
|
+
if docker_findings:
|
|
2379
|
+
console.print(
|
|
2380
|
+
f"[dim] {len(docker_findings)} hallazgo(s) en variables de entorno "
|
|
2381
|
+
f"del contenedor '{_contenedor_a_escanear}'[/]\n"
|
|
2382
|
+
)
|
|
2383
|
+
else:
|
|
2384
|
+
console.print("[dim] Sin secretos detectados en el contenedor indicado[/]\n")
|
|
2385
|
+
all_findings.extend(docker_findings)
|
|
2386
|
+
|
|
2387
|
+
elif _escanear_todos:
|
|
2388
|
+
console.print("[bold cyan] FASE 2d[/] — Escaneando todos los contenedores Docker en ejecución...")
|
|
2389
|
+
docker_findings = scan_all_docker_containers()
|
|
2390
|
+
if docker_findings:
|
|
2391
|
+
console.print(
|
|
2392
|
+
f"[dim] {len(docker_findings)} hallazgo(s) en variables de entorno "
|
|
2393
|
+
f"de contenedores Docker[/]\n"
|
|
2394
|
+
)
|
|
2395
|
+
else:
|
|
2396
|
+
console.print("[dim] Sin secretos detectados en los contenedores en ejecución[/]\n")
|
|
2397
|
+
all_findings.extend(docker_findings)
|
|
2398
|
+
|
|
2399
|
+
# ── Fase 2e: escaneo de Kubernetes Secrets (v2.4) ────────────────────────
|
|
2400
|
+
_k8s_ns = getattr(args, "k8s_namespace", None)
|
|
2401
|
+
_k8s = getattr(args, "k8s", False) or bool(_k8s_ns)
|
|
2402
|
+
|
|
2403
|
+
if _k8s:
|
|
2404
|
+
console.print("[bold cyan] FASE 2e[/] — Escaneando Kubernetes Secrets...")
|
|
2405
|
+
k8s_findings = scan_kubernetes_secrets(namespace=_k8s_ns)
|
|
2406
|
+
if k8s_findings:
|
|
2407
|
+
console.print(
|
|
2408
|
+
f"[dim] {len(k8s_findings)} hallazgo(s) en Kubernetes Secrets[/]\n"
|
|
2409
|
+
)
|
|
2410
|
+
else:
|
|
2411
|
+
console.print("[dim] Sin secretos detectados en Kubernetes Secrets[/]\n")
|
|
2412
|
+
all_findings.extend(k8s_findings)
|
|
2413
|
+
|
|
2019
2414
|
# ── Deduplicación y filtrado ──────────────────────────────────────────────
|
|
2020
2415
|
seen: Set[str] = set()
|
|
2021
2416
|
unique: List[Finding] = []
|
|
File without changes
|
|
File without changes
|
{vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/requires.txt
RENAMED
|
File without changes
|
{vamp_secrets_scanner-2.2 → vamp_secrets_scanner-2.4}/vamp_secrets_scanner.egg-info/top_level.txt
RENAMED
|
File without changes
|