Módulo 1: Threat Modeling Andes Cargo

5. Aplicando STRIDE a Andes Cargo

Descripción

Esta lección hace el cruce que las lecciones 3 y 4 prepararon por separado: toma las seis categorías de STRIDE y el inventario real de la superficie de ataque de Andes Cargo, y produce siete riesgos concretos, cada uno con evidencia específica —un nombre de rol, un ARN, un archivo que existe o que falta—, nunca una preocupación genérica. Cada riesgo lleva, además, un identificador (TM-01 a TM-07) que vas a volver a ver, sin cambios, en el documento de la lección 7 y en la matriz de la lección 8 — el mismo hilo de identificadores recorre el resto de esta guía.

Conexión con el módulo

Esta es la lección donde el trabajo de todo el módulo converge. La lección 2 te dijo qué preguntas "verde" no contestó. La lección 3 te dio la estructura para hacerlas. La lección 4 te dio la evidencia real. Esta lección responde, con esa evidencia, "¿qué puede salir mal?" — la segunda de las tres preguntas del modelado de amenazas. La lección 6 toma uno de estos siete riesgos (TM-06) y lo profundiza con un caso real. Las lecciones 7 y 8 los convierten en documentos formales.


Cómo leer esta lección: un riesgo, cuatro partes

Cada uno de los siete riesgos sigue la misma estructura: la categoría de STRIDE que aplica, la evidencia exacta del inventario de la lección 4 que lo sostiene, el impacto concreto —qué podría hacer alguien con este hallazgo, no una advertencia abstracta—, y el módulo de esta guía que lo resuelve. Ningún riesgo de esta lista es hipotético: los siete están anclados en un archivo, una política o una ausencia de política que ya confirmaste corriendo comandos reales.


TM-01 — Spoofing: el pipeline se autentica con una credencial de larga vida

Evidencia. ci.yml, heredado de cicd-and-gitops-on-aws-guide, lee ${{ secrets.AWS_ACCESS_KEY_ID }} y ${{ secrets.AWS_SECRET_ACCESS_KEY }} desde un .secrets local con los valores test/test. Esos mismos dos nombres de variable, contra una cuenta AWS real, serían un par de claves de acceso IAM estáticas —sin fecha de expiración, válidas hasta que alguien las rote a mano.

Impacto concreto. Cualquiera que obtenga esas dos líneas —en un log mal configurado, en una captura de pantalla, en un Secret de GitHub mal protegido— puede autenticarse ante AWS exactamente como si fuera el pipeline legítimo de Andes Cargo. AWS no tiene ninguna forma de distinguir "el runner real de GitHub Actions corriendo el job de Andes Cargo" de "cualquiera con estas dos cadenas de texto". Es, letra por letra, el ejemplo técnico de Spoofing de la lección 3: una llave física que cualquiera que la tenga puede usar.

Se resuelve en: Módulo 2 (identidad federada OIDC, donde no existe ninguna credencial de larga vida que robar en primer lugar).


TM-02 — Tampering: el artefacto de despliegue de la Lambda no tiene firma

Evidencia. process-shipment-manifest se empaqueta con data "archive_file" en un function.zip (terraform-and-iac-guide, Módulo 7, lección 3) y se despliega directamente con aws_lambda_function. En ningún punto del proceso —ni en ci.yml, ni en apply.yml, ni en el propio HCL— existe ningún mecanismo que confirme que el .zip que llega a producción es, byte por byte, el mismo que alguien revisó en un pull request.

Impacto concreto. Entre el momento en que el código se aprueba y el momento en que se despliega, nada impide que el .zip cambie —una dependencia comprometida, una línea agregada por error, una modificación deliberada en un paso intermedio del pipeline— sin que ningún control lo note. Es el ejemplo técnico de Tampering de la lección 3: un paquete sin sello, donde nadie puede distinguir el original del manipulado.

Se resuelve en: Módulo 6 (SBOM del handler + firma y verificación con cosign, 100% offline).


TM-03 — Repudiation: ningún cambio de infraestructura queda registrado de forma consultable

Evidencia. andes-cargo-infra/ no declara ningún recurso aws_cloudtrail. El state de Terraform registra qué existe, no quién ejecutó cada llamada ni cuándo. Si alguien corriera awslocal iam attach-role-policy a mano contra AppServerRole, por fuera de cualquier apply de Terraform, no habría ningún registro consultable de esa acción.

Impacto concreto. Sin un trail de auditoría, un cambio no autorizado —o un cambio autorizado que después alguien quiere negar haber hecho— no deja ninguna evidencia que lo confirme o lo desmienta. Es el ejemplo técnico de Repudiation de la lección 3: el edificio sin cámaras ni libro de registro, donde cualquiera puede negar haber estado ahí.

