Módulo 4: Secrets Environments And Identity

8. Proyecto: el plan de secretos y ambientes de Andes Cargo

Descripción

Este es el proyecto que cierra el Módulo 4. Tiene dos partes de naturaleza distinta, y esta lección es honesta sobre cuál es cuál desde el principio. La primera es ejecutada: confirmas, con act corriendo de verdad, que ci.yml —ya migrado en la lección 7— sigue funcionando como el pipeline completo de LocalStack que ha sido desde el Módulo 3. La segunda es un documento, en prosa técnica, no un YAML nuevo ni un rol IAM construido: qué secreto, qué ambiente, y qué rol OIDC usaría Andes Cargo si este pipeline corriera contra una cuenta AWS real —sin construir nada de eso aquí, exactamente como declaró la lección 5 sobre el patrón OIDC completo.

Conexión con el módulo

Las lecciones 2 y 3 dieron el porqué y el mecanismo de Secrets; las lecciones 4 y 5, el mecanismo completo (aunque representativo) de OIDC; la lección 6, el mecanismo (también representativo) de Environments con aprobación; la lección 7 hizo el trabajo ejecutado real: migrar ci.yml. Este proyecto integra todo eso en dos entregables: el ci.yml migrado, confirmado una vez más de punta a punta, y un documento nuevo, SECRETS-AND-ENVIRONMENTS.md, que es el primer artefacto de portfolio de esta guía que no es código —es la clase de documento que un ingeniero real escribe antes de tocar una cuenta de producción, para que el diseño de seguridad se revise antes de implementarse, no después.


Parte 1 — Ejecutada: confirma el pipeline completo, con secretos migrados

Sobre andes-cargo-infra/, con el ci.yml migrado que dejaste en la lección 7, corre el ciclo completo una vez más, esta vez listando los jobs disponibles primero, para confirmar que el proyecto entero sigue coherente:

act -l

Qué esperar (extracto literal, ejecutado para escribir esta lección — los workflows acumulados de los cuatro módulos hasta ahora):

Stage  Job ID                   Job name                 Workflow name        Workflow file           Events
0      terraform-checks         terraform-checks         ci                   ci.yml                  pull_request
0      say-hello-to-localstack  say-hello-to-localstack  hello-andes-cargo    hello-andes-cargo.yml   push
0      print-payload            print-payload            print-event-payload  print-event.yml         pull_request
0      check-secrets            check-secrets            secrets-test         secrets-test.yml        workflow_dispatch
0      check-one-secret         check-one-secret         single-secret-test   single-secret-test.yml  workflow_dispatch
act pull_request -e .github/act-events/pr-event.json -j terraform-checks --secret-file .secrets

Qué esperar (literal — el mismo resultado, extremo a extremo, que confirmaste en la lección 7):

[ci/terraform-checks]   ✅  Success - Main Terraform format check [...]
[ci/terraform-checks]   ✅  Success - Main Terraform init [...]
[ci/terraform-checks]   ✅  Success - Main Terraform validate [...]
[ci/terraform-checks]   ✅  Success - Main Install awslocal [...]
[ci/terraform-checks]   ❌  Failure - Main Confirm the runner can reach LocalStack on the host [...]
[ci/terraform-checks]   ✅  Success - Main Install tflocal [...]
[ci/terraform-checks]   | Plan: 12 to add, 0 to change, 0 to destroy.
[ci/terraform-checks]   ✅  Success - Main Terraform plan [...]
[ci/terraform-checks]   ✅  Success - Main Publish the plan to the job summary [...]
[ci/terraform-checks] 🏁  Job succeeded

Plan: 12 to add, 0 to change, 0 to destroy es, de nuevo, la firma de los cuatro recursos canónicos de Andes Cargo (bucket, tabla, dos roles, función) desplegándose con sus módulos y recursos de soporte — nada cambió respecto al Módulo 3, y nada debería haber cambiado: la migración de la lección 7 tocó exclusivamente de dónde vienen dos valores de credencial, nunca qué hace el pipeline con ellos.

Esto confirma la parte que este módulo sí puede ejecutar: el pipeline de LocalStack completo, con .secrets como la única fuente de credenciales, sin ninguna línea de credencial escrita directamente en un archivo commiteado.


Parte 2 — El documento: SECRETS-AND-ENVIRONMENTS.md

Crea este archivo en la raíz de andes-cargo-infra/, junto al resto del proyecto —es el primer documento de prosa técnica de esta guía, el antecesor directo del README.md de pipeline que el Módulo 8 va a construir como entregable final de portfolio—:

# Secretos, ambientes e identidad — plan de despliegue real de Andes Cargo

