Corre en push y pull_request, antes del job build (needs: gitleaks). Si detecta un secreto, exit-code=1 detiene el pipeline antes de construir la imagen. Scan histórico completo del repo (249 commits) confirmado limpio, corrido aparte de forma manual. Documenta el step en docs/devsecops/gitleaks.md: qué es secret scanning, por qué corre antes del build, cómo leer un hallazgo y cómo manejar falsos positivos con allowlist.
138 lines
5.7 KiB
Markdown
138 lines
5.7 KiB
Markdown
# Gitleaks — detección de secretos
|
|
|
|
!!! info "Qué problema resuelve"
|
|
Gitleaks busca patrones de credenciales (API keys, tokens, contraseñas,
|
|
llaves privadas) dentro del código fuente. No sabe si una credencial es
|
|
"real" — detecta **formas** que parecen credenciales (una API key de
|
|
AWS siempre empieza con `AKIA`, una llave privada siempre tiene el
|
|
encabezado `-----BEGIN PRIVATE KEY-----`, etc.) y también cadenas con
|
|
entropía alta (aleatoriedad), que suelen ser tokens generados.
|
|
|
|
## Por qué esto importa
|
|
|
|
Un secreto commiteado a git **nunca deja de estar ahí**, aunque lo borres
|
|
en el siguiente commit. Sigue existiendo en el historial, en cualquier
|
|
fork, en cualquier clon local que alguien ya haya hecho. La única manera
|
|
real de "revocar" un secreto filtrado es rotarlo (generar uno nuevo e
|
|
invalidar el viejo) — borrar el commit no alcanza.
|
|
|
|
Por eso el objetivo de gitleaks no es "arreglar" el secreto después de que
|
|
se filtró, sino **evitar que el commit con el secreto llegue a existir en
|
|
el repo remoto**.
|
|
|
|
## Por qué corre antes del build
|
|
|
|
En `.gitea/workflows/build.yaml`, el job `gitleaks` corre **antes** que el
|
|
job `build` (que compila la imagen Docker y la sube al registry). El job
|
|
`build` tiene `needs: gitleaks` — si el scan falla, `build` ni siquiera
|
|
arranca.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
A[push / pull_request] --> B[gitleaks]
|
|
B -- "sin hallazgos" --> C[build]
|
|
B -- "secreto detectado" --> X["❌ pipeline detenido<br/>build no corre"]
|
|
```
|
|
|
|
La lógica es simple: no tiene sentido gastar tiempo de build y minutos de
|
|
runner compilando una imagen a partir de un commit que de todas formas hay
|
|
que rechazar. Fallar rápido, fallar barato.
|
|
|
|
También corre en **pull request**, no solo en push a `main` — así un
|
|
secreto se detecta antes de que el PR se mergee, que es el punto donde
|
|
todavía es más fácil corregirlo (basta con un `git commit --amend` o un
|
|
nuevo commit en la misma rama, sin tocar `main`).
|
|
|
|
## Alcance de este scan
|
|
|
|
El step de CI escanea el árbol de archivos ya *checked out* del commit
|
|
(`gitleaks detect --no-git`), no el historial completo — el checkout del
|
|
pipeline es superficial (`fetch-depth: 1`, solo el último commit), así que
|
|
no hay historial que recorrer en ese punto.
|
|
|
|
Antes de integrar esto al pipeline corrimos un **scan histórico completo**
|
|
del repo `apps-registry` (los 249 commits, con `gitleaks detect` en modo
|
|
git normal, sin `--no-git`) para confirmar que no había secretos ya
|
|
commiteados en el pasado. Resultado: **sin hallazgos**. Ese scan histórico
|
|
es una tarea puntual, no algo que corra en cada push — si alguna vez se
|
|
sospecha una filtración vieja, se repite manualmente.
|
|
|
|
## Cómo leer un hallazgo
|
|
|
|
Un hallazgo de gitleaks (en el `gitleaks-report.json` que el pipeline
|
|
publica como artifact) se ve así:
|
|
|
|
```json
|
|
{
|
|
"Description": "AWS Access Key",
|
|
"StartLine": 14,
|
|
"File": "workloads/ecommerce/lib/config.ts",
|
|
"Match": "REDACTED",
|
|
"Secret": "REDACTED",
|
|
"RuleID": "aws-access-token",
|
|
"Commit": "a1b2c3d"
|
|
}
|
|
```
|
|
|
|
Campos clave:
|
|
|
|
| Campo | Qué significa |
|
|
|---|---|
|
|
| `RuleID` | Qué tipo de secreto detectó (la regla que hizo match) |
|
|
| `File` / `StartLine` | Dónde está, exactamente |
|
|
| `Match` / `Secret` | El valor detectado — el pipeline usa `--redact`, así que en el reporte real aparece censurado, no en texto plano |
|
|
| `Commit` | En qué commit se introdujo (solo aplica al scan histórico, no al scan `--no-git` del pipeline) |
|
|
|
|
!!! danger "Si el hallazgo es real"
|
|
1. **No lo borres del código y listo** — el secreto sigue "filtrado"
|
|
aunque ya no esté en el archivo actual.
|
|
2. **Rota la credencial primero** en el sistema que la emitió (AWS,
|
|
Gitea, Medusa, lo que sea). Un secreto que ya se vio en un log de
|
|
CI o en un diff de PR se trata como comprometido.
|
|
3. Después de rotarla, sí, saca el valor viejo del código y usa una
|
|
variable de entorno / secret de Gitea Actions en su lugar.
|
|
4. Si el secreto llegó a estar en `main` (no solo en una rama de PR),
|
|
avisa antes de reescribir historial — reescribir historial en un
|
|
repo compartido tiene sus propios riesgos y hay que decidirlo con
|
|
calma, no como reacción automática del pipeline.
|
|
|
|
## Falsos positivos: cómo hacer allowlist
|
|
|
|
Gitleaks detecta *formas*, no intención. Cosas que típicamente generan
|
|
falsos positivos en este proyecto:
|
|
|
|
- Placeholders como `CAMBIAR` en `commerce/secrets.template.yaml` — **no**
|
|
deberían disparar nada porque no tienen la forma de un secreto real
|
|
(baja entropía, texto plano legible), pero si algún día se usa un
|
|
placeholder con más pinta de secreto real (ej. un UUID de ejemplo), sí
|
|
puede hacer match.
|
|
- Hashes largos o IDs opacos que no son secretos (ej. `MEDUSA_REGION_ID`
|
|
en `frontend.yaml`), si tienen entropía suficientemente alta.
|
|
|
|
Cuando gitleaks marca algo que **no es** un secreto real, se agrega una
|
|
regla de allowlist en un archivo `.gitleaks.toml` en la raíz del repo
|
|
(todavía no existe — se crea la primera vez que haga falta):
|
|
|
|
```toml
|
|
[allowlist]
|
|
description = "Falsos positivos conocidos del lab"
|
|
regexes = [
|
|
'''MEDUSA_REGION_ID''',
|
|
]
|
|
paths = [
|
|
'''workloads/ecommerce/commerce/secrets\.template\.yaml''',
|
|
]
|
|
```
|
|
|
|
!!! warning "No es una vía rápida para ignorar hallazgos reales"
|
|
Cada entrada de allowlist debe quedar documentada (por qué es un falso
|
|
positivo, no solo "molestaba") y revisada antes de mergear, porque una
|
|
allowlist mal escrita (una regex demasiado amplia) puede silenciar un
|
|
secreto real futuro sin que nadie se dé cuenta.
|
|
|
|
## Dónde ver el resultado
|
|
|
|
El job `gitleaks` publica el reporte JSON como artifact del pipeline
|
|
(`gitleaks-report`) en cada ejecución, tenga o no hallazgos — así queda
|
|
disponible para inspección incluso cuando el scan pasa limpio.
|