Módulo 4: Secrets Environments And Identity

5. El patrón OIDC en YAML, nombrado

Descripción

Esta lección es representativa desde su primera línea. El YAML que vas a leer aquí es exactamente el que escribirías en un pipeline de producción real, contra una cuenta AWS real — pero no corre en esta guía. La razón no es pereza ni un atajo: es una limitación técnica investigada y confirmada, con cita directa de un mantenedor de act, más una segunda razón de diseño de esta guía. Vas a leer el patrón completo, paso a paso, entendiendo cada pieza con el vocabulario que construiste en la lección 4 — y vas a saber, con precisión, adónde ir si quisieras construirlo de verdad.

Conexión con el módulo

La lección 4 te dio el mecanismo conceptual completo: JWT, permissions: id-token: write, trust policy. Esta lección traduce ese mecanismo, sin cambiar ni una idea, a YAML real y a un documento JSON real de trust policy. La lección 6 sigue el mismo patrón de honestidad —representativo, etiquetado, con razón técnica citada— para los Environments con aprobación requerida.


Por qué esta lección no ejecuta nada, con la cita exacta

Dos razones, investigadas por separado, ambas necesarias para que este YAML corriera de verdad:

Razón 1 — act no implementa emisión de tokens OIDC. Verificado directamente contra una discusión pública del propio repositorio de act, con respuesta de un colaborador del proyecto (ChristopherHX):

"Additionally nektos/act doesn't implement it's own oidc tokens. (needs to change the jwk endpoint)"

"That's impossible, because only GitHub Actions from github.com can sign the token."

La segunda frase es la más importante de entender: no es que act "todavía no haya agregado" esta función como una casualidad de roadmap — es que la firma del JWT (el paso 2 del diagrama de la lección 4) depende de infraestructura criptográfica que solo existe dentro de github.com real. act corre workflows en tu máquina, pero no es GitHub — no tiene la clave privada con la que GitHub firma esos tokens, y no podría simularla de forma segura ni aunque quisiera.

Razón 2 — no hay una cuenta AWS real contra la cual federar. Incluso si act pudiera emitir un JWT válido, el paso 4 del flujo (AWS valida el JWT contra una trust policy) necesita un rol IAM real, configurado en una cuenta AWS real, con un Identity Provider real apuntando a token.actions.githubusercontent.com. LocalStack —el laboratorio $0 de toda esta guía— no implementa validación de OIDC: solo acepta las credenciales dummy test/test sin validar nada contra ninguna cuenta. Aunque act pudiera emitir el token, no habría nada real del otro lado que lo validara.

Ambas razones son necesarias por separado, y ninguna de las dos se resuelve dentro del alcance $0 de esta guía. Por eso este patrón se muestra, completo, explicado paso a paso —lo que un pipeline de producción escribiría—, y se etiqueta como representativo en el momento exacto en que aparece, no al final de la lección.


El YAML completo, explicado paso a paso

Paso 1 — El Identity Provider en IAM (una sola vez, por cuenta AWS)

Antes de que cualquier workflow pueda pedir credenciales, la cuenta AWS necesita registrar a GitHub como una fuente confiable. Esto se hace una sola vez, no en cada corrida — es configuración de infraestructura, típicamente declarada en Terraform (algo que terraform-and-iac-guide no cubrió, porque en ese momento la guía todavía no existía como hilo del ecosistema):

resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com",
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
  ]
}

url apunta al emisor exacto de tokens de GitHub Actions —el mismo token.actions.githubusercontent.com que la lección 4 nombró—. client_id_list limita qué audiencia puede usar este provider (sts.amazonaws.com, el servicio de AWS que va a validar el token). El thumbprint_list es una huella criptográfica del certificado del emisor, que AWS usa para confirmar que está hablando con el servidor correcto de GitHub, no con un impostor.

Paso 2 — El rol IAM y su trust policy

resource "aws_iam_role" "andes_cargo_deploy" {
  name = "AndesCargoDeployRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:andes-cargo/andes-cargo-infra:ref:refs/heads/main"
          }
        }
      }
    ]
  })
}

Action = "sts:AssumeRoleWithWebIdentity" —no sts:AssumeRole a secas— es la variante específica de STS para federación con un token externo, en vez de con otra credencial de AWS. El bloque Condition es donde vive la precisión de esta trust policy: StringLike sobre el claim sub (subject) del JWT restringe exactamente qué repositorio y qué rama pueden asumir este rol —repo:andes-cargo/andes-cargo-infra:ref:refs/heads/main, ni un carácter más ancho que eso—. Una trust policy mal escrita, con un patrón demasiado amplio (por ejemplo, sin la parte de la rama), permitiría que cualquier rama de ese repositorio —incluida una rama de feature creada por cualquier colaborador— asumiera un rol pensado solo para main. Este es, verificado contra la documentación oficial de AWS, el error de configuración de OIDC más común en implementaciones reales.

Paso 3 — El workflow que asume el rol