> Este documento describe el diseño de seguridad que usaría Andes Cargo si este pipeline
> corriera contra una cuenta AWS real. Nada de lo que describe aquí está construido en este
> repositorio: es el plan, no la implementación. La implementación completa vive en
> `cloud-security-and-guardrails-guide`.

## Secretos

**En este repositorio (LocalStack, ejecutado):** un único par de credenciales dummy
(`AWS_ACCESS_KEY_ID=test`, `AWS_SECRET_ACCESS_KEY=test`), como `Secrets` de repositorio,
usadas por igual en cualquier rama y cualquier ambiente.

**En un despliegue real:** cero credenciales de larga vida guardadas como `Secret`, en
absoluto. Con OIDC (Módulo 4, lección 5) correctamente configurado, no hace falta guardar
ningún `AWS_ACCESS_KEY_ID` — el pipeline recibe credenciales temporales en cada corrida. Lo
único que un workflow real necesitaría del lado de configuración es el ARN del rol a asumir
(`role-to-assume`), y ese valor **no es un secreto**: conocer el ARN de un rol no le da acceso
a nadie sin poder también presentar un JWT válido firmado por GitHub para el repositorio y
rama correctos. Por eso, en un despliegue real, ese ARN viviría como una `variable` (`vars.`,
no `secrets.`) de GitHub Actions — visible, no protegida, porque no necesita estarlo.

## Ambientes

| Ambiente | Rama permitida | Required reviewers | Rol IAM que asumiría |
|---|---|---|---|
| `dev` | cualquier rama de feature, vía `pull_request` | No | `AndesCargoPlanOnlyRole` — permisos de solo lectura/plan, nunca `apply` |
| `production` | únicamente `main`, vía `push` | Sí, al menos una persona distinta del autor del cambio | `AndesCargoDeployRole` — permisos de `apply` sobre los cuatro recursos canónicos |

La separación entre `AndesCargoPlanOnlyRole` (solo lectura, para `ci.yml`) y
`AndesCargoDeployRole` (con permiso de `apply`, para `apply.yml`, Módulo 5) es deliberada:
ningún `pull_request` de una rama de feature —donde el código todavía no fue revisado— debería
tener, ni siquiera en teoría, permisos para modificar infraestructura real. Solo el `push` a
`main`, después de la fusión de un PR ya revisado, asumiría el rol con permisos de escritura.

## El rol de `production`: `trust policy` y permisos, en prosa

**Trust policy:** confía únicamente en tokens de OIDC de GitHub cuyo `claim` `sub` sea
exactamente `repo:andes-cargo/andes-cargo-infra:ref:refs/heads/main` — ninguna otra rama,
ningún fork, ningún Pull Request puede asumir este rol, sin importar quién lo dispare.

**Permisos:** limitados exactamente a los cuatro recursos canónicos que ya conoces de
`terraform-and-iac-guide` — el bucket `andes-cargo-shipment-docs`, la tabla `Shipments`, los
roles `LambdaManifestProcessorRole`/`AppServerRole`, y la función `process-shipment-manifest` —
más los permisos de Terraform necesarios para leer/escribir el `state` remoto (fuera del
alcance de LocalStack, que usa un `state` local). Ningún permiso de administrador, ningún
`*:*` — el mismo principio de privilegio mínimo que ya aplicaste, del lado de la aplicación, en
esos mismos roles durante `terraform-and-iac-guide`.

## Lo que este documento NO resuelve

No incluye el JSON exacto del `Identity Provider`, la `trust policy` completa, ni la política
de permisos completa — esos ya están mostrados, completos, en el Módulo 4, lección 5, y
construidos de punta a punta en `cloud-security-and-guardrails-guide`. Este documento es el
**plan**, escrito antes de construir nada, exactamente como lo escribiría un ingeniero real
antes de una revisión de diseño de seguridad.

Commitea el documento:

git add SECRETS-AND-ENVIRONMENTS.md
git commit -m "Add SECRETS-AND-ENVIRONMENTS.md: the real-deployment plan for secrets, environments, and OIDC roles"
git log --oneline -5

Qué esperar (representativo en los hashes, literal en la estructura):

9f2c8a1 Add SECRETS-AND-ENVIRONMENTS.md: the real-deployment plan for secrets, environments, and OIDC roles
a1b2c3d Migrate ci.yml credentials from hardcoded values to secrets.AWS_ACCESS_KEY_ID / secrets.AWS_SECRET_ACCESS_KEY
7b6421e ci.yml: publish the plan to the job summary
dfcbfce ci.yml: install tflocal and run terraform plan
ba47438 Add ci.yml: terraform fmt and validate as CI steps

