Módulo 5: Securing The Ai Workload

8. Proyecto: el security gate de Andes Cargo, extendido

Descripción

Este proyecto reúne todo lo que las lecciones 1 a 7 construyeron —bedrock-least-privilege.rego, el .zip firmado de extract-shipment-manifest-fields, las dos filas nuevas de THREAT-MODEL.md— y confirma, con una corrida real de act pull_request contra Docker, que los tres jobs existentes de ci.ymlpolicy-check, iac-scan, verify-artifact, heredados sin ningún cambio de cloud-security-and-guardrails-guide— evalúan correctamente el Terraform y los artefactos nuevos. Ningún job nuevo. Ningún carril nuevo. La prueba final de la tesis completa de este módulo.

Conexión con el módulo

Este es el mismo ejercicio de auditoría dirigida que cada proyecto de cierre de módulo de este ecosistema practica: no "¿escribiste una política y firmaste un artefacto?", sino "¿puedes demostrar, con evidencia ejecutada, que el gate heredado evalúa esas dos piezas nuevas exactamente como evalúa cualquier otra?". RELIABILITY-CHARTER.md/ADR-001-llm-as-escalation-path.md (Módulo 1) tienen una fila para este módulo — este proyecto es la prueba de que esa fila se cumplió.


Paso 1 — El estado del proyecto, antes de correr nada

andes-cargo-infra/ con todo lo de este módulo integrado:

find . -maxdepth 2 -type f -newer THREAT-MODEL.md -not -path "./.terraform/*" | sort

Qué esperar (literal — los archivos que este módulo agregó o modificó, además de THREAT-MODEL.md mismo):

./bedrock.tf
./cosign.pub
./functions/extract-shipment-manifest-fields/function.zip
./functions/extract-shipment-manifest-fields/handler.py
./functions/extract-shipment-manifest-fields/manifest.sig
./guardrails/post_invoke_checks.py
./guardrails/pre_invoke_checks.py
./modules/bedrock-guardrail/main.tf
./modules/bedrock-guardrail/outputs.tf
./modules/bedrock-guardrail/variables.tf
./no-tlog-signing-config.json
./policy/bedrock-least-privilege.rego
./.github/workflows/ci.yml

ci.yml está en esta lista con un solo cambio real: el job verify-artifact gana un step más —no un job nuevo—:

       - name: Verify function.zip against manifest.sig
         run: |
           cosign verify-blob \
             --key cosign.pub \
             --bundle manifest.sig \
             --insecure-ignore-tlog=true \
             lambda/function.zip
+
+      - name: Verify extract-shipment-manifest-fields/function.zip against its manifest.sig
+        run: |
+          cosign verify-blob \
+            --key cosign.pub \
+            --bundle functions/extract-shipment-manifest-fields/manifest.sig \
+            --insecure-ignore-tlog=true \
+            functions/extract-shipment-manifest-fields/function.zip

Ni policy-check ni iac-scan necesitan ningún cambio de YAML — ambos ya evalúan "todo el proyecto" (policy/ completo, . completo), así que bedrock.tf, modules/bedrock-guardrail/ y policy/bedrock-least-privilege.rego entran a esos dos jobs automáticamente, sin que nadie tenga que nombrarlos explícitamente en el YAML.


Paso 2 — terraform plan, el proyecto completo con esta carga incluida

terraform plan -input=false -no-color -out=tfplan

Qué esperar (literal — resumen; el detalle completo de los 21 recursos ya se vio en el Módulo 3, lección 5 de esta guía, más los cuatro recursos que cloud-security-and-guardrails-guide agregó en sus propios módulos):

Plan: 21 to add, 0 to change, 0 to destroy.

Veintiuno, no diecisiete: los diecisiete que el Módulo 3, lección 5 de esta guía ya confirmó (bedrock.tf, el rol, el guardrail, más los catorce heredados de terraform-and-iac-guide/aws-serverless-and-containers-guide), más los recursos adicionales que cloud-security-and-guardrails-guide agregó en su propio recorrido (aws_s3_bucket_public_access_block, el proveedor OIDC, los secretos gestionados) — el proyecto completo, de punta a punta, sin que ninguna pieza de ninguna guía anterior se haya roto.


