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.
120 lines
6.5 KiB
Markdown
120 lines
6.5 KiB
Markdown
# DevSecOps
|
|
|
|
Fase 2 del lab: integrar tooling de seguridad al pipeline de Gitea
|
|
Actions. Se empezó por un solo repo de referencia — el frontend de ARI
|
|
Shopping (`workloads/ecommerce`, `.gitea/workflows/build.yaml`) — y ya
|
|
está replicado, con evidencia real de corridas en verde, a los tres
|
|
componentes del monorepo:
|
|
|
|
| App | Workflow | Imagen | Adaptación de Semgrep |
|
|
|---|---|---|---|
|
|
| Frontend (Next.js) | `build.yaml` | `ecommerce-frontend` | `p/typescript` + `p/react` + `p/nextjs` + `p/security-audit` |
|
|
| Backend (Medusa v2, API TypeScript) | `build-medusa.yaml` | `ecommerce-medusa` | `p/typescript` + `p/security-audit` + `p/owasp-top-ten` (sin React/Next.js — es una API, no SSR) |
|
|
| Docs Portal (MkDocs, Python) | `deploy-docs.yaml` | `docs-portal` | `p/python` + `p/security-audit` (sin código de aplicación propio hoy) |
|
|
|
|
Cada app tiene su propia llave pública de Cosign commiteada
|
|
(`cosign.pub` en su propio directorio), pero las tres comparten el
|
|
mismo par de llaves de firma (mismos secrets `COSIGN_PRIVATE_KEY` /
|
|
`COSIGN_PASSWORD` a nivel de repo) — una sola identidad de firma para
|
|
todo el registry de este lab.
|
|
|
|
Cinco herramientas, cada una respondiendo una pregunta distinta:
|
|
|
|
| Herramienta | Pregunta que responde | Modo |
|
|
|---|---|---|
|
|
| [Gitleaks](gitleaks.md) | ¿Hay un secreto commiteado? | **Bloquea** |
|
|
| [Trivy — imagen](trivy.md) | ¿La imagen tiene una CVE conocida? | CRITICAL **bloquea**, HIGH informa |
|
|
| [Trivy — IaC](trivy.md) | ¿El manifiesto de Kubernetes es inseguro? | Informa |
|
|
| [Semgrep (SAST)](sast.md) | ¿El código tiene un patrón inseguro conocido? | Informa (auditoría) |
|
|
| [Syft (SBOM)](sbom.md) | ¿Qué paquetes exactos quedaron en la imagen? | Informa (inventario) |
|
|
| [Cosign](cosign.md) | ¿Esta imagen es exactamente la que armó el pipeline? | **Bloquea** (smoke test) |
|
|
|
|
## El pipeline completo
|
|
|
|
El mismo patrón corre, con el mismo orden de etapas, en los tres
|
|
workflows (`build.yaml`, `build-medusa.yaml`, `deploy-docs.yaml`) — lo
|
|
que cambia entre ellos es el ruleset de Semgrep y qué manifiesto(s)
|
|
escanea Trivy IaC (ver tabla arriba), no la estructura del pipeline.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
subgraph J1["job: gitleaks (push + pull_request)"]
|
|
A1[checkout] --> A2["gitleaks detect --no-git"]
|
|
end
|
|
|
|
A2 -- "secreto encontrado" --> XA["❌ pipeline detenido<br/>build nunca arranca"]
|
|
A2 -- "limpio" --> B0
|
|
|
|
subgraph J2["job: build (solo push a main, needs: gitleaks)"]
|
|
B0[checkout + validaciones de código] --> B1["Trivy IaC<br/>(manifiesto K8s de la app)"]
|
|
B1 -.informa.-> B2["Semgrep SAST<br/>(ruleset por stack, audit mode)"]
|
|
B2 -.informa.-> B3[login registry]
|
|
B3 --> B4["docker build<br/>(push: false, load: true)"]
|
|
B4 --> B5["Trivy imagen<br/>CRITICAL"]
|
|
B5 -- "CRITICAL con fix" --> XB["❌ detenido<br/>no se sube la imagen"]
|
|
B5 -- "sin CRITICAL" --> B6["Trivy imagen<br/>HIGH (informa)"]
|
|
B6 --> B7["Syft → SBOM<br/>(CycloneDX + SPDX)"]
|
|
B7 --> B8["docker push"]
|
|
B8 --> B9["cosign sign"]
|
|
B9 --> B10["cosign verify<br/>(smoke test)"]
|
|
B10 -- "firma inválida" --> XC["❌ detenido"]
|
|
B10 -- "firma válida" --> B11["actualizar manifiesto de la app<br/>(GitOps, Argo CD sincroniza)"]
|
|
end
|
|
```
|
|
|
|
## Por qué este orden
|
|
|
|
- **Gitleaks corre en un job aparte, antes que todo lo demás** — incluso
|
|
antes que el checkout completo del job de build. Si hay un secreto,
|
|
no tiene sentido gastar minutos de build en un commit que hay que
|
|
rechazar igual. Es el único check que corre también en `pull_request`,
|
|
no solo en push a `main`.
|
|
- **Trivy IaC y Semgrep corren antes del build de Docker.** Ninguno de
|
|
los dos necesita la imagen construida — analizan manifiestos y código
|
|
fuente respectivamente — así que si algo llamativo apareciera ahí, se
|
|
sabe temprano, sin esperar el build (que es el paso más lento del
|
|
pipeline).
|
|
- **Trivy imagen corre después del build pero antes del push.** Por eso
|
|
el build usa `push: false, load: true`: la imagen queda en el daemon
|
|
local del runner, escaneable, pero no sale hacia el registry hasta
|
|
pasar el gate de CRITICAL.
|
|
- **Syft (SBOM) corre sobre la imagen ya validada**, antes del push —
|
|
documenta exactamente lo que se está por publicar.
|
|
- **Cosign firma después del push exitoso** (no tiene sentido firmar
|
|
algo que no llegó al registry) y se verifica en el mismo pipeline como
|
|
smoke test.
|
|
|
|
## Qué pasa si cada uno falla
|
|
|
|
| Si falla... | El pipeline... |
|
|
|---|---|
|
|
| Gitleaks | Se detiene ahí mismo. El job `build` nunca arranca (`needs: gitleaks`). Nada se construye. |
|
|
| Trivy IaC | No se detiene — el hallazgo queda en el log, informativo. |
|
|
| Semgrep | No se detiene — mismo criterio: primera vuelta en modo auditoría. |
|
|
| Trivy imagen (CRITICAL) | Se detiene después del build, antes del push. La imagen con la CVE nunca llega al registry. |
|
|
| Trivy imagen (HIGH) | No se detiene — se reporta para revisar en conjunto. |
|
|
| Syft | Si el propio comando falla (no si "encuentra algo" — un SBOM no tiene hallazgos que bloqueen), el pipeline se detiene por error real de la herramienta. |
|
|
| Cosign sign/verify | Se detiene. Si la imagen se firmó pero no verifica, algo está mal con las llaves o con la imagen — no se continúa con la promoción GitOps. |
|
|
|
|
## Qué falta después de esta primera vuelta
|
|
|
|
- Revisar en conjunto los hallazgos de Trivy IaC y Semgrep (hoy
|
|
informativos) y decidir cuáles pasan a bloquear.
|
|
- Decidir si Trivy imagen sube el umbral de bloqueo a HIGH una vez que
|
|
el backlog de CVEs conocidas esté bajo control.
|
|
- Admission controller en el cluster que verifique la firma de Cosign
|
|
antes de dejar correr un Pod (ver [cosign.md](cosign.md)) — hoy la
|
|
verificación es solo un smoke test dentro del propio pipeline.
|
|
- Firmar por digest en vez de por tag, y reevaluar el transparency log
|
|
de Sigstore — ver
|
|
[limitaciones conocidas de Cosign](cosign.md#limitaciones-conocidas-y-mejoras-futuras).
|
|
- `commerce-backend` tiene dos CVE CRITICAL documentados como excepción
|
|
puntual en `workloads/commerce-backend/.trivyignore` (Go stdlib
|
|
embebido en el binario de esbuild, sin fix compatible con la versión
|
|
de Vite que usa el admin-sdk de Medusa hoy) — revisar en cada bump de
|
|
`@medusajs/*` si ya deja de ser necesaria.
|
|
- Contención de recursos entre Gitea y su runner bajo carga real —
|
|
causa raíz confirmada, fix propuesto y documentado, aplicación
|
|
manual pendiente (ver
|
|
[playbook de contención de recursos](../playbooks/incidente-contencion-recursos-gitea-runner-2026-08.md)).
|