Se resuelve en: Módulo 7 (intento real de CloudTrail hasta donde LocalStack lo permite, honestidad explícita sobre el resultado).


TM-04 — Information disclosure: el bucket no tiene bloqueo de acceso público

Evidencia. La política de andes-cargo-shipment-docs (Paso 4 de la lección 4) resuelve una sola pregunta —¿el tráfico es HTTPS?—. En ningún lugar de modules/s3-bucket/ existe un recurso aws_s3_bucket_public_access_block; terraform-and-iac-guide lo declaró, explícitamente, fuera del alcance de ese módulo.

Impacto concreto. Hoy, con la configuración actual, el bucket no es público. Pero nada en el código impediría que lo fuera si mañana alguien agregara, por error o sin entender la implicancia, un statement Allow demasiado amplio a la política del bucket, o un ACL mal configurado. Es el archivador sin cerradura de la lección 3: no está expuesto hoy, pero nada en su diseño actual lo protege de estarlo mañana.

Se resuelve en: Módulo 4 (no-public-buckets.rego, policy-as-code que rechaza cualquier plan que deje un bucket sin este bloqueo).


TM-05 — Information disclosure: credenciales en texto plano sobre disco

Evidencia. El archivo .secrets, en la raíz de andes-cargo-infra/, contiene AWS_ACCESS_KEY_ID=test y AWS_SECRET_ACCESS_KEY=test en texto plano. Está gitignoreado desde su creación —cicd-and-gitops-on-aws-guide ya confirmó, con git log --all --full-history -- .secrets, que nunca entró al historial—, pero sigue siendo un archivo plano sobre disco, sin ningún control de acceso propio, sin versionado, sin rotación posible más allá de editarlo a mano.

Impacto concreto. Cualquiera con acceso de lectura a la máquina donde vive ese archivo —no al repositorio Git, a la máquina misma— puede leer la credencial completa sin ningún registro de haberlo hecho. Es una segunda instancia de Information disclosure, distinta de TM-04: ahí faltaba un control sobre un recurso de AWS; aquí falta un control sobre el propio mecanismo que guarda la credencial.

Se resuelve en: Módulo 3 (SSM Parameter Store para configuración, Secrets Manager para credenciales, ninguno de los dos en texto plano sobre disco).


TM-06 — Denial of service: nada impide un plan que destruye Shipments

Evidencia. El guardrail más cercano que existe hoy es el que cicd-and-gitops-on-aws-guide nombró en su Módulo 4 —un grep artesanal sobre el plan en JSON, no todavía una política real corriendo en el pipeline—. Ningún mecanismo automático, encadenado al pipeline de forma que un cambio no pueda evitarlo, revisa hoy si un plan propuesto elimina la tabla Shipments o el bucket andes-cargo-shipment-docs antes de que ese plan pueda aplicarse.

Impacto concreto, y por qué esto es Denial of service y no otra categoría. La definición de Microsoft es explícita: "deny service to valid users" —no exige que el mecanismo sea un ataque de red—. Un apply que destruye Shipments, sea por error humano, por un plan mal revisado, o por un agente que interpreta mal el state (exactamente el escenario de la lección 6), deja al sistema completo sin su única fuente de verdad sobre el estado de los envíos — ningún usuario legítimo de la app de tracking puede consultar nada, sin que haya existido ningún ataque externo. El resultado —servicio denegado a usuarios legítimos— es lo que define la categoría, no el mecanismo que lo produjo.

Se resuelve en: Módulo 4 (no-destroy-shipments.rego, evaluado sobre el plan antes de que exista la posibilidad de aplicarlo).


TM-07 — Elevation of privilege: AppServerRole tiene más alcance del que su función usa

Evidencia. El Paso 3 de la lección 4 lo confirmó letra por letra: AppServerRole —el rol de un servidor de aplicación cuyo único trabajo es mostrarle a un humano el estado de los envíos— tiene exactamente el mismo alcance de lectura que LambdaManifestProcessorRole —el rol de la función que procesa manifiestos nuevos—: s3:GetObject/s3:ListBucket sobre el bucket completo, sin distinguir entre el prefijo manifests/ (que solo la Lambda necesita leer, para procesar manifiestos crudos que nunca deberían mostrarse directamente a un usuario final) y el prefijo photos/ (lo que la app de tracking efectivamente muestra).