Cierre del Módulo 4

Completaste el módulo que responde de frente a la brecha más citada de la auditoría de mercado. Repasa lo que te llevas:

  • El antipatrón, nombrado con precisión, y visto con tus propios ojos: una clave de acceso de larga vida escrita directamente en un archivo commiteado — no en abstracto, sino localizada en tu propio ci.yml — y por qué el historial de Git, no el archivo actual, es el verdadero problema de una credencial filtrada (lecciones 2, con un experimento real de recuperación).
  • El mecanismo intermedio, ejecutado de verdad: Secrets de GitHub, repo-scoped y environment-scoped, referenciados con secrets.<NOMBRE>, migrados de verdad en el ci.yml real de Andes Cargo, con salida idéntica antes y después (lecciones 3 y 7).
  • El mecanismo completo, mostrado y con razón de honestidad citada: federación OIDC —JWT de corta vida, permissions: id-token: write, trust policy de IAM— en YAML de producción real, no ejecutado por dos razones técnicas citadas, con pointer directo a cloud-security-and-guardrails-guide (lecciones 4 y 5).
  • El control de aprobación humana, descrito y probado en su límite: Environments con required reviewers, con una corrida real de act confirmando —en 1.6 segundos, sin pausa— que la herramienta local ignora esta protección por completo (lección 6).
  • El plan completo, en prosa técnica, para un despliegue real: qué rol, qué trust policy, qué separación entre dev y production usaría Andes Cargo, documentado sin construirlo (esta lección).

Qué viene después

El Módulo 5 toma este mismo ci.yml —ya migrado, ya con Secrets correctos— y construye la mitad de CD del pipeline: apply.yml, disparado por push a main, encadenado con needs: al plan que lo precedió, para que el apply use exactamente el mismo plan que se revisó, no uno recalculado. Vas a ver environment: production —el mecanismo de esta lección— declarado por primera vez en un workflow real de esta guía, y vas a construir control de concurrencia y detección de drift programada.


Errores comunes

Construir el Identity Provider o el rol IAM reales dentro de andes-cargo-infra/, "ya que el documento los describe" (de flujo, el más importante de este proyecto). Qué pasa: alguien, motivado por lo concreto que suena SECRETS-AND-ENVIRONMENTS.md, agrega un archivo .tf nuevo con aws_iam_openid_connect_provider y aws_iam_role, intentando aplicarlo contra LocalStack. Por qué pasa: el documento describe recursos de AWS con suficiente detalle como para sentirse "casi listo para escribir en HCL". Cómo detectarlo: si tu andes-cargo-infra/ tiene un archivo nuevo que declara un aws_iam_openid_connect_provider. Cómo corregirlo: bórralo — DISENO.md de esta guía es explícito en que no se declara un solo recurso HCL de negocio nuevo salvo el guardrail mínimo del Módulo 6; LocalStack, además, no implementa validación de OIDC, así que ese recurso, aunque se aplicara, no serviría para nada real. El documento de esta lección es deliberadamente un plan, no un borrador de HCL.

Tratar SECRETS-AND-ENVIRONMENTS.md como opcional o de relleno (de expectativa). Qué pasa: alguien escribe el documento de forma apresurada, con una o dos líneas, sin la tabla de ambientes ni la separación explícita entre AndesCargoPlanOnlyRole y AndesCargoDeployRole. Por qué pasa: comparado con un ci.yml que corre y falla o tiene éxito, un documento de prosa se siente menos "verificable", y es tentador tratarlo como menos importante. Cómo detectarlo: si tu versión del documento no explica, con claridad, por qué dev y production necesitan roles distintos, no solo Secrets distintos. Cómo corregirlo: este documento es exactamente el tipo de entregable que se revisa en una entrevista técnica real o en una revisión de diseño de seguridad — la calidad de tu razonamiento sobre separación de permisos importa tanto como el YAML que sí corre. Vuelve a escribirlo con el detalle completo del ejemplo de esta lección.


Ejercicios

Ejercicio 1 — Justifica la separación de roles entre dev y production. Sin mirar el documento de esta lección, explica en tus propias palabras por qué AndesCargoPlanOnlyRole y AndesCargoDeployRole deberían ser dos roles IAM distintos, en vez de un único rol compartido con permisos de apply usado tanto en dev como en production.

Ver solución