name: apply

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  terraform-apply:
    runs-on: ubuntu-latest
    steps:
      - name: Check out andes-cargo-infra
        uses: actions/checkout@v4

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/AndesCargoDeployRole
          aws-region: us-east-1

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.15.8"

      - name: Terraform apply
        run: terraform apply -auto-approve -input=false

Tres piezas nuevas frente a todo lo que construiste hasta ahora:

  • permissions: id-token: write —del nivel del workflow, no del step— es el consentimiento explícito que ya conoces de la lección 4: sin esta línea, el paso siguiente falla antes de intentar nada.
  • aws-actions/configure-aws-credentials@v6 es la Action oficial de AWS que orquesta todo el flujo de la lección 4 por ti: pide el JWT a GitHub, lo presenta a AWS STS con role-to-assume, y —si la trust policy del Paso 2 lo permite— exporta las credenciales temporales resultantes como variables de entorno (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) para que los steps siguientes las usen sin ninguna configuración adicional.
  • Nota sobre @v6: la documentación oficial de GitHub, en su ejemplo canónico de este mismo patrón, pinea esta Action por SHA de commit, no por tag —aws-actions/configure-aws-credentials@<sha-de-40-caracteres>—, precisamente por la práctica de seguridad de cadena de suministro que ya nombró el Módulo 2, lección 5: un tag puede reapuntar a un commit distinto en el futuro; un SHA no puede. Esta lección usa @v6 (el major vigente de la Action) por legibilidad —es más fácil de leer y recordar en un contexto de aprendizaje—, pero un pipeline real de producción debería preferir el SHA fijo del release que use.
  • Fíjate en lo que no aparece en ningún lado de este workflow: ni secrets.AWS_ACCESS_KEY_ID, ni .secrets, ni ningún Secret configurado en Settings. No hace falta ninguno — es exactamente el punto central de OIDC frente al patrón de la lección 3.

Qué esperar si intentaras correr esto con act (representativo, la falla exacta)

Si guardaras este YAML como .github/workflows/oidc-demo.yml en andes-cargo-infra/ y corrieras act push -j terraform-apply, el step Configure AWS credentials via OIDC fallaría — no por un error de sintaxis, sino exactamente por la Razón 1 de arriba: act no tiene forma de emitir el JWT que ese step necesita pedir. El error real reportado por usuarios de act que intentaron este mismo patrón (documentado en la discusión citada arriba) es consistente con "no se pudo obtener el token de OIDC" — la Action nunca llega siquiera a intentar hablar con AWS STS, porque el paso anterior, pedirle el token a GitHub, ya falla dentro del propio entorno de act.


Dónde construir esto de verdad: cloud-security-and-guardrails-guide

Esta guía no construye el Paso 1 ni el Paso 2 contra una cuenta AWS real —harían falta permisos de IAM más allá de lo que LocalStack puede ofrecer, y una cuenta AWS real rompería el compromiso de $0 que sostiene toda esta guía—. cloud-security-and-guardrails-guide es la guía hermana que sí construye este patrón de punta a punta: el Identity Provider, el rol, la trust policy completa, y un workflow real corriendo contra una cuenta AWS real, con el nivel de profundidad de seguridad —SAST/DAST, supply chain, políticas como código— que está deliberadamente fuera del alcance de esta guía.


Errores comunes

Escribir este YAML dentro de ci.yml o apply.yml reales de Andes Cargo, "para tenerlo listo" (de flujo). Qué pasa: alguien, motivado por lo completo que se ve este patrón, lo copia directamente a un workflow real del proyecto, reemplazando las credenciales dummy de LocalStack. Por qué pasa: el YAML se ve terminado y correcto —porque lo es, para una cuenta real—, y es tentador "adelantar" el trabajo. Cómo detectarlo: si tu apply.yml real, corrido contra LocalStack, ahora tiene un step de aws-actions/configure-aws-credentials con role-to-assume. Cómo corregirlo: revierte ese cambio — LocalStack no tiene ningún rol IAM real que asumir vía OIDC, y ese step fallaría inmediatamente, incluso corriendo en GitHub real, porque no hay un role-to-assume válido apuntando a nada. El patrón de credenciales dummy (test/test, vía Secrets desde la lección 7) sigue siendo el correcto para todo lo que esta guía ejecuta contra LocalStack.

Confundir el thumbprint_list con algo que se pueda inventar o copiar de cualquier ejemplo (de configuración). Qué pasa: alguien copia el valor de thumbprint_list de un tutorial antiguo, sin verificar si sigue siendo válido. Por qué pasa: es un valor hexadecimal que no parece cambiar, así que se asume estático para siempre. Cómo detectarlo: si tu configuración real de OIDC (fuera de esta guía) usa un thumbprint_list copiado de un artículo de hace más de un año, sin confirmarlo contra la documentación actual de AWS. Cómo corregirlo: el thumbprint corresponde al certificado del servidor de GitHub, y los certificados se renuevan periódicamente — un valor desactualizado puede romper silenciosamente la validación de OIDC. cloud-security-and-guardrails-guide es la guía que profundiza en cómo mantener esto correcto en un despliegue real.


