Files
apps-registry/workloads/docs-portal/docs/playbooks/incidente-docker-in-docker-semgrep-2026-08.md
T
devops be3e5c2a4c docs(devsecops): documentar 4+1 hallazgos de infraestructura y TODOs de Cosign
5 playbooks nuevos en docs/playbooks/ del portal:
- SQLite en modo DELETE causando SQLITE_BUSY, por que WAL lo resuelve.
- Anchors YAML no soportados por el parser de Gitea Actions, incluido
  el diagnostico equivocado inicial (indentacion) que se corrigio
  despues -- documentado con honestidad como leccion de proceso.
- Docker-in-Docker vs sibling containers, y por que Semgrep corre
  nativo en un venv en vez de como container aparte.
- El SNI/realm HTTP del tunel rompiendo especificamente a Cosign
  (docker push funcionaba, cosign sign no), conectado con el
  incidente ya documentado del 504 -- misma capa de ingress, dos
  fallas distintas.
- Contencion de recursos entre Gitea y su runner: causa raiz real
  confirmada en vivo durante esta misma sesion (ambos contenedores
  reiniciados a mitad de un build, sin limites de CPU/memoria reales),
  con fix propuesto (ver rama fix/gitea-runner-resource-limits en
  scripts/) pendiente de aplicacion manual.

Se actualiza el playbook del 504 para linkear hacia el nuevo hallazgo
de contencion, cerrando el loop que habia quedado abierto ahi.

cosign.md: nueva seccion "Limitaciones conocidas y mejoras futuras"
con los dos TODO reales -- firma por tag en vez de digest (con el
warning real de Cosign visto en los logs) y transparency log de Rekor
deshabilitado, explicado en simple.

index.md: tabla resumen de los 3 pipelines (imagen + adaptacion de
Semgrep por stack), diagrama generalizado para reflejar que es el
mismo patron en los 3 workflows, y "que falta" actualizado.

Validado con mkdocs build --strict (0 errores, 0 warnings) antes de
commitear.
2026-08-15 15:17:43 -05:00

103 lines
4.4 KiB
Markdown

# Incidente: Docker-in-Docker roto en el runner — por qué Semgrep corre nativo (2026-08)
| | |
|---|---|
| **Ventana del incidente** | 2026-08-15, primer intento de correr Semgrep como container aparte en `build.yaml` |
| **Servicio afectado** | Gitea Actions runner (`gitea-runner`, imagen `act_runner`) |
| **Impacto** | Cualquier step que intentara `docker run -v "$(pwd):/algo"` desde dentro de un job fallaba con `read-only file system` o rutas inexistentes |
## Docker-in-Docker vs. sibling containers, para quien recién arranca en CI/CD
Cuando un job de CI necesita usar Docker (por ejemplo, para correr una
herramienta empaquetada como imagen), hay dos formas distintas de
dárselo, y confundirlas rompe cosas de maneras difíciles de
diagnosticar:
- **Docker-in-Docker (DinD):** el contenedor del job tiene su **propio**
daemon Docker corriendo adentro, completamente aislado del daemon
del host. Cuando el job hace `docker run`, ese contenedor nuevo nace
*dentro* del contenedor del job, como una muñeca rusa. El
filesystem que ve ese daemon interno es el del contenedor del job,
no el del host real.
- **Sibling containers (contenedores hermanos):** el contenedor del
job **no** tiene su propio daemon — en cambio, monta el socket del
daemon Docker del **host** (`/var/run/docker.sock`) y habla
directamente con él. Cuando el job hace `docker run`, ese contenedor
nuevo nace como **hermano** del contenedor del job (mismo nivel,
mismo host, mismo daemon), no adentro suyo.
La imagen `act_runner` que usa este runner de Gitea Actions usa el
segundo modelo: **sibling containers**, montando el socket del daemon
del host (ver `create_gitea_runner_container()` en
`scripts/deploy-lab.sh`, que monta
`-v /var/run/docker.sock:/var/run/docker.sock`).
## Por qué eso rompe un `docker run -v` ingenuo
Con sibling containers, cuando un step dentro de un job pide
```bash
docker run -v "${{ github.workspace }}:/src" alguna-imagen
```
ese comando lo ejecuta el **daemon del host**, no el contenedor del
job. `${{ github.workspace }}` es una ruta que existe *dentro* del
contenedor del job (por ejemplo `/workspace/devops/apps-registry`) —
pero el daemon del host, al crear el contenedor hermano, intenta
montar esa misma ruta **desde el filesystem del host real**, donde
probablemente no existe (o existe otra cosa completamente distinta ahí).
## Síntoma
Al intentar correr Semgrep como container aparte (`docker run` desde
dentro del step), el error observado en el run real fue:
```text
mkdir /workspace: read-only file system
```
No un "no existe la ruta" limpio — un intento de crear el directorio
que falla porque, en el contexto real del daemon del host, esa ruta
cae en un punto de montaje de solo lectura (o simplemente no es un
lugar donde el daemon del host puede/debe escribir).
## Causa raíz
Sibling containers, no Docker-in-Docker: un `docker run -v
"${{ github.workspace }}:/src"` ejecutado desde dentro de un job
intenta montar, en el daemon del **host**, una ruta que solo tiene
sentido **dentro** del contenedor del job. El daemon del host no ve
esa ruta como el mismo directorio — la ve como una ruta arbitraria de
su propio filesystem.
## Fix aplicado
Se evita el problema de raíz: **Semgrep corre nativo**, instalado
directo en el contenedor del job (mismo criterio ya usado para
Gitleaks, Trivy, Syft y Cosign — todos se instalan como binario/paquete
dentro del job, ninguno corre como container aparte):
```bash
python3 -m venv /tmp/semgrep-venv
/tmp/semgrep-venv/bin/pip install --quiet "semgrep==1.173.0"
```
### Por qué un venv y no `pip3 install` directo
La imagen del runner ya trae paquetes de Python instalados por `apt`
(por ejemplo `PyJWT`) sin metadata compatible con `pip`. Un
`pip3 install --break-system-packages` sobre esa base falla al
intentar reemplazarlos (`RECORD file not found`) porque `pip` no
encuentra el registro de archivos que necesita para saber qué está
reemplazando. Un venv aislado evita tocar los paquetes del sistema por
completo — Semgrep y sus dependencias quedan en su propio directorio,
sin interferir con nada que `apt` ya haya instalado.
## Validación
Validado localmente contra `docker.gitea.com/runner-images:ubuntu-latest`
(la misma imagen que usa el runner real) que `python3`/`pip3` están
disponibles, y confirmado en una corrida real posterior al fix que
Semgrep corre y reporta hallazgos sin ningún error de Docker de por
medio.