Módulo 8: Capstone The Andes Cargo Security Gate
2. Repaso de arquitectura: el security gate completo
Descripción
Antes de tocar una sola línea de YAML, esta lección dibuja el mapa completo: dónde vive cada control nuevo, en qué archivo, disparado por qué evento, y qué pasa exactamente cuando uno de ellos falla. Es una lección de lectura, no de ejecución — el mapa que vas a construir aquí es el que la lección 3 convierte en ci.yml real.
Conexión con el módulo
cicd-and-gitops-on-aws-guide dejó dos archivos —ci.yml (dispara en cada Pull Request, corre terraform plan) y apply.yml (dispara en cada push a main, corre terraform apply)—. Los Módulos 5 y 6 de esta guía ya tocaron ambos: el Módulo 5, lección 7, agregó un step de Trivy dentro del job terraform-checks de ci.yml; el Módulo 6, lección 8, agregó un job verify-artifact completo a apply.yml. Este módulo capstone hace algo distinto de los dos: reorganiza el control de política y el de escaneo en jobs propios, nombrados, encadenados con needs:, dentro de ci.yml — el gate completo, visible como una sola cadena, no como pasos dispersos dentro de un job con otro propósito.
Analogía: el plano del aeropuerto, antes de construir los mostradores
La lección 1 comparó el gate con los controles en cadena de un aeropuerto. Esta lección es el plano arquitectónico de ese aeropuerto: antes de instalar el primer detector de metales, alguien decide dónde va cada control, qué pasa si alguien no lo pasa, y por qué el orden es ese y no otro. Un plano mal pensado pondría el control de pasaporte después del de equipaje —técnicamente funciona, pero desperdicia el tiempo de todos los pasajeros cuyo pasaporte de todos modos no era válido—. Esta lección es ese ejercicio de planeación, hecho a propósito antes de la lección 3.
El diagrama completo: plan → conftest → Trivy → cosign → apply
PULL REQUEST (ci.yml)
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────────────┐ │
│ │ terraform │ │ conftest │ │ Trivy │ │
│ │ plan + show │ ───► │ policy-check│ ───► │ iac-scan │ │
│ │ -json │ │ (Módulo 4) │ │ (Módulo 5) │ │
│ └─────────────┘ └──────┬──────┘ └──────────┬─────────┘ │
│ │ FAIL │ FAIL │
│ ▼ ▼ │
│ 🛑 detiene aquí 🛑 detiene aquí │
│ │
│ ┌───────────────┐ │
│ │ cosign │ │
│ │verify-artifact│ │
│ │ (Módulo 6) │ │
│ └───────┬───────┘ │
│ │ FAIL │
│ ▼ │
│ 🛑 detiene aquí │
└──────────────────────────────────────────────────────────────────────┘
│ los tres PASS
▼
MERGE a main → push (apply.yml)
┌──────────────────────────────────────────────────────────────────────┐
│ fetch-reviewed-plan ──┐ │
│ ├──► needs: [ambos] ──► terraform-apply │
│ verify-artifact ───────┘ (Módulo 6.8, ya heredado) │
│ (segunda verificación, defensa en profundidad) │
└──────────────────────────────────────────────────────────────────────┘
Dos observaciones que gobiernan todo lo que sigue:
verify-artifact aparece dos veces, y no es un error. El Módulo 6, lección 8, ya agregó un job verify-artifact a apply.yml, corriendo justo antes de terraform-apply — la última verificación posible, en el momento exacto antes de que algo se despliegue. Este módulo agrega la misma verificación, más temprano, dentro de ci.yml, como parte de la cadena de Pull Request. No es redundancia desperdiciada: es defensa en profundidad, el mismo principio que ya viste con conftest/Trivy evaluando capas distintas del mismo plan. Verificar la firma en el PR le da al equipo revisor una señal temprana ("este artefacto ya está roto, ni te molestes en aprobar este PR"); verificar de nuevo en apply.yml protege contra el caso, poco probable pero real, de que algo cambie entre el momento del PR y el momento del merge.
Cada 🛑 es literal, no decorativo. needs: en GitHub Actions —y en act, que lo respeta con la misma semántica— significa que un job no se dispara en absoluto si el que necesita falló. No es que corra y falle rápido: no corre. La lección 5 de este módulo lo confirma con un log real donde iac-scan y verify-artifact no aparecen ni una sola vez, en ningún punto, después de que policy-check falla.
El mismo diagrama, como flujo secuencial
flowchart LR
A[terraform plan] --> B[terraform show -json]
B --> C{policy-check<br/>conftest}
C -->|FAIL| X1[🛑 detenido]
C -->|PASS| D{iac-scan<br/>Trivy}
D -->|FAIL| X2[🛑 detenido]
D -->|PASS| E{verify-artifact<br/>cosign}
E -->|FAIL| X3[🛑 detenido]
E -->|PASS| F[merge a main]
F --> G[apply.yml: verify-artifact + terraform-apply]
ci.yml, antes y después de este módulo
cicd-and-gitops-on-aws-guide dejó ci.yml con un solo job, terraform-checks, con nueve steps corriendo en secuencia dentro de ese único job. El Módulo 5 de esta guía insertó dos steps más (instalar Trivy, correr trivy config) dentro de ese mismo job, entre Terraform format check y Terraform init. Este módulo cambia la forma, no solo el contenido:
| Antes de este módulo | Después de este módulo | |
|---|---|---|
Jobs en ci.yml | 1 (terraform-checks, con Trivy como dos steps internos) | 4 (policy-check, iac-scan, verify-artifact, más terraform-checks sin cambios) |
| Trivy vive en... | Un step dentro de terraform-checks | Su propio job, iac-scan |
conftest vive en... | En ningún lado de ci.yml — solo corrido a mano en M4 | Su propio job, policy-check |
cosign vive en... | Solo en apply.yml (M6.8) | En apply.yml y en su propio job de ci.yml, verify-artifact |
| Visibilidad en la interfaz de GitHub Actions | Un solo ícono de éxito/fallo para todo el job | Cuatro íconos independientes, uno por control |
¿Por qué separar Trivy de terraform-checks en vez de dejarlo donde el Módulo 5 lo puso? La misma razón que el Ejercicio 2 del Módulo 6, lección 8, ya adelantó sobre verify-artifact: un job separado da visibilidad independiente —si iac-scan falla, se ve como un fallo de escaneo, no como "el job de Terraform falló, hay que investigar cuál de sus quince steps fue"—, y permite paralelismo real cuando needs: lo permite. terraform-checks (el job original, con fmt/init/validate/plan) sigue existiendo sin cambios — este módulo no lo toca, solo agrega los tres jobs nuevos junto a él.
Qué controla cada job, exactamente
No es una repetición vacía de M4/M5/M6 — es la versión "qué corre en CI" de cada uno, con el comando exacto que la lección 3 va a poner dentro de cada job:
| Job | Herramienta | Qué evalúa | Comando central |
|---|---|---|---|
policy-check | conftest 0.69.0 | El plan de Terraform, convertido a JSON | conftest test tfplan.json -p policy/ |
iac-scan | Trivy 0.74.0 | El HCL crudo, sin necesidad de ningún plan | trivy config --exit-code 1 --severity CRITICAL,HIGH . |
verify-artifact | cosign v3.1.3 | El artefacto de despliegue (lambda/function.zip) contra su firma | cosign verify-blob --key cosign.pub --bundle manifest.sig --insecure-ignore-tlog=true lambda/function.zip |
Fíjate en una asimetría real: policy-check necesita que terraform plan/terraform show -json corran primero, dentro del mismo job (son sus propios steps previos) — depende del estado calculado del proyecto. iac-scan no necesita ningún plan: evalúa el HCL tal como está en el repositorio, sin ningún paso de Terraform de por medio (la misma razón, ya explicada en el Módulo 5, lección 7, de por qué ese control puede correr antes que terraform init). verify-artifact tampoco necesita Terraform en absoluto: solo necesita que lambda/function.zip, cosign.pub y manifest.sig existan en el repositorio, los tres ya committeados desde el Módulo 6. Tres controles, tres dependencias completamente distintas — la razón técnica exacta por la que pueden vivir en jobs separados en vez de forzarlos dentro de un solo job secuencial.
Errores comunes
Asumir que needs: [policy-check, iac-scan] (una lista) y needs: policy-check seguido de needs: iac-scan en jobs separados son lo mismo. Qué pasa: alguien, diseñando el ci.yml de la lección 3 de memoria, escribe verify-artifact con needs: [policy-check, iac-scan] en vez de encadenarlo solo a iac-scan (que a su vez depende de policy-check). Cómo detectarlo: con la lista, verify-artifact esperaría a que ambos terminen — funcionalmente equivalente en este caso específico, porque iac-scan ya depende de policy-check, pero conceptualmente distinto: una cadena lineal (A → B → C) comunica una secuencia; una lista de dependencias (C needs: [A, B]) comunica un punto de sincronización entre ramas paralelas. Cómo corregirlo: para una cadena secuencial como este gate, cada job depende únicamente del que le precede directamente —iac-scan de policy-check, verify-artifact de iac-scan—, nunca de todos los anteriores a la vez, exactamente como la lección 3 lo construye.
Olvidar que un job sin ningún needs: corre en paralelo con los demás, no antes. Qué pasa: alguien agrega un cuarto job al ci.yml de este módulo, sin needs:, asumiendo que "va a correr después de los otros tres porque lo escribí más abajo en el archivo". Cómo detectarlo: si tu razonamiento sobre el orden de ejecución se basa en la posición del job dentro del archivo YAML. Cómo corregirlo: GitHub Actions —y act— no ejecutan jobs en el orden en que aparecen en el archivo; ejecutan según el grafo de dependencias que needs: declara explícitamente. Un job sin needs: arranca tan pronto el workflow se dispara, en paralelo con cualquier otro job sin needs:, sin importar dónde esté escrito en el archivo.
Pensar que el segundo verify-artifact (en apply.yml) es redundante y se puede quitar. Qué pasa: alguien, al notar que ci.yml ya verifica la firma en el PR, propone eliminar el job equivalente de apply.yml para "no repetir trabajo". Cómo detectarlo: si tu argumento es "ya se verificó una vez, no hace falta de nuevo". Cómo corregirlo: la sección de esta lección sobre defensa en profundidad ya lo explica — son dos momentos distintos (revisión de PR vs. justo antes de desplegar), y el costo de correr cosign verify-blob de nuevo (segundos) es insignificante comparado con el riesgo de desplegar un artefacto que cambió entre esos dos momentos sin que nadie lo note.
Ejercicios
Ejercicio 1 — Dibuja, en texto, qué pasaría si iac-scan no tuviera needs: policy-check. Sin mirar el ci.yml de la lección 3 todavía, describe el comportamiento del pipeline si iac-scan no declarara ninguna dependencia: ¿en qué orden correrían los tres jobs, y qué pasaría si policy-check falla?
Ver solución
Sin needs:, los tres jobs (policy-check, iac-scan, verify-artifact) correrían en paralelo, todos disparados al mismo tiempo por el evento pull_request, sin ninguna relación entre ellos. Si policy-check falla, iac-scan y verify-artifact seguirían corriendo de todos modos —ya estarían en marcha, sin ninguna señal que los detenga—, terminando cada uno con su propio resultado independiente. El pipeline completo seguiría reportándose como fallido (GitHub Actions marca el workflow como fallido si cualquier job falla), pero se habría gastado tiempo de runner en iac-scan y verify-artifact sobre un cambio que, de todos modos, policy-check ya rechazó — exactamente el desperdicio que la sección "por qué el orden importa" de la lección 1 identificó, ahora confirmado con el mecanismo técnico preciso que lo evita.
Ejercicio 2 — Explica por qué terraform-checks (el job original, con Trivy como step interno desde el Módulo 5) no se elimina ni se fusiona con iac-scan. Un compañero pregunta: si iac-scan ya corre Trivy en su propio job, ¿por qué dejar el step de Trivy duplicado dentro de terraform-checks también?
Ver solución
En realidad no queda duplicado si se hace bien: la lección 3 de este módulo mueve el step de Trivy fuera de terraform-checks hacia el nuevo job iac-scan, no lo copia. terraform-checks conserva fmt/init/validate/plan —el trabajo que sí necesita el motor de Terraform completo, incluida la conexión representativa a LocalStack para awslocal s3 ls—, mientras que iac-scan se queda solo con el escaneo, que nunca necesitó ese motor en primer lugar. Fusionarlos de vuelta en un solo job perdería exactamente la ventaja que la sección "¿por qué separar Trivy...?" de esta lección ya explicó: visibilidad independiente y la posibilidad de que el gate falle rápido, en el job más barato, sin arrastrar todo el trabajo de Terraform detrás.
Ejercicio 3 — Predice el resultado de correr act pull_request -j verify-artifact directamente (acotado a un solo job), sin haber corrido policy-check ni iac-scan primero. ¿act respeta needs: cuando le pides un solo job específico con -j, o lo corre de todos modos?
Ver solución
act -j <job> corre solo el job pedido, ignorando sus dependencias declaradas en needs: — es, deliberadamente, una forma de aislar un job para depurarlo, la misma que el Módulo 6, lección 8, ya usó (act push -j verify-artifact -W .github/workflows/apply.yml) para probar ese job en aislamiento. Esto significa que act pull_request -j verify-artifact correría ese job sin que policy-check ni iac-scan se hayan ejecutado siquiera, dando un resultado potencialmente engañoso si lo confundes con una corrida real del pipeline completo. Para observar el comportamiento real del needs: encadenado —el que la lección 5 de este módulo depende de demostrar— hace falta correr act pull_request sin el flag -j, dejando que act resuelva el grafo de dependencias completo por su cuenta.
Resumen y siguiente paso
Esta lección dibujó el mapa completo antes de construir nada: tres jobs nuevos (policy-check, iac-scan, verify-artifact) encadenados con needs: dentro de ci.yml, cada uno con una dependencia distinta (el plan, el HCL crudo, el artefacto committeado), y una cuarta pieza —el verify-artifact de apply.yml, heredado del Módulo 6— que no se toca, sino que se entiende como defensa en profundidad, no redundancia. Confirmaste, con el diagrama y la tabla de antes/después, por qué Trivy se mueve de un step interno a su propio job, y por qué el orden de los tres jobs nuevos —policy antes que escaneo, escaneo antes que firma— sigue el mismo criterio de costo creciente que ya viste en el Módulo 5.
La lección 3 convierte este diagrama en YAML real, y lo corre con act pull_request contra andes-cargo-infra/.
Recursos
- GitHub Docs —
jobs.<job_id>.needs— la referencia oficial del mecanismo de dependencias entre jobs que gobierna todo este módulo. - Este curso, Módulo 5, lección 7 — el origen del criterio "falla rápido, en el paso más barato primero" que este módulo aplica un nivel más arriba, a jobs completos.
- Este curso, Módulo 6, lección 8 — el
apply.ymlconverify-artifactque este módulo no modifica, solo referencia como defensa en profundidad. cicd-and-gitops-on-aws-guide, Módulo 3, lección 8 — elci.ymloriginal de nueve steps, el punto de partida exacto de este módulo.