Ejercicios

Ejercicio 1 — Cita, de memoria, las dos razones técnicas de esta lección. Sin mirar hacia atrás, explica las dos razones independientes por las que este YAML no corre en esta guía, y por qué ambas son necesarias —no basta con resolver solo una—.

Ver solución

Razón 1: act no implementa emisión de tokens OIDC — confirmado por un colaborador del proyecto (ChristopherHX, discusión #2029 de nektos/act): la firma del JWT depende de infraestructura criptográfica exclusiva de github.com real, que act no tiene ni puede simular de forma segura. Razón 2: incluso si act pudiera emitir el token, no hay una cuenta AWS real con un rol IAM y una trust policy configurados contra los cuales validarlo — LocalStack no implementa validación de OIDC. Son independientes porque resolver la Razón 1 (si act algún día lo implementara) no resolvería la Razón 2, y viceversa: harían falta ambas piezas a la vez para que este patrón corriera de verdad en esta guía.

Ejercicio 2 — Encuentra el error en una trust policy incompleta. Un colega escribe esta condición para su trust policy: "StringLike": { "token.actions.githubusercontent.com:sub": "repo:andes-cargo/andes-cargo-infra:*" }. ¿Qué problema de seguridad tiene, comparado con la de esta lección?

Ver solución

El patrón repo:andes-cargo/andes-cargo-infra:* permite que cualquier referencia dentro de ese repositorio asuma el rol —cualquier rama, cualquier Pull Request, cualquier tag—, no solo main. La trust policy de esta lección es mucho más estrecha: repo:andes-cargo/andes-cargo-infra:ref:refs/heads/main, que solo permite corridas disparadas específicamente desde la rama main. Con el patrón amplio del colega, cualquiera que pudiera abrir una rama de feature en ese repositorio —o incluso un Pull Request desde un fork, dependiendo de la configuración— podría, en teoría, lograr que un workflow en esa rama asumiera el rol de despliegue de producción.

Ejercicio 3 — Explica qué exporta aws-actions/configure-aws-credentials y por qué eso importa. Sin mirar el YAML de esta lección, explica qué hace exactamente el step Configure AWS credentials via OIDC, y por qué el step siguiente (Terraform apply) no necesita ningún env: adicional con credenciales.

Ver solución

aws-actions/configure-aws-credentials orquesta todo el flujo de OIDC de la lección 4: pide el JWT a GitHub, lo presenta a AWS STS junto con el role-to-assume especificado, y si la trust policy del rol lo permite, recibe credenciales temporales de vuelta. La Action exporta esas credenciales como variables de entorno estándar (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) para el resto de los steps del mismo job — por eso terraform apply, en el step siguiente, no necesita ningún env: explícito: el CLI de AWS (y, por extensión, el provider de Terraform) ya sabe leer esas tres variables de entorno automáticamente, sin ninguna configuración adicional.


Resumen y siguiente paso

En esta lección leíste el patrón OIDC completo, en YAML real: el Identity Provider de IAM (una sola vez, por cuenta), la trust policy del rol (restringida a un repositorio y rama exactos), y el workflow que usa aws-actions/configure-aws-credentials con role-to-assume para asumir ese rol sin ninguna clave guardada. Viste las dos razones técnicas, citadas y verificadas, por las que este patrón no corre en esta guía, y el pointer directo a cloud-security-and-guardrails-guide para construirlo contra una cuenta real.

Antes de avanzar deberías poder: escribir de memoria la estructura de una trust policy de OIDC, incluida la condición sobre sub; explicar por qué aws-actions/configure-aws-credentials no necesita ningún Secret de GitHub; y citar la razón exacta —con la fuente— de por qué act no puede ejecutar este patrón.

La lección 6 aplica exactamente el mismo patrón de honestidad —representativo, con razón técnica citada— a un mecanismo distinto: los Environments de GitHub con aprobación humana requerida.

Recursos

  1. GitHub Docs — Configuring OpenID Connect in Amazon Web Services — la fuente oficial del patrón completo de esta lección, incluido el ejemplo con pin por SHA.
  2. aws-actions/configure-aws-credentials — GitHub — repositorio oficial de la Action, con su README completo y ejemplos de role-to-assume.
  3. nektos/act discussion #2029 — la fuente exacta de la cita de esta lección sobre por qué act no implementa emisión de tokens OIDC.
  4. AWS Docs — Creating a role for web identity or OpenID Connect Federation — documentación oficial de AWS sobre la trust policy de OIDC, incluida la sintaxis completa de Condition.
  5. cloud-security-and-guardrails-guide (NIEVA) — la guía que construye este patrón de punta a punta contra una cuenta AWS real, con SAST/DAST, supply chain, y policy-as-code, todo fuera del alcance de esta guía.