Impacto concreto. Un compromiso de AppServerRole —una vulnerabilidad en la propia app de tracking, por ejemplo— le da a un atacante acceso de lectura a los manifiestos crudos del bucket, un dato que la app de tracking nunca tuvo que exponer para cumplir su función. El alcance del rol no refleja el alcance real del código que lo usa — la tarjeta de hotel que abre más puertas de las que alguien pidió, el ejemplo técnico exacto de Elevation of privilege de la lección 3.

Se resuelve en: Módulo 2, lección 7 (roles reales de Andes Cargo recortados a los verbos y prefijos exactos que su código usa).


La tabla completa: siete riesgos, seis categorías, siete módulos

IDSTRIDERiesgoEvidenciaSe resuelve en
TM-01SpoofingPipeline autenticado con credencial de larga vida.secrets, ci.yml (secrets.AWS_ACCESS_KEY_ID)M2
TM-02Tamperingfunction.zip sin firmalambda.tf (archive_file sin verificación posterior)M6
TM-03RepudiationSin trail de auditoríaNingún aws_cloudtrail en andes-cargo-infra/M7
TM-04Information disclosureBucket sin bloqueo de acceso públicomodules/s3-bucket/ (sin aws_s3_bucket_public_access_block)M4
TM-05Information disclosureCredenciales en texto plano sobre disco.secrets (AWS_ACCESS_KEY_ID=test)M3
TM-06Denial of serviceNada bloquea un plan que destruye ShipmentsSin política preventiva sobre el planM4
TM-07Elevation of privilegeAppServerRole más amplio de lo que usaAppServerRole-policy = LambdaManifestProcessorRole-policyM2.7

Fíjate en algo que vale la pena notar antes de cerrar la lección: dos riesgos (TM-04, TM-06) apuntan al mismo módulo (M4), y esto no es casualidad — el Módulo 4 es, por diseño de esta guía, el que construye policy-as-code preventivo: reglas que se evalúan sobre el plan, antes de que exista la posibilidad de aplicarlo. Cualquier riesgo cuya resolución sea "una regla que bloquea un plan malo antes del apply" converge ahí, sin importar de qué categoría de STRIDE haya salido.


Errores comunes

Inventar un riesgo sin evidencia específica del M1.4, "porque suena plausible" (de rigor). Qué pasa: alguien agrega un octavo riesgo basado en una preocupación genérica de seguridad cloud —por ejemplo, "la cuenta podría estar comprometida"— sin ningún dato concreto del inventario que lo sostenga. Cómo detectarlo: si tu riesgo no incluye un nombre de archivo, un ARN, o un statement específico como evidencia. Cómo corregirlo: cada uno de los siete riesgos de esta lección señala algo que existe (una credencial, una política) o algo que específicamente falta (una firma, un bloqueo). Un modelo de amenazas construido sobre preocupaciones genéricas, sin evidencia verificable, es indistinguible de una lista de miedos — el valor de esta lección viene exactamente de que cada fila se puede volver a verificar corriendo el comando correspondiente de la lección 4.

Asignar más de una categoría de STRIDE a un mismo riesgo sin justificarlo (de disciplina, retomado de la lección 3). Qué pasa: alguien, recordando que una amenaza puede caer en varias categorías, empieza a marcar cada riesgo con tres o cuatro letras "por si acaso". Cómo detectarlo: si tu clasificación de un riesgo no puede explicar, en una frase, por qué cada categoría adicional aplica de verdad. Cómo corregirlo: la categoría principal de cada riesgo en esta lección es la que domina su impacto más directo. Es correcto notar, como hace TM-01, que un riesgo puede habilitar otras categorías en cadena —pero la tabla final necesita una categoría principal clara por fila, no una lista abierta sin criterio.

Confundir "más grave" con "más urgente de resolver" (de priorización implícita). Qué pasa: alguien lee el orden S-T-R-I-D-E de esta lección como un orden de severidad, y asume que TM-01 (Spoofing) es automáticamente más grave que TM-07 (Elevation of privilege). Cómo detectarlo: si tu plan de qué resolver primero se basa en el orden en que aparece el riesgo en esta lección, no en un criterio explícito de impacto o facilidad de explotación. Cómo corregirlo: el orden de esta lección sigue el acrónimo STRIDE por claridad pedagógica, no una escala de severidad. La lección 8 de este módulo va a ordenar estos mismos siete riesgos en la secuencia real en que esta guía los resuelve —M2 antes que M6, por ejemplo—, con el criterio de diseño explícito detrás de ese orden.


Ejercicios

Ejercicio 1 — Reconstruye la tabla completa de memoria, sin ver el archivo. Escribe los siete identificadores (TM-01 a TM-07), su categoría de STRIDE, y una frase de evidencia para cada uno.

