Módulo 5: Scanning Iac And Dependencies
8. Proyecto: el reporte de postura de seguridad de Andes Cargo
Descripción
Este es el proyecto que cierra el Módulo 5. Reúne, sobre el andes-cargo-infra/ completo —dieciocho recursos gestionados, endurecido por los Módulos 1 a 4—, el escaneo final de Trivy y Checkov, con cada hallazgo restante clasificado sin ambigüedad: arreglado, suprimido con razón documentada, o aceptado y nombrado. El entregable es doble: un reporte legible por humanos, y dos pares de archivos SARIF/JSON —uno por herramienta— listos para integrarse en cualquier plataforma que consuma ese formato estándar. Al cerrar este proyecto, andes-cargo-infra/ no tiene un solo hallazgo CRITICAL o HIGH sin justificar.
Conexión con el módulo
Las lecciones 4 a 7 de este módulo hicieron el trabajo pieza por pieza: escanear, comparar, decidir, automatizar. Este proyecto es la fotografía final — el estado real del proyecto después de que todas esas decisiones se aplicaron, documentado de una forma que alguien que nunca vio este módulo pueda auditar sin tener que reconstruir el razonamiento completo.
El recorrido completo, en una tabla
| Momento | Hallazgos Trivy | Hallazgos Checkov (fallidos) | Qué cambió |
|---|---|---|---|
| Lección 4 (primer escaneo) | 11 | — | Estado real de andes-cargo-infra/ después de M1-M3 |
| Lección 5 (Checkov corrido) | 11 | 15 | Segundo escáner, mismo HCL, 113 controles pasados |
| Lección 6 (arreglar + suprimir x2) | 9 | 13 (+1 omitido) | point_in_time_recovery agregado; CKV_AWS_144 y AWS-0089 suprimidos con razón |
Lección 7 (gate de CI, AWS-0132 suprimido) | 8 (CRITICAL/HIGH) → 0 | — | El gate automático fuerza la decisión sobre el último HIGH |
| Este proyecto (final) | 3 | 12 (+1 omitido) | El bucket público-bloqueado agregado; reporte consolidado |
Paso 1 — El HCL nuevo de este proyecto: cerrando TM-04 de verdad
Los cinco hallazgos HIGH de la lección 4 —AWS-0086, AWS-0087, AWS-0091, AWS-0093, AWS-0094— comparten una sola causa raíz, ya identificada: el bucket andes-cargo-shipment-docs nunca declaró aws_s3_bucket_public_access_block. Es, letra por letra, TM-04 de THREAT-MODEL.md (Módulo 1) — el mismo hallazgo que el no-public-buckets.rego del Módulo 4 evalúa de forma preventiva sobre cualquier plan futuro. Ese policy Rego puede bloquear un cambio que quite esta protección mañana — pero necesita que la protección exista hoy para tener algo que proteger. Este proyecto es el que la hace existir:
# modules/s3-bucket/main.tf
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Un recurso nuevo, cuatro banderas, todas en true — el arreglo directo que las lecciones 4 y 6 de este módulo ya anticiparon como candidato ideal (barato, sin efectos secundarios, sobre el hallazgo de mayor severidad de todo el escaneo).
terraform fmt -recursive
terraform validate
Qué esperar (literal):
Success! The configuration is valid.
Paso 2 — El escaneo final, Trivy
trivy config .
Qué esperar (literal — ejecutado para escribir esta lección, sobre el HCL completo del proyecto):
Report Summary
┌───────────────────────────┬───────────┬───────────────────┐
│ Target │ Type │ Misconfigurations │
├───────────────────────────┼───────────┼───────────────────┤
│ . │ terraform │ 0 │
├───────────────────────────┼───────────┼───────────────────┤
│ dynamodb.tf │ terraform │ 1 │
├───────────────────────────┼───────────┼───────────────────┤
│ lambda.tf │ terraform │ 1 │
├───────────────────────────┼───────────┼───────────────────┤
│ modules/s3-bucket/main.tf │ terraform │ 0 │
├───────────────────────────┼───────────┼───────────────────┤
│ secrets.tf │ terraform │ 1 │
└───────────────────────────┴───────────┴───────────────────┘
modules/s3-bucket/main.tf pasó de siete hallazgos (lección 4) a cero. El recurso del Paso 1 resolvió, de un solo cambio, los cinco HIGH del bloqueo de acceso público — AWS-0132 y AWS-0089, los dos hallazgos restantes de ese archivo, ya estaban cubiertos por las supresiones documentadas de las lecciones 6 y 7. Quedan tres hallazgos en todo el proyecto:
trivy config --format json . | python3 -c "
import json, sys
d = json.load(sys.stdin)
for res in d.get('Results', []):
for m in res.get('Misconfigurations') or []:
print(res['Target'], '|', m['ID'], '|', m['Severity'])
"
Qué esperar (literal):
dynamodb.tf | AWS-0025 | LOW
lambda.tf | AWS-0066 | LOW
secrets.tf | AWS-0098 | LOW
Cero CRITICAL. Cero HIGH. Cero MEDIUM. Los tres hallazgos que quedan son, los tres, LOW, y los tres comparten la misma naturaleza: cifrado o trazabilidad con la clave/configuración administrada por AWS en vez de un mecanismo administrado por el cliente — nunca un vector de acceso no autorizado.
Paso 3 — El escaneo final, Checkov
checkov -d . --compact --quiet
Qué esperar (literal — ejecutado para escribir esta lección):
Passed checks: 119, Failed checks: 12, Skipped checks: 1
Ciento diecinueve controles pasados, sobre dieciocho recursos gestionados — más del noventa por ciento del catálogo aplicable de Checkov para este proyecto. Los doce que fallan, y el uno que quedó omitido con razón documentada, están clasificados uno por uno en la tabla siguiente.
La tabla de clasificación final: cero hallazgos sin justificar
| ID (Trivy / Checkov) | Severidad | Estado | Justificación |
|---|---|---|---|
AWS-0086/0087/0091/0093/0094 / CKV2_AWS_6 | HIGH / — | ✅ Arreglado | aws_s3_bucket_public_access_block agregado (Paso 1 de este proyecto) |
AWS-0024 / CKV_AWS_28 | MEDIUM | ✅ Arreglado | point_in_time_recovery agregado (lección 6) |
CKV_AWS_144 | — | 🟡 Suprimido | Región única por diseño (us-east-1); ver #checkov:skip= en modules/s3-bucket/main.tf (lección 6) |
AWS-0089 | LOW | 🟡 Suprimido | Registro de acceso fuera de alcance $0; CloudTrail (Módulo 7) cubre parcialmente; ver .trivyignore (lección 6) |
AWS-0132 / CKV_AWS_145 | HIGH | 🟡 Suprimido (Trivy) / visible (Checkov) | KMS CMK sin vía $0 en LocalStack; ver .trivyignore (lección 7). Checkov no lee .trivyignore — el hallazgo sigue visible ahí, la misma justificación aplica |
AWS-0025 / CKV_AWS_119 | LOW | 🔵 Aceptado, visible | Misma razón de costo de KMS CMK que AWS-0132, sin suprimir — DynamoDB, no S3 |
AWS-0066 / CKV_AWS_50 | LOW | 🔵 Aceptado, visible | X-Ray tracing: observabilidad, no seguridad de acceso; fuera del alcance de esta guía |
AWS-0098 / CKV_AWS_149 | LOW | 🔵 Aceptado, visible | Misma razón de costo de KMS CMK, sobre el secreto de Secrets Manager |
CKV_AWS_337 | — | 🔵 Aceptado, visible | Misma razón de costo de KMS CMK, sobre el parámetro de SSM |
CKV2_AWS_57 | — | 🔵 Aceptado, nombrado | Rotación automática de Secrets Manager — mecanismo real explicado, no ejecutado de punta a punta (Módulo 3, lección 6) |
CKV_AWS_117 | — | 🔵 Aceptado, visible | Lambda fuera de VPC: solo accede a S3/DynamoDB (servicios administrados alcanzables sin VPC); agregar VPC exige NAT Gateway (costo real, sin vía $0) |
CKV_AWS_116 | — | 🔵 Aceptado, visible | Cola de mensajes fallidos (DLQ): hardening operacional, terreno de sre-and-incident-response-guide |
CKV_AWS_115 | — | 🔵 Aceptado, visible | Límite de concurrencia: control de costo/disponibilidad, no de acceso — frontera con finops-and-cost-guardrails-guide |
CKV_AWS_272 | — | 🔵 Aceptado, nombrado | Firma de código de Lambda — el mismo riesgo (TM-02) que el Módulo 6 resuelve con cosign/SBOM, por una vía independiente de la funcionalidad nativa de AWS |
CKV2_AWS_61 | — | 🔵 Aceptado, visible | Ciclo de vida de objetos S3: control de costo, no de seguridad |
AWS-0089 (Checkov: CKV_AWS_18) | — | 🔵 Aceptado, visible (Checkov) | Igual que la fila de AWS-0089 arriba — Checkov, sin .trivyignore, sigue mostrándolo |
Cero filas sin una razón escrita. Cero hallazgos CRITICAL en ninguna de las dos herramientas. Los dos HIGH que existieron en algún momento de este módulo (AWS-0086-família y AWS-0132) están, uno arreglado, el otro suprimido con razón — ninguno queda abierto sin explicación.
Paso 4 — Generando los reportes SARIF/JSON, el entregable formal
trivy config --format json --output trivy-report.json .
trivy config --format sarif --output trivy-report.sarif .
checkov -d . -o json --output-file-path .
checkov -d . -o sarif --output-file-path .
Qué esperar (literal — cuatro archivos nuevos, confirmados con ls):
ls -la trivy-report.json trivy-report.sarif results_json.json results_sarif.sarif
-rw-r--r-- 1 user staff 15069 trivy-report.json
-rw-r--r-- 1 user staff 9353 trivy-report.sarif
-rw-r--r-- 1 user staff 30324 results_json.json
-rw-r--r-- 1 user staff 15095 results_sarif.sarif
Cuatro archivos, dos formatos por herramienta. SARIF (Static Analysis Results Interchange Format) es el estándar que GitHub, GitLab, y prácticamente cualquier plataforma de revisión de código consume de forma nativa para mostrar hallazgos de seguridad directamente sobre el diff de un Pull Request, línea por línea — es el formato que, en un equipo real, conectarías al step IaC security scan (Trivy) de la lección 7 con --format sarif en vez de la tabla de texto, para que los hallazgos aparezcan como comentarios en la revisión, no solo en el log del job.
Confirma que ambos SARIF son válidos y cuentan lo mismo que ya viste en las tablas de texto:
python3 -c "
import json
t = json.load(open('trivy-report.sarif'))
c = json.load(open('results_sarif.sarif'))
print('Trivy SARIF results:', len(t['runs'][0]['results']))
print('Checkov SARIF results:', len(c['runs'][0]['results']))
"
Qué esperar (literal):
Trivy SARIF results: 3
Checkov SARIF results: 13
Trivy: tres, coincide exactamente con el Paso 2. Checkov: trece —los doce FAILED más el uno SKIPPED, porque SARIF, a diferencia de la tabla --compact de terminal, sí incluye los resultados suprimidos como entradas marcadas con su propio campo de supresión (suppressions), en vez de omitirlos por completo — otra razón concreta para preferir el reporte estructurado sobre la tabla de texto cuando necesitas evidencia completa y auditable.
Actualizando RISK-MAP.md: el cierre formal de TM-04
- | 3 | TM-04 | Information disclosure | S3 bucket has no public-access block | `no-public-buckets.rego` (preventive) + community scanner remediation | M4 / M5 | Open |
+ | 3 | TM-04 | Information disclosure | S3 bucket has no public-access block | `no-public-buckets.rego` (preventive) + community scanner remediation | M4 / M5 | Resolved (M5.8): `aws_s3_bucket_public_access_block` declared in `modules/s3-bucket/`, confirmed via `trivy config` (5 HIGH findings → 0) and `checkov -d` (`CKV2_AWS_6` passing); `no-public-buckets.rego` now has a real protection to enforce going forward |
Con esta fila, RISK-MAP.md acumula tres de siete riesgos resueltos —TM-01 y TM-07 por el Módulo 2, TM-04 ahora por el Módulo 5— con la misma disciplina de evidencia en cada una: no solo "resuelto", sino con qué comando se confirmó.
Errores comunes
Confundir "cero hallazgos CRITICAL/HIGH sin justificar" con "cero hallazgos, punto" (de lectura del criterio de cierre). Qué pasa: alguien revisa este proyecto, ve Failed checks: 12 en Checkov, y concluye que el módulo no cumplió su propio objetivo. Cómo detectarlo: si tu criterio de éxito es un número en cero, sin revisar la tabla de clasificación. Cómo corregirlo: el criterio de este proyecto, declarado desde el diseño de la guía, es específicamente sobre severidad y justificación —no sobre un conteo absoluto—. Doce hallazgos LOW/sin severidad asignada, cada uno con una fila en la tabla de clasificación explicando por qué sigue ahí, es exactamente el resultado esperado de un proyecto real: no cero riesgo, riesgo entendido.
Suprimir un hallazgo en Trivy con .trivyignore y asumir que Checkov también lo suprimió. Qué pasa: alguien ve AWS-0132 desaparecer de trivy config después de la lección 7, y espera ver también CKV_AWS_145 desaparecer de checkov -d. Cómo detectarlo: la tabla de este proyecto lo marca explícitamente — AWS-0132/CKV_AWS_145 está "suprimido (Trivy) / visible (Checkov)", no suprimido en ambas. Cómo corregirlo: .trivyignore y #checkov:skip= son mecanismos completamente independientes, cada uno propio de su herramienta — suprimir en una nunca suprime en la otra. Si quieres que ambas herramientas dejen de mostrar el mismo hallazgo, necesitas aplicar el mecanismo de supresión de cada herramienta, con la misma razón documentada en ambos lugares.
Generar los reportes SARIF/JSON antes de terminar todas las correcciones, y no volver a generarlos (de secuencia). Qué pasa: alguien corre el Paso 4 de este proyecto antes del Paso 1 (el arreglo del bucket público), y termina con un trivy-report.json que todavía muestra los cinco HIGH como abiertos. Cómo detectarlo: si el conteo de tu reporte generado no coincide con la tabla de clasificación final de este proyecto. Cómo corregirlo: los reportes SARIF/JSON son una fotografía del estado en el momento exacto en que se generaron — cualquier cambio posterior al HCL exige regenerarlos. El orden de este proyecto (arreglar primero, reportar al final) no es arbitrario.
Ejercicios
Ejercicio 1 — Verifica de memoria por qué TM-04 involucra tanto al Módulo 4 como al Módulo 5. Sin volver a mirar, explica en dos o tres frases la relación entre no-public-buckets.rego (Módulo 4) y aws_s3_bucket_public_access_block (este proyecto).
Ver solución
no-public-buckets.rego es una política preventiva — evalúa cualquier plan futuro y bloquea un cambio que deje un bucket S3 sin bloqueo de acceso público, pero no puede, por sí sola, hacer que la protección exista si nunca existió. Este proyecto es el que declara el recurso real (aws_s3_bucket_public_access_block) que hace que la política tenga algo que proteger — sin este recurso, no-public-buckets.rego bloquearía cualquier intento de quitar una protección que, de hecho, nunca se declaró. Las dos piezas, preventiva (Módulo 4) y correctiva (Módulo 5), se completan mutuamente sobre el mismo hallazgo.
Ejercicio 2 — Calcula qué pasaría con el conteo de Checkov si también agregaras .trivyignore-equivalente para Checkov sobre CKV_AWS_145. Si suprimieras CKV_AWS_145 con un #checkov:skip= (la misma razón que ya tiene AWS-0132 en .trivyignore), ¿cuál sería el nuevo conteo de Passed/Failed/Skipped de Checkov?
Ver solución
Passed checks: 119 (sin cambio — suprimir no es lo mismo que pasar), Failed checks: 11 (baja de 12 a 11), Skipped checks: 2 (sube de 1 a 2). El total de controles evaluados (119 + 11 + 2 = 132) se mantiene constante — suprimir mueve un hallazgo de la columna Failed a la columna Skipped, nunca lo hace desaparecer del conteo total ni lo convierte en Passed, la misma distinción que la lección 6 ya estableció.
Ejercicio 3 — Diseña la siguiente fila que agregarías a la tabla de clasificación si el Módulo 6 resolviera CKV_AWS_272. Basándote en el patrón de la tabla de este proyecto, escribe cómo cambiaría la fila de CKV_AWS_272 una vez que el Módulo 6 firme el .zip de la Lambda con cosign.
Ver solución
| ID | Severidad | Estado | Justificación |
|---|---|---|---|
CKV_AWS_272 | — | 🟡 Mitigado por control equivalente (M6) | Firma de artefacto con cosign/Sigstore (Módulo 6) cubre la misma necesidad de integridad que Lambda Code Signing nativo verificaría — mecanismo distinto, mismo riesgo (TM-02) resuelto |
El estado pasa de "aceptado, nombrado" a algo más preciso que "arreglado": el hallazgo específico de Checkov (que pide la funcionalidad nativa de AWS Lambda Code Signing) sigue técnicamente sin resolverse tal como la regla lo pide —porque esta guía elige cosign en su lugar—, pero el riesgo de fondo que la regla protege ya tiene un control real cubriéndolo. Es una clasificación honesta que ninguna de las categorías binarias (arreglado/suprimido) captura del todo bien — otra razón para que la tabla de clasificación permita matices, no solo dos casillas.
Resumen y siguiente paso
Este proyecto cerró el Módulo 5 con el estado final de andes-cargo-infra/: aws_s3_bucket_public_access_block agregado, resolviendo los cinco hallazgos HIGH de Trivy y cerrando TM-04 de THREAT-MODEL.md de forma que complementa, no reemplaza, la política preventiva del Módulo 4. Trivy terminó con tres hallazgos, los tres LOW. Checkov terminó con doce fallidos y uno omitido, sobre ciento diecinueve pasados. Cada uno de los quince hallazgos que en algún momento existió en este módulo tiene una fila en la tabla de clasificación de este proyecto — arreglado, suprimido con razón, o aceptado y nombrado — sin ninguno escondido. Generaste los cuatro reportes SARIF/JSON, el entregable formal que cualquier plataforma de revisión de código consumiría directamente.
Antes de avanzar deberías poder: explicar la relación complementaria entre no-public-buckets.rego (Módulo 4) y el recurso HCL que este proyecto declaró; distinguir una supresión de Trivy de una de Checkov, y explicar por qué no se transfieren entre herramientas; y generar, para cualquier proyecto Terraform, un reporte SARIF de ambas herramientas listo para integrarse en una plataforma de revisión real.
Con esto, el Módulo 5 queda cerrado. Tienes reglas propias (Módulo 4) y reglas de la comunidad (este módulo) evaluando el mismo andes-cargo-infra/, con evidencia de que ambas capas encuentran cosas distintas, y un pipeline que las corre automáticamente en cada Pull Request.
Qué viene después
El Módulo 6 ataca el hueco de competencia más citado por la investigación de mercado de esta guía —cadena de suministro de software, cero en toda la competencia según VALIDACION.md—: qué es un SBOM, por qué importa, y cómo generar el de Andes Cargo con trivy fs --format cyclonedx sobre el handler de process-shipment-manifest. Después, firma y verificación del artefacto de despliegue con cosign/Sigstore, 100% offline, cerrando exactamente el hallazgo CKV_AWS_272 que este módulo dejó nombrado.
Recursos
- trivy.dev — SARIF output — documentación oficial del formato SARIF en Trivy.
- www.checkov.io — SARIF output — referencia de
-o sarifen Checkov. - SARIF — sarif OASIS standard — sitio de referencia del estándar SARIF (Static Analysis Results Interchange Format).
- Este curso, Módulo 1,
RISK-MAP.md(lección 8) — el documento que este proyecto actualiza con la tercera fila resuelta. - Este curso, Módulo 4 completo — el
no-public-buckets.regoque este proyecto complementa con el recurso HCL real.