Paso 3 — conftest, la biblioteca completa de cuatro políticas

terraform show -json tfplan > tfplan.json
conftest test tfplan.json -p policy/

Qué esperar (literal — ejecutado para escribir esta lección):

6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions

Los seis tests de la lección 3 de este módulo, todos pasando contra el proyecto completo — no solo contra un tfplan.json aislado del Módulo 3, sino contra el plan real que resulta de todo lo que cloud-security-and-guardrails-guide y este módulo construyeron juntos.


Paso 4 — act pull_request: los tres jobs, con lo nuevo incluido

act pull_request -e .github/act-events/pr-event.json

Qué esperar (literal — ejecutado para escribir esta lección, con Docker real y la imagen catthehacker/ubuntu:act-latest; recortado a las líneas de progreso y resultado):

[ci/policy-check] ⭐ Run Main Terraform init
[ci/policy-check]   ✅  Success - Main Terraform init [13.60892375s]
[ci/policy-check] ⭐ Run Main Terraform plan
[ci/policy-check]   ✅  Success - Main Terraform plan [4.461012042s]
[ci/policy-check] ⭐ Run Main Convert plan to JSON
[ci/policy-check]   ✅  Success - Main Convert plan to JSON [1.710491292s]
[ci/policy-check] ⭐ Run Main Install conftest
[ci/policy-check]   ✅  Success - Main Install conftest [1.964748792s]
[ci/policy-check] ⭐ Run Main Evaluate the policy library against the plan
[ci/policy-check]   | 6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions
[ci/policy-check]   ✅  Success - Main Evaluate the policy library against the plan [292.428375ms]
[ci/policy-check] 🏁  Job succeeded

[ci/iac-scan    ] ⭐ Run Main Install Trivy
[ci/iac-scan    ]   ✅  Success - Main Install Trivy [3.896488416s]
[ci/iac-scan    ] ⭐ Run Main IaC security scan (Trivy)
[ci/iac-scan    ]   | Report Summary
[ci/iac-scan    ]   | ┌───────────────────────────┬───────────┬───────────────────┐
[ci/iac-scan    ]   | │          Target           │   Type    │ Misconfigurations │
[ci/iac-scan    ]   | ├───────────────────────────┼───────────┼───────────────────┤
[ci/iac-scan    ]   | │ .                         │ terraform │         0         │
[ci/iac-scan    ]   | ├───────────────────────────┼───────────┼───────────────────┤
[ci/iac-scan    ]   | │ dynamodb.tf               │ terraform │         0         │
[ci/iac-scan    ]   | ├───────────────────────────┼───────────┼───────────────────┤
[ci/iac-scan    ]   | │ lambda.tf                 │ terraform │         0         │
[ci/iac-scan    ]   | ├───────────────────────────┼───────────┼───────────────────┤
[ci/iac-scan    ]   | │ modules/s3-bucket/main.tf │ terraform │         0         │
[ci/iac-scan    ]   | ├───────────────────────────┼───────────┼───────────────────┤
[ci/iac-scan    ]   | │ secrets.tf                │ terraform │         0         │
[ci/iac-scan    ]   | └───────────────────────────┴───────────┴───────────────────┘
[ci/iac-scan    ]   ✅  Success - Main IaC security scan (Trivy) [1.967581333s]
[ci/iac-scan    ] 🏁  Job succeeded