Un pull_request desde una rama de feature corre código que todavía no fue revisado —ese es justamente el propósito de ci.yml: generar un plan para que alguien lo revise antes de fusionar—. Si ese pull_request asumiera un rol con permisos de apply, cualquier código malicioso o con un error grave en una rama de feature podría, en teoría, aplicar cambios reales contra la infraestructura antes de que nadie lo haya revisado — exactamente lo contrario del propósito del patrón plan-antes-de-apply que construyó el Módulo 3. Separar los roles asegura que el peor caso posible en dev (una rama de feature comprometida o con errores) se limite a generar un plan que nadie tiene por qué aplicar, nunca a modificar infraestructura real por sí sola.

Ejercicio 2 — Explica por qué el ARN del rol no necesita ser un Secret. Un colega, escribiendo su propio SECRETS-AND-ENVIRONMENTS.md, guarda el ARN de AndesCargoDeployRole como un Secret de GitHub, "por si acaso". ¿Es necesario? Justifica tu respuesta en dos frases.

Ver solución

No es necesario, y no le hace daño a nadie que lo sea, pero tampoco agrega ninguna protección real: conocer el ARN de un rol IAM no le permite a nadie asumirlo — la seguridad real vive completamente en la trust policy de ese rol (qué repositorio, qué rama, qué claim de OIDC puede asumirlo), no en mantener el ARN en secreto. Guardarlo como vars.AWS_ROLE_ARN (una variable normal, no protegida) en vez de secrets.AWS_ROLE_ARN es más honesto sobre qué protege realmente el sistema, y hace el YAML más legible para cualquiera que lo revise.

Ejercicio 3 — Recorre el módulo completo de memoria, en una frase por lección. Sin mirar atrás, resume cada una de las 8 lecciones de este módulo en una sola frase, en el orden en que aparecieron.

Ver solución

1. El mapa del módulo y por qué es la brecha de seguridad más citada de la competencia. 2. Por qué una credencial filtrada en un commit vive en el historial de Git, no en el archivo actual. 3. Secrets de GitHub, repo-scoped y environment-scoped, y la sintaxis secrets.<NOMBRE>. 4. Qué es la federación OIDC, conceptualmente: JWT de corta vida, permissions: id-token: write, trust policy. 5. El patrón OIDC completo en YAML de producción, representativo, con la razón técnica citada de por qué act no puede ejecutarlo. 6. Environments con required reviewers, representativo, confirmado con una corrida real de act que lo ignora. 7. La migración ejecutada de ci.yml de credenciales escritas a Secrets, con salida idéntica antes y después. 8. El plan de secretos, ambientes y roles OIDC que usaría Andes Cargo en un despliegue real, documentado sin construirlo.


Resumen y siguiente paso

En este proyecto cerraste el Módulo 4 con dos entregables de naturaleza distinta y ambos honestos sobre su propia naturaleza: ci.yml, confirmado de nuevo de punta a punta con act corriendo de verdad, y SECRETS-AND-ENVIRONMENTS.md, un documento de prosa técnica que describe —sin construir— el diseño completo de secretos, ambientes y roles OIDC que usaría Andes Cargo contra una cuenta AWS real.

Antes de avanzar deberías poder: explicar la diferencia entre AndesCargoPlanOnlyRole y AndesCargoDeployRole, y por qué existen como dos roles separados; confirmar, corriendo act tú mismo, que ci.yml sigue funcionando igual después de la migración de credenciales; y nombrar, sin dudar, las tres piezas de seguridad de este módulo en orden de madurez: credencial escrita en el YAML (el antipatrón) → Secret de GitHub (mejor, pero sigue siendo una clave de larga vida) → OIDC (sin ninguna clave de larga vida que guardar).

Con esto, el Módulo 4 queda cerrado. Tienes el vocabulario completo de seguridad de identidad de esta guía, el pipeline real corriendo con Secrets correctos, y un documento de diseño para el despliegue real que todavía no construiste — a propósito.

Siguiente módulo: la mitad de CD del pipeline — apply.yml disparado por push a main, encadenado al plan con needs:, con environment: production declarado por primera vez de verdad, control de concurrencia, y detección de drift programada.

Recursos

  1. src/paths/aws-cloud-ecosystem/VALIDACION.md (NIEVA) — la fuente de la brecha de mercado que este módulo entero resuelve, citada en la lección 1.
  2. GitHub Docs — Configuring OpenID Connect in Amazon Web Services — el patrón completo de OIDC, base de la lección 5 y del documento de esta lección.
  3. terraform-and-iac-guide, capstone (NIEVA) — el origen de LambdaManifestProcessorRole y AppServerRole, los dos roles que el documento de esta lección usa como referencia de privilegio mínimo.
  4. cloud-security-and-guardrails-guide (NIEVA) — la guía que construye, de punta a punta, exactamente lo que este documento planea sin implementar.