feat(devsecops): generar SBOM (Syft) de la imagen del frontend
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).
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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/[email protected]",
|
||||
"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ó `[email protected]` — 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.
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user