[ci/verify-artifact] ⭐ Run Main Install cosign
[ci/verify-artifact]   ✅  Success - Main Install cosign [3.870082458s]
[ci/verify-artifact] ⭐ Run Main Verify function.zip against manifest.sig
[ci/verify-artifact]   | WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
[ci/verify-artifact]   | Verified OK
[ci/verify-artifact]   ✅  Success - Main Verify function.zip against manifest.sig [89.02475ms]
[ci/verify-artifact] ⭐ Run Main Verify extract-shipment-manifest-fields/function.zip against its manifest.sig
[ci/verify-artifact]   | WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
[ci/verify-artifact]   | Verified OK
[ci/verify-artifact]   ✅  Success - Main Verify extract-shipment-manifest-fields/function.zip against its manifest.sig [82.431584ms]
[ci/verify-artifact] 🏁  Job succeeded

Tres 🏁 Job succeeded, en el orden exacto de la cadena, con la infraestructura y los artefactos de este módulo incluidos de punta a punta. Fíjate en algo que vale la pena leer con atención: iac-scan no muestra ninguna fila para bedrock.tf ni modules/bedrock-guardrail/main.tf en su Report Summary — exactamente el resultado que la lección 5 de este módulo ya explicó con precisión: no porque Trivy los haya ignorado (el mismo --debug de esa lección ya confirmó que sí los parseó), sino porque su checks bundle todavía no tiene reglas específicas para aws_bedrock_guardrail, y porque BedrockManifestExtractorRole —vía modules/iam-role/— cumple limpio las reglas de IAM que sí existen. verify-artifact, en cambio, sí muestra dos Verified OK distintos: uno por cada artefacto firmado del proyecto, cada uno con su propio step, dentro del mismo job de siempre.


Cómo defender este trabajo en una entrevista

"¿Por qué no construyeron un job de CI específico para IA?" — la respuesta vive en la lección 1 de este módulo: un security gate bien diseñado generaliza a dominios que nadie tenía en mente cuando se construyó. policy-check no distingue el type de un recurso de Terraform; iac-scan escanea cualquier .tf; verify-artifact verifica cualquier .zip firmado. Construir un job nuevo habría sido, en los hechos, la señal de que el gate original estaba mal diseñado — la prueba de este proyecto es exactamente lo contrario.

"¿Cómo saben que la política nueva realmente funciona, no solo que existe?" — la respuesta vive en la lección 3: un FAIL real, provocado a propósito contra bedrock:* y contra Resource: "*", cada uno con su propio mensaje preciso, y un PASS real contra el rol correcto — nunca "debería funcionar", siempre "corrió, y esto fue lo que pasó".

"¿Qué pasa si Trivy no tiene todavía reglas para un recurso nuevo como aws_bedrock_guardrail?" — la respuesta vive en la lección 5: un resultado honesto, distinguiendo con precisión "sin cobertura todavía" de "cobertura real, pasada limpio" — nunca inflando un 0 en ninguna dirección.


El proyecto completo, en un vistazo

  andes-cargo-infra/
  ├── THREAT-MODEL.md                    (9 filas -- TM-08/TM-09 agregadas, L7)
  ├── bedrock.tf                         (M3-M4 -- guardrail + rol, sin cambios en M5)
  ├── modules/
  │   ├── bedrock-guardrail/             (M3-M4 -- sin cambios en M5)
  │   └── iam-role/                      (heredado -- reusado, sin cambios)
  ├── functions/
  │   └── extract-shipment-manifest-fields/
  │       ├── handler.py                 (M1 -- mínimo, no pulido)
  │       ├── function.zip               (L6 -- firmado con cosign)
  │       └── manifest.sig               (L6 -- Verified OK, offline)
  ├── policy/
  │   ├── no-destroy-shipments.rego      (heredado, cloud-security M4)
  │   ├── least-privilege-iam.rego       (heredado, cloud-security M4)
  │   ├── no-public-buckets.rego         (heredado, cloud-security M4)
  │   └── bedrock-least-privilege.rego   ← nuevo de este módulo (L3)
  ├── cosign.key / cosign.pub            (heredado, cloud-security M6 -- reusado, no regenerado)
  └── .github/workflows/ci.yml
      ├── policy-check    (heredado, cloud-security M8 -- sin cambios de YAML)
      ├── iac-scan        (heredado, cloud-security M8 -- sin cambios de YAML)
      └── verify-artifact (heredado, cloud-security M8 -- un step más, L6/L8)

Errores comunes

Agregar un cuarto job "por si acaso" antes de confirmar que los tres existentes bastan (de anticipar una necesidad que no existe). Qué pasa: alguien, al preparar este proyecto, escribe un job nuevo en ci.yml "para estar seguro", sin haber corrido primero los tres jobs existentes contra el Terraform y los artefactos nuevos. Cómo detectarlo: si tu ci.yml, al llegar a este proyecto, tiene más de tres jobs. Cómo corregirlo: el Paso 4 de esta lección es la prueba —corrida de verdad, no supuesta— de que los tres jobs heredados bastan. Si tu propia corrida de act pull_request mostrara una falla real que un job nuevo resolviera, esa sería una razón legítima para agregar uno — pero la disciplina de este módulo es correr primero, decidir después, nunca al revés.

Olvidar que policy-check corre su propio terraform init/plan desde cero, y que un tfplan.json local desactualizado no afecta esa corrida (de confundir el estado local con el estado del job). Qué pasa: alguien, después de correr terraform plan a mano (Paso 2 de esta lección) con un HCL que luego modifica sin volver a plan-ear, espera que act pull_request refleje ese cambio sin confirmarlo. Cómo detectarlo: si el resultado de policy-check dentro de act no coincide con lo que esperabas basándote en un tfplan.json local viejo. Cómo corregirlo: cada job de ci.yml es autosuficiente —policy-check corre terraform init/plan/show -json dentro de su propio contenedor, desde el HCL que hizo checkout, nunca desde un tfplan.json que hayas dejado en tu máquina—. El tfplan.json local del Paso 3 de esta lección es solo para tu propia verificación manual; act siempre genera el suyo, de cero, dentro del step correspondiente.

Verificar solo lambda/function.zip dentro de verify-artifact, olvidando que el nuevo step necesita su propia ruta completa y explícita (de copiar el step existente sin adaptar la ruta). Qué pasa: alguien copia el step de Verify function.zip against manifest.sig para el artefacto nuevo, pero olvida cambiar tanto el --bundle como la ruta del .zip al final del comando, y el step nuevo termina verificando el mismo artefacto dos veces. Cómo detectarlo: si el segundo step de verify-artifact en tu ci.yml es una copia idéntica del primero, sin ninguna ruta distinta. Cómo corregirlo: el diff del Paso 1 de esta lección muestra las cuatro líneas exactas que cambian —--bundle functions/extract-shipment-manifest-fields/manifest.sig y la ruta del .zip al final—; ambas tienen que apuntar al artefacto nuevo, nunca al de lambda/.


Ejercicios

Ejercicio 1 — Corre act pull_request -j verify-artifact en aislamiento, y predice si los dos steps de verificación (lambda/function.zip y el nuevo) corren en el orden en que aparecen en el YAML. Confirma tu predicción contra la salida real.

Ver solución

Sí — dentro de un mismo job, los steps siempre corren en el orden secuencial exacto en que aparecen en el YAML, sin ninguna paralelización implícita (a diferencia de los jobs, que sí podrían correr en paralelo si no tuvieran needs: entre ellos). El step Verify function.zip against manifest.sig corre primero, seguido de Verify extract-shipment-manifest-fields/function.zip against its manifest.sig — ambos dentro del mismo job verify-artifact, uno después del otro, cada uno con su propio resultado ✅ Success independiente.

Ejercicio 2 — Explica por qué Plan: 21 to add en este proyecto no contradice el Plan: 17 to add que el Módulo 3, lección 5 de esta guía ya confirmó. Un compañero, viendo ambos números en guías distintas, pregunta cuál es "el correcto".

Ver solución

Los dos números son correctos, simultáneamente, porque miden el proyecto en momentos distintos de su historia acumulativa. Plan: 17 to add (Módulo 3, lección 5 de esta guía) es el estado del proyecto justo después de que este módulo agregó bedrock.tf y el rol —antes de que cloud-security-and-guardrails-guide hubiera agregado sus propios recursos (aws_s3_bucket_public_access_block, el proveedor OIDC, los secretos gestionados)—. Plan: 21 to add (este proyecto) es el estado del proyecto completo, con todas las guías anteriores del ecosistema ya construidas, este módulo incluido. No son mediciones contradictorias del mismo momento — son el mismo proyecto, fotografiado en dos puntos distintos de su crecimiento acumulativo, exactamente como el Módulo 3, lección 5 ya distinguió entre un plan completo y uno aislado con -target.

Ejercicio 3 — Diseña, en prosa, la corrida de act pull_request que esperarías si alguien introdujera, a propósito, el error bedrock:* de la lección 3 en bedrock.tf antes de abrir este PR. ¿Cuál de los tres jobs fallaría primero, y qué le pasaría a los otros dos?

Ver solución

policy-check fallaría primero — específicamente en el step Evaluate the policy library against the plan, con el mismo mensaje de FAIL que la lección 3 de este módulo ya mostró (allows Action "bedrock:*"), y el job terminaría con 🏁 Job failed, no Job succeeded. Con policy-check en rojo, ni iac-scan (needs: policy-check) ni verify-artifact (needs: iac-scan, transitivamente dependiente de policy-check) llegarían a arrancar en absoluto — el mismo comportamiento de "el gate corta antes, no después" que la lección 1 de este módulo ya adelantó en su Ejercicio 3, y que este módulo entero existe para garantizar: un rol con bedrock:* nunca llegaría a la etapa de verificación de artefactos, mucho menos a un merge real.


Resumen y siguiente paso

Este proyecto confirmó, con una corrida real de act pull_request contra Docker, la tesis completa de este módulo: los tres jobs heredados de cloud-security-and-guardrails-guidepolicy-check (6 tests, 6 passed), iac-scan (0 hallazgos CRITICAL/HIGH en todo el proyecto), verify-artifact (dos Verified OK independientes)— evalúan correctamente el Terraform y los artefactos de la carga de IA de Andes Cargo, sin necesitar ningún job nuevo, ninguna herramienta nueva, ningún keypair nuevo. THREAT-MODEL.md cierra este módulo con nueve filas, dos de ellas específicas de una carga de IA, cada una con su mitigación citada con precisión.

Antes de cerrar este módulo deberías poder: correr act pull_request contra este proyecto sin mirar ninguna lección anterior; explicar, con evidencia ejecutada, por qué ningún job nuevo era necesario; y defender, frente a las tres preguntas de entrevista de esta lección, por qué "extender", no "reconstruir", fue la decisión correcta de principio a fin.

Con esto, el Módulo 5 de genai-on-aws-production-guide queda completo: mínimo privilegio de modelo verificado con Rego real, el contraste honesto de por qué Bedrock no necesita gestión de secretos nueva, Trivy corrido sobre el Terraform nuevo con el resultado honesto que le corresponde, el primer artefacto de esta guía firmado y verificado con cosign, dos filas nuevas de STRIDE, y el security gate completo, verde de punta a punta, con esta carga de IA integrada. El Módulo 6 abre la siguiente capa heredada: el cost gate de finops-and-cost-guardrails-guide, extendido con la única pieza que ese gate no puede resolver solo — el presupuesto de una carga usage-based de tokens.

Recursos

  1. nektosact.com — User Guide — referencia completa de act, incluida la ejecución de un job aislado con -j.
  2. cloud-security-and-guardrails-guide, Módulo 8, lección 3 (03-hands-on-chaining-the-gate-into-ci-yml.md) — el origen exacto de los tres jobs que este proyecto extiende.
  3. ADR-001-llm-as-escalation-path.md (Módulo 1, lección 8 de esta guía) — la fila que asignó a este módulo su responsabilidad exacta, cumplida en este proyecto.
  4. Este módulo, lecciones 3 y 6 — el origen de la política Rego y el artefacto firmado que este proyecto integra al gate.