El pipeline completo
Llegamos al final. Tenemos nueve herramientas sueltas y toca convertirlas en algo que funcione solo, en cada push, sin que nadie tenga que acordarse.
Las dos decisiones que importan
Antes del YAML, dos decisiones que determinan si el pipeline sobrevive al primer mes.
1. Informar de todo, bloquear por poco
Es la regla que ha ido apareciendo en todos los capítulos, y aquí es donde se materializa. Cada herramienta se ejecuta dos veces, conceptualmente:
- Una que reporta absolutamente todo y nunca falla
- Otra que bloquea solo lo crítico y con parche disponible
Así tienes visibilidad completa sin que el equipo se pelee con el pipeline. Un pipeline que falla por cosas que nadie puede arreglar hoy se acaba desactivando, y entonces no proteges nada.
2. Los hallazgos van donde la gente mira
Un informe en los logs de un job no lo lee nadie. SARIF (Static Analysis Results Interchange Format) es el formato estándar de resultados de análisis, y GitHub lo entiende: al subirlo, los hallazgos aparecen en la pestaña Security del repositorio y como comentarios en línea dentro del pull request.
Es la diferencia entre un informe que hay que ir a buscar y un aviso que aparece donde ya estás trabajando.
El pipeline
Cinco trabajos que se ejecutan en paralelo, uno por cada etapa que hemos visto:
# .github/workflows/devsecops.yml
name: DevSecOps
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
security-events: write # necesario para subir los SARIF
Ese bloque permissions es el error número uno. Sin security-events: write, la subida del SARIF falla con un permiso denegado y te quedas sin hallazgos en ningún sitio.
Secretos
secretos:
name: Secretos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # sin esto solo se analiza el último commit
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SAST
sast:
name: SAST
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- run: |
semgrep --config=auto --config=.semgrep.yml \
--sarif --output=semgrep.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
category: semgrep
Dos detalles: el || true evita que un hallazgo tumbe el job antes de subir el informe, y el --config=.semgrep.yml incluye nuestra regla de inyección SQL del capítulo 4.
Dependencias
dependencias:
name: Dependencias
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scanners: vuln
format: sarif
output: trivy-fs.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-fs.sarif
category: trivy-fs
Infraestructura
iac:
name: Infraestructura
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@master
with:
directory: infra/
framework: terraform
soft_fail: true # informa, todavía no bloquea
output_format: cli,sarif
output_file_path: console,checkov.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
category: checkov
Imagen, con el gate
Aquí se ve el patrón de los dos pasos en su forma más clara:
imagen:
name: Imagen
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t demo:${{ github.sha }} .
# 1) Informa de TODO, sin bloquear
- uses: aquasecurity/trivy-action@master
with:
image-ref: demo:${{ github.sha }}
format: sarif
output: trivy-image.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-image.sarif
category: trivy-image
# 2) Bloquea SOLO por lo crítico y con parche disponible
- uses: aquasecurity/trivy-action@master
with:
image-ref: demo:${{ github.sha }}
severity: CRITICAL
ignore-unfixed: true
exit-code: '1'
El category de cada subida es importante: sin él, cada análisis sobrescribe al anterior y solo verías los hallazgos de la última herramienta.
Ejecutarlo
Este pipeline está en el repositorio del curso, y esta es su ejecución real:
✓ Secretos in 18s
✓ SAST in 34s
✓ Dependencias in 24s
✓ Infraestructura in 30s
✗ Imagen in 52s ← el gate ha bloqueado
Menos de un minuto para las cinco etapas, porque van en paralelo.
Y el job de la imagen ha fallado, que es exactamente lo que queríamos:
Total: 11 (CRITICAL: 11)
git CVE-2024-32002 CRITICAL fixed 1:2.20.1-2+deb10u8 → 1:2.20.1-2+deb10u9
git: Recursive clones RCE
Once vulnerabilidades críticas con parche disponible, heredadas del FROM node:16. El pipeline no deja pasar la imagen y te dice exactamente qué hacer. Es el capítulo 5 aplicado automáticamente.
El resultado en la pestaña Security
Los cuatro informes SARIF suben correctamente:
| Herramienta | Categoría | Resultados |
|---|---|---|
| Trivy | trivy-image | 1054 |
| Semgrep OSS | semgrep | 26 |
| checkov | checkov | 27 |
| Trivy | trivy-fs | 20 |
Todo eso aparece en Security → Code scanning, filtrable por herramienta y severidad, y con cada hallazgo enlazado a su línea de código.
Aquí está la razón real de todo el curso: 1127 hallazgos que antes no existían para nadie, ahora en un sitio donde el equipo los ve sin buscarlos.
Mil hallazgos no es una victoria, es una lista de tareas intimidante. Por eso el soft_fail y el ignore-unfixed: te centras en las 11 críticas accionables, no en las 1054 totales.
El primer día, ordena por "tiene parche" y no por severidad.
Cuánto cuesta cada control
No todo se puede ejecutar en cada push:
| Control | Duración | Cuándo |
|---|---|---|
| gitleaks | segundos | Cada push |
| Semgrep | ~30 s | Cada push |
Trivy fs | ~25 s | Cada push |
| Checkov | ~30 s | Cada push |
Trivy image | ~1 min | Cada build |
| SBOM y firma | ~1 min | Cada release |
| ZAP pasivo | minutos | Tras desplegar en staging |
| ZAP activo | decenas de minutos | Semanal, programado |
La regla: lo que tarda segundos va en cada push. Lo que tarda minutos, en el build. Lo que tarda mucho, programado por la noche.
Cómo implantarlo sin que te lo desactiven
Si mañana enchufas esto en un repositorio con cinco años de historia, saldrán cientos de hallazgos y el equipo pedirá quitarlo. Con razón.
El camino que funciona:
- Semana 1 — todo en modo informe. Nada bloquea. Solo mides.
- Semana 2 — congela la deuda: bloquea únicamente lo que se añade nuevo (
--baseline-commiten Semgrep, comparar contramain). - Mes 2 — activa el gate de secretos. Es el menos discutible: nadie defiende que hay que poder subir contraseñas.
- Mes 3 — gate de vulnerabilidades críticas con parche.
- Después — ve bajando el listón según el equipo se acostumbre.
Y algo que no está en ningún YAML: cuando el pipeline bloquee a alguien, ayúdale a arreglarlo. La primera vez que un control frena a un compañero decide si el DevSecOps de tu equipo se percibe como ayuda o como obstáculo.
Resumen
- Cinco trabajos en paralelo cubren las cinco etapas en menos de un minuto
- Informar de todo, bloquear solo lo crítico con parche disponible
permissions: security-events: writeo los SARIF no subencategorydistinta por herramienta o se sobrescriben entre ellas- El gate bloqueó por 11 críticas heredadas de la imagen base: el capítulo 5, automatizado
- 1127 hallazgos ahora visibles donde el equipo ya trabaja
- Implanta por fases: informar, congelar la deuda, y bloquear cuando sea asumible
Has terminado el curso
Repasando lo que has montado: detección de secretos antes del commit, análisis de dependencias y de código en cada push, escaneo y firma del artefacto, auditoría de la infraestructura antes de crearla y ataque a la aplicación ya desplegada.
Eso es DevSecOps. No es una herramienta ni un puesto: es que la seguridad deje de ser una revisión al final y pase a ser algo que ocurre solo, en cada paso.
Por dónde seguir:
- Kubernetes — seguridad del cluster, Falco, OPA y la preparación del CKS
- DevOps — el curso completo, si te has saltado los fundamentos
- Cadena de suministro — SLSA y attestations, el siguiente escalón después de la firma
- Threat modeling — pensar los ataques antes de escribir el código
Recursos adicionales
- Repositorio de prácticas del curso
- Ruta DevSecOps, recomendaciones para empezar
- OWASP DevSecOps Guideline
- Documentación de SARIF en GitHub
💡 Recuerda: el mejor pipeline de seguridad no es el que más cosas bloquea, es el que el equipo no quiere desactivar.
Gracias por llegar hasta aquí. Si el curso te ha servido, deja una estrella en el repositorio y suscríbete al canal. 🛡️