From b6038e06ca9974f044121c6d25add986615967a8 Mon Sep 17 00:00:00 2001 From: Cristian Felipe Cruz Buitron Date: Fri, 14 Aug 2026 21:14:00 -0500 Subject: [PATCH] feat(devsecops): generar SBOM (Syft) de la imagen del frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corre después del gate de CRITICAL de Trivy, sobre la imagen ya construida. Genera CycloneDX y SPDX, publicados como artifact sbom-v1.0.X en cada build. Validado contra la imagen real: 3528 componentes CycloneDX / 228 paquetes SPDX. Documenta en docs/devsecops/sbom.md el propósito (trazabilidad tipo "log4shell"), diferencia entre formatos, y un hallazgo real (yarn empaquetado sin usarse, mismo patrón que el npm sacado en el fix de Trivy). --- .gitea/workflows/build.yaml | 34 +++++ workloads/docs-portal/docs/devsecops/sbom.md | 124 +++++++++++++++++++ workloads/docs-portal/mkdocs.yml | 1 + 3 files changed, 159 insertions(+) create mode 100644 workloads/docs-portal/docs/devsecops/sbom.md diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index fb3a3f1..568dc08 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -349,6 +349,40 @@ for r in results: --ignore-unfixed \ "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}" + # SBOM de la imagen que ya pasó el gate de CRITICAL — describe + # exactamente qué paquetes (y en qué versión) quedaron adentro. + # CycloneDX: formato más usado por herramientas de consulta/alertas + # de CVEs (ej. cruzar el SBOM contra un aviso nuevo tipo log4shell). + - name: Instalar Syft + shell: bash + run: | + set -euo pipefail + SYFT_VERSION="1.51.0" + curl -sSfL \ + "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz" \ + -o /tmp/syft.tar.gz + tar -xzf /tmp/syft.tar.gz -C /tmp syft + chmod +x /tmp/syft + /tmp/syft version + + - name: Generar SBOM (Syft) + shell: bash + run: | + set -euo pipefail + /tmp/syft "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}" \ + -o cyclonedx-json=sbom.cdx.json \ + -o spdx-json=sbom.spdx.json + + - name: Publicar SBOM + if: always() + uses: actions/upload-artifact@v3 + with: + name: sbom-${{ steps.vars.outputs.VERSION }} + path: | + sbom.cdx.json + sbom.spdx.json + if-no-files-found: ignore + # Login ya se hizo arriba; recién acá se sube, después de que la # imagen pasó el gate de CRITICAL. - name: Subir Imagen al Registry diff --git a/workloads/docs-portal/docs/devsecops/sbom.md b/workloads/docs-portal/docs/devsecops/sbom.md new file mode 100644 index 0000000..95e2198 --- /dev/null +++ b/workloads/docs-portal/docs/devsecops/sbom.md @@ -0,0 +1,124 @@ +# SBOM (Syft) — inventario de software + +!!! info "Qué es un SBOM" + SBOM = *Software Bill of Materials* — literalmente, una "lista de + materiales" del software, igual que la lista de ingredientes de un + producto. Es un documento (JSON, en este caso) que enumera **cada + paquete que terminó dentro de la imagen final**: nombre, versión + exacta, de dónde viene (`npm`, `deb`, etc.) y, cuando aplica, su + licencia. + + [Syft](https://github.com/anchore/syft) genera ese inventario + inspeccionando la imagen Docker ya construida — no necesita acceso al + código fuente ni a `package.json`, lee directamente lo que quedó + instalado en los layers de la imagen. + +## Por qué importa: el escenario "log4shell" + +En diciembre de 2021 apareció una vulnerabilidad crítica en Log4j (una +librería de logging de Java) usada, directa o indirectamente, en una +cantidad enorme de software. La pregunta que todo equipo tuvo que +responder en horas, no en días, fue: **"¿nosotros usamos esto, en algún +lugar, aunque sea una dependencia de una dependencia?"** + +Sin un SBOM, esa respuesta implica revisar manualmente cada +`package.json`, cada imagen Docker, cada servicio — y confiar en que no +se te escapó una dependencia transitiva de tres niveles de profundidad. +Con un SBOM generado en cada build y guardado como artifact, la respuesta +es una búsqueda de texto sobre un archivo: + +```bash +grep -i "nombre-del-paquete-afectado" sbom.cdx.json +``` + +Si aparece, sabés exactamente en qué versión, y podés cruzarlo contra el +aviso de seguridad para saber si tu versión específica está afectada — +en minutos, no en una auditoría manual del repo completo. + +## Qué genera este pipeline + +Un step de Syft corre sobre la imagen ya construida (la misma que pasó +el gate de CRITICAL de Trivy) y produce **dos formatos** del mismo +inventario, publicados como artifact del pipeline: + +- `sbom.cdx.json` — [CycloneDX](https://cyclonedx.org/), el formato con + mejor soporte en herramientas de consulta/alertas automáticas de CVEs. +- `sbom.spdx.json` — [SPDX](https://spdx.dev/), el estándar ISO, más + orientado a cumplimiento de licencias y trazabilidad legal. + +No hay una razón fuerte para elegir solo uno en esta etapa — generar +ambos cuesta segundos y cada formato es mejor para un caso de uso +distinto, así que se publican los dos. + +## Resultado real de este build + +Corriendo Syft contra la imagen real de `workloads/ecommerce`: + +| Formato | Paquetes listados | +|---|---| +| CycloneDX | 3528 componentes | +| SPDX | 228 paquetes | + +!!! tip "¿Por qué el número es tan distinto entre formatos?" + No es un error — cada formato tiene un nivel de detalle distinto. + CycloneDX de Syft incluye entradas más granulares (variantes, + sub-paquetes, entradas sin versión resuelta marcadas `UNKNOWN`) + mientras que SPDX agrupa a un nivel más alto. Para "¿tengo este + paquete, sí o no?" cualquiera de los dos sirve; para conteos exactos, + hay que saber cuál se está mirando. + +Ejemplo de una entrada real (`sbom.cdx.json`, recortado): + +```json +{ + "name": "next", + "version": "15.5.10", + "licenses": [{ "license": { "id": "MIT" } }], + "purl": "pkg:npm/next@15.5.10", + "properties": [ + { "name": "syft:package:language", "value": "javascript" }, + { "name": "syft:package:type", "value": "npm" } + ] +} +``` + +El campo `purl` (*Package URL*) es el identificador estándar que usan +Trivy, Syft, GitHub Advisories y la mayoría de las bases de datos de +CVEs para referirse a "este paquete, en este ecosistema, en esta +versión" — es lo que hace posible cruzar un SBOM contra un aviso de +seguridad de forma automática, sin depender de que el nombre coincida +exactamente en texto libre. + +!!! example "El SBOM también sirve para encontrar cosas que sobran" + Revisando el inventario real apareció `yarn@1.22.22` — viene + empaquetado en la imagen base de Node igual que el `npm` que ya se + sacó del stage final en el fix de [Trivy](trivy.md). No es una + vulnerabilidad activa hoy, pero es exactamente el tipo de hallazgo + que un SBOM hace visible para revisar después: herramientas que + viajan en la imagen de producción sin que el runtime las necesite. + +## Cómo se consulta + +El SBOM se publica como artifact del pipeline (`sbom-v1.0.X`, con ambos +archivos) en cada build. Para consultarlo: + +1. Descargar el artifact de la corrida del pipeline que te interesa + (o del último build de `main`, para saber qué corre en producción + ahora mismo). +2. Buscar el paquete en cuestión: + ```bash + python3 -c " + import json + d = json.load(open('sbom.cdx.json')) + for c in d['components']: + if c['name'] == 'next': + print(c['name'], c['version']) + " + ``` + (o `grep -A3 '"name": "next"' sbom.cdx.json` si no hay Python a mano). +3. Si el paquete aparece, confirmar la versión contra el aviso de + seguridad para saber si aplica. + +No hace falta memorizar el formato — el punto de tener el SBOM ya +generado es no depender de reconstruir esta información bajo presión +el día que aparezca la próxima CVE grande. diff --git a/workloads/docs-portal/mkdocs.yml b/workloads/docs-portal/mkdocs.yml index ee971a8..67e414e 100644 --- a/workloads/docs-portal/mkdocs.yml +++ b/workloads/docs-portal/mkdocs.yml @@ -102,6 +102,7 @@ nav: - Gitleaks (secretos): devsecops/gitleaks.md - Trivy (imagen + IaC): devsecops/trivy.md - SAST (Semgrep): devsecops/sast.md + - SBOM (Syft): devsecops/sbom.md extra: social: