Módulo 5: Securing The Ai Workload
6. Manos a la obra: firmando el artefacto del extractor con `cosign`
Descripción
Esta lección firma, de verdad, el primer artefacto binario genuinamente nuevo de esta guía: el .zip de extract-shipment-manifest-fields. Usa exactamente el mismo cosign.key/cosign.pub que cloud-security-and-guardrails-guide, Módulo 6, lección 5 ya generó —el mismo par de llaves, byte por byte, sin regenerar nada—, y el mismo flujo offline con --signing-config que esa guía, Módulo 6, lección 6, ya documentó con precisión, incluido el hallazgo real de que dos flags de una versión anterior de la documentación (--tlog-upload=false, --output-signature) están deprecados en cosign v3.1.3. Nada de eso se repite aquí desde cero — se aplica, tal cual, a un artefacto distinto.
Conexión con el módulo
El job verify-artifact de ci.yml (heredado de cloud-security-and-guardrails-guide, Módulo 8, lección 3) hoy verifica un solo artefacto: lambda/function.zip, el de process-shipment-manifest. La lección 8 de este módulo extiende ese mismo job —sin crear uno nuevo— para que también verifique el .zip que esta lección firma aquí. Sin el manifest.sig de esta lección, ese step nuevo no tendría nada que verificar.
Analogía: el mismo sello, aplicado a un paquete distinto
Un notario que ya tiene su sello oficial registrado no necesita tramitar un sello nuevo cada vez que certifica un documento distinto — el mismo sello, aplicado con el mismo procedimiento, certifica un contrato de compraventa hoy y un poder notarial mañana. Lo que cambia es el documento; el sello, y el procedimiento para aplicarlo, siguen siendo los mismos. cosign.key es ese sello. lambda/function.zip ya lo tiene aplicado, desde cloud-security-and-guardrails-guide, Módulo 6, lección 6. Esta lección aplica el mismo sello, con el mismo procedimiento exacto, al .zip de extract-shipment-manifest-fields.
Paso 1 — Confirmando que el keypair sigue ahí, sin regenerar nada
cosign version
Qué esperar (literal — la misma versión que cloud-security-and-guardrails-guide, Módulo 6, lección 5 ya confirmó, sin ninguna instalación nueva en este módulo):
GitVersion: v3.1.3
GitCommit: 11926fa5bbbbde47e88fc006b625a17769b743b2
GitTreeState: "clean"
BuildDate: 2026-08-05T23:43:27Z
GoVersion: go1.26.5
Compiler: gc
Platform: darwin/arm64
ls -la cosign.key cosign.pub no-tlog-signing-config.json
Qué esperar (literal — los tres archivos que esa misma lección ya dejó en andes-cargo-infra/; ninguno se toca en este módulo):
-rw------- 1 andes-cargo staff 653 cosign.key
-rw-r--r-- 1 andes-cargo staff 178 cosign.pub
-rw-r--r-- 1 andes-cargo staff 106 no-tlog-signing-config.json
Tres archivos, cero comandos de instalación. no-tlog-signing-config.json es el archivo de configuración de firma sin ningún servicio declarado —ni Fulcio, ni Rekor, ni sellado de tiempo— que esa lección ya construyó para evitar, con certeza, una subida accidental al registro público de transparencia; se reusa aquí exactamente igual.
Paso 2 — El artefacto: extract-shipment-manifest-fields/function.zip
functions/extract-shipment-manifest-fields/handler.py es el handler mínimo que el Módulo 1 de esta guía presenta (mínimo, no pulido — la frontera con AI Engineering, Módulo 1, lección 4). Empaquétalo:
cd functions/extract-shipment-manifest-fields
zip -X function.zip handler.py
Qué esperar (literal):
adding: handler.py (deflated 53%)
ls -la function.zip
shasum -a 256 function.zip
Qué esperar (literal — ejecutado para escribir esta lección):
-rw-r--r-- 1 andes-cargo staff 1230 function.zip
aefcd07a3f2a3b426ebea98efc002cd7986061f08f07be2242f487289b359ba2 function.zip
1.230 bytes — más pequeño que lambda/function.zip (890 bytes, el handler de process-shipment-manifest), pero comparable: ambos son artefactos de práctica, minúsculos, exactamente el punto de que la disciplina de firma no depende del tamaño del código, sino de que exista una cadena verificable entre lo que alguien revisó y lo que se despliega.
Paso 3 — Firmando, offline, con el keypair ya existente
El flujo completo que cloud-security-and-guardrails-guide, Módulo 6, lección 6, ya recorrió con sus dos intentos fallidos —--tlog-upload=false/--output-signature deprecados, --bundle solo subiendo sin querer a Rekor por defecto— no se repite aquí: esta lección va directo al comando correcto, ya conocido:
COSIGN_PASSWORD="" cosign sign-blob \
--key cosign.key \
--signing-config no-tlog-signing-config.json \
--bundle functions/extract-shipment-manifest-fields/manifest.sig \
--yes \
functions/extract-shipment-manifest-fields/function.zip
Qué esperar (literal — ejecutado para escribir esta lección):
Using payload from: functions/extract-shipment-manifest-fields/function.zip
Signing artifact...
Wrote bundle to file functions/extract-shipment-manifest-fields/manifest.sig
Sin ningún aviso legal sobre un registro público —la misma confirmación indirecta que cloud-security-and-guardrails-guide ya explicó: si cosign fuera a subir algo a un servicio hospedado, pediría consentimiento explícito antes de firmar. Su ausencia aquí es la prueba de que no hay ninguna subida de por medio.
cat functions/extract-shipment-manifest-fields/manifest.sig
Qué esperar (el campo mediaType y la estructura son literales; messageSignature.signature es VARIABLE — ver la nota abajo):
{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"publicKey":{"hint":"rcWoHarzQrHVD4Fsb2wPlD9/X+ZVuFA1F2yWL+A2EQk="}}, "messageSignature":{"messageDigest":{"algorithm":"SHA2_256", "digest":"rvzQej8qO0JuvqmO/AAs15hgYfCPB74iQvSHKJs1m6I="}, "signature":"MEUCIAtn2Qxn7aZMe2rSvEYhgy5/z9VxbOT+4Rn8TVp3btN/AiEAtL5+X9Q54z3aAGczzn7jU5AVMuF4OjF0kOaXRy4KBVA="}}
Fíjate en verificationMaterial.publicKey.hint: rcWoHarzQrHVD4Fsb2wPlD9/X+ZVuFA1F2yWL+A2EQk=, exactamente el mismo hint que cloud-security-and-guardrails-guide, Módulo 6, lección 6 ya mostró en su propio manifest.sig para lambda/function.zip. No es una coincidencia — es la confirmación literal, en el archivo mismo, de que este manifest.sig se firmó con el mismo par de llaves, no uno nuevo. messageDigest.digest es el hash SHA-256 real de este .zip específico, distinto del de lambda/function.zip porque el contenido es distinto; messageSignature.signature es el valor que va a variar en tu propia corrida —ECDSA, el algoritmo por defecto del keypair, es no determinista por diseño, la misma propiedad de seguridad que cloud-security-and-guardrails-guide ya explicó—.
grep -c tlogEntries functions/extract-shipment-manifest-fields/manifest.sig
Qué esperar (literal): 0 — sin ningún registro de transparencia público involucrado, exactamente como el no-tlog-signing-config.json reusado garantiza.
Paso 4 — Verificando, 100% offline
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
Qué esperar (literal — ejecutado para escribir esta lección):
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
Verified OK
echo $?
0
Verified OK — la misma garantía exacta que cloud-security-and-guardrails-guide, Módulo 6, lección 6 ya explicó con precisión: este .zip, tal como existe en disco en este momento, es exactamente el mismo, byte por byte, que existía cuando se firmó, y quien lo firmó tenía acceso a la llave privada correspondiente a cosign.pub. No confirma que el código dentro del .zip sea correcto, ni que pase las políticas de policy/ (lección 3 de este módulo) o el escaneo de Trivy (lección 5) — esas son preguntas distintas, respondidas por controles distintos, encadenados juntos en el gate que la lección 8 corre de punta a punta.
Por qué esta lección no repite el error del Módulo 6 de cloud-security-and-guardrails-guide
Vale la pena decir con precisión por qué esta lección no muestra los dos intentos fallidos (--tlog-upload=false deprecado, --bundle sin --signing-config subiendo a Rekor sin querer) que esa guía sí documentó: no porque esta lección oculte algo, sino porque esa lección ya hizo el trabajo de investigar el comando correcto, con evidencia real, y esta lección hereda ese hallazgo tal cual — exactamente la misma disciplina de "no reinvestigar lo que una guía hermana ya resolvió" que este ecosistema aplica en cada guía nueva. Repetir los dos intentos fallidos aquí sería relleno, no honestidad — la honestidad ya está documentada, con su fecha y su versión exacta, en la fuente original.
Errores comunes
Generar un cosign.key/cosign.pub nuevo para este módulo, en vez de reusar el existente (de no confirmar qué ya existe antes de actuar). Qué pasa: alguien, al llegar a esta lección, corre cosign generate-key-pair sin verificar primero si ya hay un keypair en el proyecto. Cómo detectarlo: si cosign.key/cosign.pub en tu proyecto tienen una fecha de modificación de hoy, en vez de la fecha en que cloud-security-and-guardrails-guide, Módulo 6, los generó originalmente. Cómo corregirlo: el Paso 1 de esta lección confirma explícitamente, con ls -la, que los tres archivos ya existen antes de firmar nada nuevo — la regla dura de este módulo completo es cero reinstalación, y un keypair nuevo, aunque técnicamente funcionaría, rompería la continuidad de que lambda/function.zip y el nuevo .zip del extractor comparten la misma cadena de confianza.
Verificar con --bundle manifest.sig sin especificar la ruta completa, y confundir el manifest.sig de lambda/ con el de functions/extract-shipment-manifest-fields/ (de dos artefactos con el mismo nombre de archivo de firma). Qué pasa: alguien, trabajando desde la raíz de andes-cargo-infra/, corre cosign verify-blob --bundle manifest.sig ... sin la ruta completa, y el comando encuentra —o no encuentra— el archivo equivocado, porque ambos artefactos usan el mismo nombre convencional manifest.sig en directorios distintos. Cómo detectarlo: un error de archivo no encontrado, o —peor, silencioso— una verificación que compara la firma equivocada contra el artefacto equivocado. Cómo corregirlo: siempre usa la ruta completa y explícita, como en el Paso 4 de esta lección (functions/extract-shipment-manifest-fields/manifest.sig, nunca solo manifest.sig) — la misma disciplina de rutas explícitas que la lección 8 de este módulo aplica dentro de ci.yml.
Esperar que messageDigest.digest sea el mismo entre lambda/function.zip y el .zip del extractor (de confundir "misma llave" con "mismo contenido"). Qué pasa: alguien, viendo que ambos manifest.sig comparten el mismo publicKey.hint, asume que también deberían compartir el mismo messageDigest.digest. Cómo detectarlo: si comparas los dos archivos manifest.sig esperando que coincidan en más que el hint de la llave pública. Cómo corregirlo: el hint identifica quién firmó (la misma llave, en ambos casos); el messageDigest.digest identifica qué se firmó (el contenido exacto de cada .zip, necesariamente distinto porque handler.py de process-shipment-manifest y el de extract-shipment-manifest-fields son archivos diferentes). Compartir la llave no implica compartir el contenido — son dos preguntas independientes que el bundle de cosign responde por separado.
Ejercicios
Ejercicio 1 — Verifica, sin correr ningún comando de cosign, si esperarías que rvzQej8qO0JuvqmO/AAs15hgYfCPB74iQvSHKJs1m6I= (el messageDigest.digest de esta lección) coincida con algún hash que ya viste en otra guía de este ecosistema. Explica por qué sí o por qué no.
Ver solución
No coincidiría con ningún hash de otra guía, porque extract-shipment-manifest-fields/function.zip es un artefacto genuinamente nuevo de esta guía —el handler no existía en terraform-and-iac-guide ni en aws-serverless-and-containers-guide—. El único hash que sí coincidiría entre dos fuentes independientes sería el de lambda/function.zip (el de process-shipment-manifest), que cloud-security-and-guardrails-guide, Módulo 6, lección 6 ya confirmó igual al que terraform-and-iac-guide, Módulo 7, reportó con output_base64sha256 — dos herramientas distintas, mismo artefacto, mismo hash. El .zip de esta lección es nuevo, así que su hash tampoco tiene un precedente que confirmar.
Ejercicio 2 — Explica por qué cosign verify-blob necesita tanto cosign.pub como manifest.sig como argumentos, y qué pasaría si solo tuvieras uno de los dos. Piensa en qué información aporta cada archivo por separado.
Ver solución
cosign.pub responde "¿quién dice haber firmado esto?" —la llave pública correspondiente a la privada que se usó—; manifest.sig responde "¿cuál es la firma específica, y sobre qué hash exacto?". Sin cosign.pub, no habría con qué comparar matemáticamente la firma —no sabrías si corresponde a una llave legítima o a una cualquiera—. Sin manifest.sig, no habría ninguna firma que verificar en absoluto —solo tendrías una llave pública sin nada que confirmar contra ella—. Los dos archivos, juntos, son lo mínimo necesario para que la verificación tenga sentido: una firma sin llave pública es inútil, una llave pública sin firma no verifica nada.
Ejercicio 3 — Predice qué mostraría cosign verify-blob si intentaras verificar functions/extract-shipment-manifest-fields/function.zip contra el manifest.sig de lambda/ (el artefacto equivocado, a propósito). ¿Fallaría con un error, o con un mensaje distinto?
Ver solución
Fallaría — pero con un mensaje que indica que la firma no corresponde al contenido, no un error de "archivo no encontrado" ni de "llave inválida". La llave pública seguiría siendo válida (es la misma llave para ambos artefactos), pero el messageDigest.digest dentro de manifest.sig de lambda/ corresponde al hash de lambda/function.zip, no al de functions/extract-shipment-manifest-fields/function.zip — cosign calcularía el hash real del archivo que le diste y lo compararía contra el que la firma dice haber firmado; al no coincidir, la verificación fallaría con un mensaje del estilo de una firma inválida para ese contenido específico, exactamente el mismo tipo de fallo que cloud-security-and-guardrails-guide, Módulo 6, lección 7 ya demostró al romper la cadena a propósito, modificando el artefacto después de firmarlo.
Resumen y siguiente paso
Esta lección firmó, con cosign sign-blob real, el primer artefacto binario genuinamente nuevo de esta guía —extract-shipment-manifest-fields/function.zip—, reusando exactamente el mismo cosign.key/cosign.pub/no-tlog-signing-config.json que cloud-security-and-guardrails-guide ya generó, sin instalar nada nuevo y sin repetir el trabajo de investigación de esa guía. Confirmaste, con evidencia literal, que el manifest.sig resultante comparte el mismo hint de llave pública que el de lambda/function.zip —misma llave, contenido distinto—, que no contiene ningún tlogEntries (0, offline de verdad), y que cosign verify-blob devuelve Verified OK con código de salida 0.
Antes de avanzar deberías poder: firmar y verificar un artefacto nuevo reusando un keypair existente, sin necesitar generar uno propio; explicar por qué dos manifest.sig distintos pueden compartir el mismo hint de llave pública pero nunca el mismo messageDigest.digest; y anticipar qué mensaje mostraría cosign verify-blob si la firma y el artefacto no correspondieran entre sí.
La lección 7 vuelve a THREAT-MODEL.md —el documento que cloud-security-and-guardrails-guide dejó con siete filas— y agrega las dos primeras filas específicas de una carga de IA, con la mitigación exacta que los Módulos 4 y 5 de esta guía ya construyeron.
Recursos
- Sigstore — Signing Blobs — referencia oficial de
sign-blob/verify-bloby el formato de bundlev0.3usado en esta lección. cloud-security-and-guardrails-guide, Módulo 6, lección 5 (05-hands-on-installing-cosign-and-a-local-keypair.md) — el origen exacto del keypair que esta lección reusa, sin regenerar.cloud-security-and-guardrails-guide, Módulo 6, lección 6 (06-hands-on-signing-and-verifying-the-deployment-artifact.md) — la investigación completa de los flags deprecados y el flujo offline correcto, heredada tal cual por esta lección.- Módulo 1, lección 4 de esta guía — la frontera con AI Engineering que fija por qué
handler.pyde esta lección es mínimo, no pulido.