Files
apps-registry/workloads/docs-portal/docs/devsecops/index.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

6.5 KiB

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 ¿Hay un secreto commiteado? Bloquea
Trivy — imagen ¿La imagen tiene una CVE conocida? CRITICAL bloquea, HIGH informa
Trivy — IaC ¿El manifiesto de Kubernetes es inseguro? Informa
Semgrep (SAST) ¿El código tiene un patrón inseguro conocido? Informa (auditoría)
Syft (SBOM) ¿Qué paquetes exactos quedaron en la imagen? Informa (inventario)
Cosign ¿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.

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) — 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.
  • 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).