Módulo 4: Secrets Environments And Identity
4. Qué es la federación OIDC
Descripción
Hasta aquí, este módulo resolvió "dónde guardar una credencial de forma segura" (lección 3: Secrets). Esta lección da un paso distinto, más profundo: ¿qué pasaría si un pipeline pudiera demostrar su identidad ante AWS sin tener, en ningún momento, ninguna credencial de larga vida que guardar? Esa es la federación OIDC (OpenID Connect) — el mecanismo que la auditoría de mercado señala como el hueco más citado de la competencia. Esta lección es puramente conceptual: entiendes el mecanismo completo, pieza por pieza, antes de ver una sola línea del YAML de producción que la lección 5 muestra completo.
Conexión con el módulo
Las lecciones 2 y 3 dieron por sentado que existe una credencial —una clave de acceso de AWS— que hay que proteger, ya sea sacándola del YAML (lección 3) o entendiendo el riesgo de no hacerlo (lección 2). Esta lección cuestiona esa premisa: ¿y si no hiciera falta ninguna clave? La lección 5 toma exactamente lo que aprendes aquí y lo traduce a YAML real, completo, etiquetado como representativo por una razón técnica específica que vas a entender mejor después de esta lección.
Analogía: el pase de visitante frente a la llave maestra copiada
Imagina un edificio de oficinas con dos formas distintas de dar acceso a alguien que solo necesita entrar por unas horas. La primera: le entregas una copia física de la llave maestra. Esa copia sigue funcionando mañana, la próxima semana, el próximo año — hasta que alguien, activamente, cambie la cerradura. Si la persona pierde la copia, o la presta, o alguien la fotografía sin que ella se dé cuenta, esa llave sigue abriendo la puerta indefinidamente, para quien sea que la tenga.
La segunda forma: en recepción, la persona muestra su identificación, recepción llama a quien la invitó para confirmar que la visita es legítima, y le entregan un pase temporal —una tarjeta que expira sola a las 6 p.m. de ese mismo día—. Nadie tuvo que copiar ninguna llave. Si la tarjeta se pierde, deja de funcionar sola, sin que nadie tenga que perseguirla ni cambiar ninguna cerradura. Y cada vez que la persona necesita volver, repite el mismo proceso: se identifica, alguien de confianza lo confirma, recibe un pase nuevo, que vuelve a expirar solo.
Una clave de acceso de AWS de un usuario IAM tradicional es la primera opción: una copia que existe hasta que alguien la revoca a mano. OIDC es la segunda: cada vez que el pipeline necesita actuar, GitHub —el "recepcionista" de confianza— emite un token que expira en minutos, AWS confirma que ese token viene de una fuente que ya decidió confiar, y entrega credenciales temporales, solo por esa corrida específica. No hay ninguna copia de llave que alguien pueda perder, filtrar o dejar de rotar por descuido.
El mecanismo completo, pieza por pieza
OIDC (OpenID Connect) es un protocolo estándar —no algo inventado por GitHub ni por AWS— para que un sistema demuestre su identidad ante otro usando un JWT (JSON Web Token, un token firmado digitalmente que cualquiera puede leer pero nadie puede falsificar sin la clave privada de quien lo firmó). GitHub Actions actúa como identity provider: cuando un workflow lo pide, GitHub firma un JWT que dice, en esencia, "este token viene de una corrida específica del repositorio andes-cargo/andes-cargo-infra, disparada por este evento, en este momento". AWS, del otro lado, ya configuró de antemano que confía en los tokens firmados por GitHub —sin necesitar ninguna credencial compartida de antemano entre ambos—.
EL FLUJO COMPLETO DE OIDC (conceptual — sin YAML todavía)
1. Un job de GitHub Actions corre, y pide un JWT
│
▼
2. GitHub (el identity provider) firma un JWT de corta vida
con "claims" (afirmaciones) sobre quién está pidiendo:
repositorio, rama, evento que disparó la corrida
│
▼
3. El job presenta ese JWT a AWS STS
(Security Token Service), pidiendo asumir un rol IAM
│
▼
4. AWS valida el JWT contra una "trust policy" ya configurada:
"confío en tokens firmados por GitHub, para ESTE repositorio
específico, para ESTE rol específico"
│
▼
5. Si la validación pasa, AWS STS devuelve credenciales
TEMPORALES (válidas por minutos u horas, nunca para siempre)
│
▼
6. El job usa esas credenciales temporales para el resto
de la corrida — y cuando expiran, ya no sirven para nada,
sin que nadie tenga que revocarlas activamente
Fíjate en el detalle central: en ningún punto de este flujo existe una clave de AWS guardada en GitHub, ni una clave de GitHub guardada en AWS. Lo único que existe, de antemano, es una relación de confianza configurada —el paso 4— entre dos sistemas que nunca comparten un secreto directamente.
Las dos piezas de configuración que vas a ver nombradas en la lección 5
permissions: id-token: write
Un workflow de GitHub Actions, por defecto, no tiene permiso para pedir un JWT de OIDC — es una decisión deliberada de seguridad: si cualquier workflow pudiera pedir un token de identidad sin declararlo, sería mucho más fácil que un paso comprometido de una Action de terceros abusara de ese poder sin que nadie lo notara. El bloque permissions: id-token: write, declarado explícitamente en el nivel del job (o del workflow entero), es el consentimiento explícito: "este job, específicamente, necesita poder pedir un token de identidad". Sin esta línea, el paso 1 del diagrama de arriba simplemente no puede ocurrir — el intento de pedir el JWT falla antes de llegar a AWS.
La trust policy de IAM
Del lado de AWS, un rol IAM normal se puede asumir de varias formas — una de ellas, la que usa OIDC, es a través de un Identity Provider configurado dentro de IAM que apunta específicamente a token.actions.githubusercontent.com, el emisor de tokens de GitHub Actions. La trust policy de ese rol (el documento JSON que define quién puede asumirlo) no dice "confío en esta clave secreta compartida" — dice algo mucho más específico y auditable: "confío en tokens firmados por este emisor exacto, únicamente si el claim sub del token coincide con repo:andes-cargo/andes-cargo-infra:ref:refs/heads/main" (o el patrón exacto que se configure). Es la pieza que reemplaza por completo a la clave compartida: en vez de un secreto que ambos lados conocen, hay una regla pública, legible por cualquiera con acceso al rol, sobre exactamente qué repositorio, qué rama, y qué evento tiene permitido asumir ese rol.
Por qué esto es un salto cualitativo, no solo "una clave más escondida"
Vale la pena volver, con este mecanismo ya claro, al Ejercicio 2 de la lección 1 de este módulo. Mover una credencial a un Secret de GitHub (lección 3) reduce el riesgo de que termine en un commit, pero la credencial sigue existiendo: sigue siendo una clave de larga vida, guardada en algún lugar, válida hasta que alguien la revoque a mano. OIDC elimina esa pieza por completo. No hay ninguna clave de AWS guardada en ningún Secret de GitHub. Lo único que existe, permanentemente, es la configuración de confianza del lado de AWS (la trust policy) — y esa configuración, por sí sola, sin ningún JWT válido presentado en el momento exacto de una corrida real, no le da acceso a nadie a nada.
Esto también responde una pregunta que quizás ya te hiciste: ¿qué pasa si alguien roba el archivo ci.yml completo, con toda su configuración? Con el patrón de la lección 3 (secrets.AWS_ACCESS_KEY_ID), robar el YAML no sirve de nada sin también robar el valor del Secret —pero el Secret existe, en algún lugar, y ese "en algún lugar" siempre es un blanco posible. Con OIDC, robar el ci.yml completo tampoco sirve de nada: el atacante necesitaría, además, lograr que GitHub le firme un JWT válido en nombre del repositorio real —algo que no puede falsificar sin comprometer la infraestructura de GitHub misma—.
Errores comunes
Pensar que OIDC significa "sin credenciales, en absoluto" (conceptual). Qué pasa: alguien concluye que, como no hay ninguna clave de larga vida guardada, el paso final del flujo (usar credenciales de AWS para actuar) tampoco necesita ninguna credencial. Por qué pasa: la frase "sin claves que guardar" es fácil de simplificar de más hacia "sin credenciales en absoluto". Cómo detectarlo: si crees que el paso 6 del diagrama de arriba —usar credenciales para el resto de la corrida— no necesita ninguna credencial real. Cómo corregirlo: OIDC sí produce credenciales de AWS temporales al final del flujo (el AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN de una sesión asumida) — la diferencia no es "cero credenciales", es "credenciales que nacen ya caducadas en minutos, generadas de nuevo en cada corrida, en vez de una clave que vive para siempre hasta que alguien la mata a mano".
Asumir que la trust policy reemplaza la necesidad de permisos de IAM sobre los recursos (de alcance). Qué pasa: alguien configura la trust policy correctamente —el rol confía en el repositorio correcto— y asume que eso es suficiente para que el pipeline pueda hacer cualquier cosa en AWS. Cómo detectarlo: si tu mental model de OIDC es "quién puede entrar", sin pensar todavía en "qué puede hacer una vez adentro". Cómo corregirlo: la trust policy controla quién puede asumir el rol (la pregunta de identidad, el foco de esta lección); los permisos del rol (una política de permisos separada, adjunta al mismo rol IAM) controlan qué puede hacer una vez que lo asumió — exactamente el mismo principio de privilegio mínimo que ya aplicaste a LambdaManifestProcessorRole y AppServerRole en terraform-and-iac-guide. Un rol con una trust policy perfecta pero permisos demasiado amplios sigue siendo un riesgo real.
Ejercicios
Ejercicio 1 — Traza el flujo completo de memoria. Sin mirar el diagrama de esta lección, describe en tus propias palabras los seis pasos del flujo de OIDC, desde que un job pide un token hasta que usa credenciales temporales de AWS.
Ver solución
(1) Un job de GitHub Actions pide un JWT. (2) GitHub, como identity provider, firma ese JWT con información sobre quién lo pide (repositorio, rama, evento). (3) El job presenta ese JWT a AWS STS, pidiendo asumir un rol IAM específico. (4) AWS valida el JWT contra la trust policy configurada de antemano en ese rol — confirma que el token viene de un emisor confiable y que corresponde exactamente al repositorio/rama permitidos. (5) Si la validación pasa, AWS STS devuelve credenciales temporales, válidas por un tiempo limitado. (6) El job usa esas credenciales temporales durante el resto de la corrida; cuando expiran, dejan de funcionar solas, sin que nadie tenga que revocarlas.
Ejercicio 2 — Explica permissions: id-token: write a alguien que nunca lo vio. Un colega te pregunta por qué haría falta declarar explícitamente un permiso solo para "pedir un token de identidad" — ¿no debería un workflow poder hacer eso siempre que lo necesite? Respóndele en dos o tres frases.
Ver solución
Una respuesta completa suena, más o menos, así: "Es una decisión deliberada de seguridad, no una limitación accidental. Si cualquier workflow pudiera pedir un token de identidad sin que nadie lo declarara explícitamente, sería mucho más fácil que un paso comprometido —por ejemplo, una Action de terceros con una versión maliciosa— abusara de ese poder sin que se notara en una revisión del YAML. permissions: id-token: write hace que ese consentimiento sea explícito, visible, y auditable línea por línea, exactamente como cualquier otro permiso mínimo que declaramos a propósito en vez de asumir por defecto."
Ejercicio 3 — Distingue el rol de la trust policy del rol de los permisos de IAM. Un rol IAM llamado AndesCargoDeployRole tiene una trust policy que confía correctamente solo en el repositorio andes-cargo/andes-cargo-infra, rama main. Pero su política de permisos le da AdministratorAccess completo sobre la cuenta. ¿Está esto bien configurado? Explica qué controla cada pieza.
Ver solución
La trust policy está bien configurada — responde correctamente "quién puede asumir este rol": solo corridas del repositorio y rama correctos. Pero la política de permisos no sigue el principio de privilegio mínimo: AdministratorAccess le da al pipeline la capacidad de hacer absolutamente cualquier cosa en la cuenta de AWS, muy por encima de lo que un pipeline de Terraform sobre los cuatro recursos de Andes Cargo necesita en realidad. La trust policy controla el "quién entra"; la política de permisos controla el "qué puede hacer una vez adentro" — y ambas piezas necesitan estar bien configuradas para que el diseño sea seguro, no solo una de las dos.
Resumen y siguiente paso
En esta lección entendiste el mecanismo completo de la federación OIDC, sin ver todavía una línea de YAML de producción: un JWT de corta vida, firmado por GitHub como identity provider, presentado a AWS STS, validado contra una trust policy de IAM que confía en el emisor en vez de guardar una clave compartida, y credenciales temporales como resultado final. Viste las dos piezas de configuración —permissions: id-token: write del lado de GitHub, la trust policy del lado de AWS— y por qué esto es un salto cualitativo frente a mover una credencial a un Secret, no solo "una clave más escondida".
Antes de avanzar deberías poder: dibujar de memoria el flujo de seis pasos de OIDC; explicar qué controla la trust policy frente a qué controla la política de permisos de un rol; y explicar por qué permissions: id-token: write tiene que declararse explícitamente.
La lección 5 toma este mecanismo y lo traduce a YAML completo y real —exactamente lo que escribirías en un pipeline de producción—, etiquetado desde su primera línea como representativo, con la razón técnica exacta de por qué no corre en esta guía.
Recursos
- GitHub Docs — About security hardening with OpenID Connect — la explicación oficial y completa del mecanismo de OIDC en GitHub Actions, base conceptual de esta lección.
- OpenID Connect — Especificación oficial — el estándar sobre el que GitHub construye su implementación de OIDC; no hace falta leerlo completo para esta guía, pero confirma que OIDC no es una invención propietaria de GitHub.
- AWS Docs — Creating OpenID Connect (OIDC) identity providers — documentación oficial de AWS sobre cómo se configura, del lado de IAM, el identity provider que confía en tokens de GitHub.