Ver solución

La tabla completa está en la sección anterior — el objetivo de este ejercicio es haberla podido reconstruir agrupando los siete riesgos por su categoría: un riesgo de Spoofing (credencial de larga vida), uno de Tampering (.zip sin firmar), uno de Repudiation (sin trail), dos de Information disclosure (bucket sin bloqueo público, secreto en texto plano), uno de Denial of service (sin protección contra destrucción de Shipments), y uno de Elevation of privilege (AppServerRole demasiado amplio). Si agrupaste los siete sin ayuda, tienes el análisis completo internalizado.

Ejercicio 2 — Explica por qué TM-04 y TM-05 son categorías distintas de riesgo, aunque las dos sean Information disclosure. Un compañero, viendo que ambas comparten la misma letra de STRIDE, sugiere fusionarlas en una sola fila de la tabla. ¿Estás de acuerdo? Justifica en dos o tres frases.

Ver solución

En desacuerdo. Comparten la categoría de STRIDE porque ambas terminan en el mismo tipo de consecuencia —información que no debería ser accesible, siendo accesible—, pero el mecanismo y el control que las resuelve son completamente distintos: TM-04 es la ausencia de un control sobre un recurso de AWS (aws_s3_bucket_public_access_block), y se resuelve con policy-as-code sobre el plan (M4); TM-05 es la ausencia de un servicio de gestión de secretos, y se resuelve migrando el valor a SSM/Secrets Manager (M3). Fusionarlas perdería exactamente la información que hace útil a la tabla: qué módulo resuelve cada una. STRIDE clasifica el tipo de amenaza, no reemplaza la necesidad de nombrar cada mecanismo por separado.

Ejercicio 3 — Predice qué pasaría si M2 se completara pero M4 no. Supón que Andes Cargo implementa la identidad federada OIDC completa del Módulo 2 (resolviendo TM-01 y TM-07) pero nunca llega a construir las políticas Rego del Módulo 4. ¿Qué riesgos de esta tabla seguirían abiertos, y el sistema estaría genuinamente más seguro o solo parcialmente?

Ver solución

TM-02 (Tampering, sin firma), TM-03 (Repudiation, sin trail), TM-04 (Information disclosure, bucket sin bloqueo público) y TM-06 (Denial of service, sin protección sobre Shipments) seguirían completamente abiertos — ninguno de los cuatro depende de la identidad que ejecuta el apply, dependen de qué controla ese apply una vez que corre. El sistema estaría genuinamente más seguro que antes —dos riesgos reales resueltos, no cosméticos—, pero lejos de "seguro" en general: la tabla completa de esta lección existe exactamente para que nadie confunda "resolvimos el riesgo más citado en foros" (identidad, mínimo privilegio) con "resolvimos todos los riesgos que importan". Cada fila de esta tabla es independiente; cerrar una no cierra las demás.


Resumen y siguiente paso

En esta lección cruzaste las seis categorías de STRIDE con el inventario real de la lección 4 y produjiste siete riesgos concretos —TM-01 a TM-07—, cada uno con evidencia verificable, impacto concreto, y el módulo exacto de esta guía que lo resuelve. Confirmaste que dos riesgos de categorías distintas (TM-04, TM-06) convergen en el mismo módulo (M4, policy-as-code preventivo), y que el orden S-T-R-I-D-E no es un orden de severidad ni de resolución.

Antes de avanzar deberías poder: nombrar los siete riesgos con su identificador, su categoría de STRIDE y su evidencia, sin mirar la tabla; explicar por qué TM-04 y TM-05, aunque comparten categoría, necesitan controles distintos; y explicar, con el ejemplo de TM-06, por qué Denial of service no requiere un ataque de red.

La lección 6 toma específicamente TM-06 y lo profundiza con un caso real y verificado: el incidente del destroy de Claude Code, desde el ángulo de qué control automatizado —no la atención humana— lo hubiera detenido.

Recursos

  1. terraform-and-iac-guide, Módulos 6 y 7 — la fuente de cada archivo y política citada como evidencia en esta lección.
  2. cicd-and-gitops-on-aws-guide, Módulo 4 — la fuente de .secrets, ci.yml, y el guardrail artesanal nombrado en TM-06.
  3. Microsoft Learn — Threats: Microsoft Threat Modeling Tool — las seis definiciones de STRIDE aplicadas en esta lección, ya citadas en la lección 3.
  4. AWS IAM — Grant least privilege — la práctica oficial de AWS que TM-07 identifica como incumplida, y que el Módulo 2 de esta guía resuelve.