Módulo 2: Federated Identity And Least Privilege Iam
2. El antipatrón de las claves de larga vida, retomado a fondo
Descripción
THREAT-MODEL.md ya nombró esto como TM-01: "CI/CD pipeline authenticates with long-lived static credentials". RISK-MAP.md ya lo puso primero en la fila de resolución. Esta lección no repite ese hallazgo — lo lleva más profundo de lo que cualquier guía anterior de este ecosistema pudo, con la cita completa de la auditoría de mercado que fija el peso de esta guía entera, y con una demostración real, ejecutada con Git de verdad, de por qué "borré el archivo" nunca es lo mismo que "el secreto ya no existe".
Conexión con el módulo
La lección 1 te dio el mapa. Esta lección construye el caso completo de por qué TM-01 está primero en RISK-MAP.md, no en cualquier otro lugar — no por burocracia de orden alfabético, sino porque una credencial de larga vida es, de los siete riesgos del Módulo 1, el que tiene el radio de explosión más amplio y el mecanismo de fuga menos intuitivo. La lección 3 toma el problema que esta lección acaba de extender y presenta la solución completa, pieza por pieza, antes de tocar HCL.
La cita completa, sin recortar
Del diseño de esta guía, citando textual la auditoría de mercado (src/paths/aws-cloud-ecosystem/VALIDACION.md, jul-2026):
"IAM y radio de explosión son consenso en foros y la habilidad de seguridad que sí se usa a diario; el vector dominante de 2026 ya no es el humano descuidado sino el agente con credenciales [...]. Y el hueco de competencia es total: Seguridad de cadena de suministro: cero. Ningún temario menciona SBOM, firma de imágenes (cosign/Sigstore), escaneo (Trivy) ni —lo más grave— autenticación OIDC de GitHub Actions hacia AWS en lugar de claves de larga vida. Se sigue enseñando el antipatrón."
Lee esa última frase dos veces: "se sigue enseñando el antipatrón". No dice que la industria no sepa que OIDC existe — dice que el material educativo que la auditoría revisó, en 2026, sigue mostrando AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY como la forma normal de autenticar un pipeline, sin mencionar la alternativa que elimina la clase entera de riesgo. Es la razón exacta por la que el Módulo 2 completo de esta guía existe, y por la que aparece segundo en el orden de módulos, justo después de threat modeling.
Analogía: la llave maestra copiada frente al pase de visitante
Ya usaste esta analogía en cicd-and-gitops-on-aws-guide, así que vale la pena extenderla, no repetirla. Imagina que, además de copiar la llave maestra de un edificio, alguien también fotografía esa copia antes de devolverla. Cambiar la cerradura invalida la llave física — pero la fotografía sigue existiendo, en el rollo de alguien, indefinidamente, sin que cambiar la cerradura la afecte en absoluto. Si esa fotografía llega a la persona equivocada meses después, la cerradura nueva no la protege de nada: la fotografía nunca dependió de que la cerradura vieja siguiera funcionando, solo de que alguien la conservara.
Un AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY commiteado a un repositorio es exactamente esa fotografía. Rotar la clave en la consola de IAM —el equivalente a cambiar la cerradura— invalida la clave para autenticarse contra AWS, pero no borra el commit donde esa clave quedó escrita, en texto plano, en el historial de Git. Cualquiera con acceso de lectura al repositorio —incluido cualquiera que lo haya clonado antes de que alguien "arreglara" el problema— sigue teniendo la fotografía completa, para siempre, aunque la cerradura ya haya cambiado.
Ejemplo trabajado: un secreto que se "borra" y no desaparece
Esta demostración usa un repositorio de Git desechable, nada de andes-cargo-infra/ — el objetivo es que veas, con tus propias manos, la mecánica exacta, no que la imagines.
Paso 1 — Commitear un secreto de prueba
git init -q -b main
cat > .secrets <<'EOF'
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY
EOF
git add .secrets
git commit -m "Add LocalStack dummy credentials for CI"
Nada distinto todavía de cualquier commit — el mismo patrón que .secrets gitignoreado de cicd-and-gitops-on-aws-guide intentó evitar desde el principio, aquí a propósito sin .gitignore, para ver qué pasa cuando ese paso se salta.
Paso 2 — "Arreglarlo": borrar el archivo y commitear de nuevo
rm .secrets
git add .secrets
git commit -m "Remove .secrets, add to .gitignore"
Este es, exactamente, el reflejo instintivo de alguien que se da cuenta del error: borrar el archivo, commitear el borrado, sentir que el problema quedó resuelto.
Paso 3 — Confirmar que el archivo ya no existe
git status --short
Qué esperar (literal, ejecutado para escribir esta lección):
Sin salida — un working tree completamente limpio, sin ningún archivo pendiente, sin .secrets en ningún lado del directorio. Hasta aquí, todo confirma la sensación de "está resuelto".
Paso 4 — La prueba: el secreto sigue completo, en el historial
git log --oneline
Qué esperar (literal — los hashes son deterministas en esta demostración porque se fijaron GIT_AUTHOR_DATE/GIT_COMMITTER_DATE; en un repositorio real, tus hashes serán distintos, pero el comportamiento que sigue es idéntico siempre):
353e6e9 Remove .secrets, add to .gitignore
2c71ad5 Add LocalStack dummy credentials for CI
Ahora, el comando que de verdad importa — pedirle a Git el contenido exacto de .secrets tal como existía en el primer commit, el que el Paso 2 supuestamente "eliminó":
git show 2c71ad5:.secrets
Qué esperar (literal, ejecutado para escribir esta lección):
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY
Ahí está, completo, carácter por carácter — el mismo secreto que el Paso 3 confirmó que "ya no existe" en el directorio de trabajo. git show <commit>:<archivo> no busca en el disco: busca en los objetos que Git ya guardó, de forma permanente, en el momento del primer commit. Borrar el archivo del directorio de trabajo y volver a commitear no tocó ese objeto en absoluto — solo agregó un segundo commit que dice "a partir de aquí, este archivo no forma parte del snapshot". El objeto del primer commit —con el secreto completo adentro— sigue ahí, alcanzable por cualquiera con acceso de lectura al repositorio, para siempre, a menos que alguien reescriba el historial completo (git filter-repo, BFG Repo-Cleaner, o similar) y fuerce que todos los clones existentes se descarten — algo que casi nunca es realista una vez que un repositorio tiene más de un colaborador.
Por qué esto es peor que "un archivo mal guardado"
Vale la pena ser preciso sobre qué hace que este mecanismo sea, específicamente, más peligroso que perder un archivo de configuración cualquiera:
El historial de Git no tiene, por diseño, ningún mecanismo de expiración. Un archivo en un disco puede sobrescribirse, un log puede rotar y perder entradas viejas — el historial de Git está diseñado explícitamente para lo contrario: preservar cada versión de cada archivo, para siempre, como su propósito central. La misma propiedad que hace que Git sea la herramienta correcta para versionar andes-cargo-infra/ es, sin ningún ajuste adicional, la misma propiedad que convierte un secreto commiteado en un secreto permanente.
El acceso al secreto no requiere ningún privilegio especial, solo lectura del repositorio. No hace falta acceso de administrador a AWS, no hace falta ningún exploit — cualquiera que pueda hacer git clone (o que ya lo haya hecho antes del "arreglo") tiene el secreto completo, con el mismo comando de tres palabras del Paso 4.
El "arreglo" intuitivo (borrar el archivo) no solo no ayuda — puede generar una falsa sensación de que el problema ya está resuelto, exactamente el mismo antipatrón de falso sentido de seguridad que la lección 2 del Módulo 1 nombró para un pipeline en verde. Un equipo que ve git status limpio y sigue adelante sin rotar la credencial real en AWS deja la clave activa, expuesta en el historial, indefinidamente.
La única mitigación real: la credencial deja de existir, no el archivo
Compara las dos estrategias, una al lado de la otra:
| Estrategia | ¿El secreto sigue en el historial de Git? | ¿El secreto sigue siendo válido contra AWS? |
|---|---|---|
| Borrar el archivo, commitear el borrado (Paso 2 de esta lección) | Sí, para siempre | Sí — nada cambió del lado de AWS |
Rotar/revocar la credencial en IAM (aws iam delete-access-key o equivalente) | Sí, sigue en el historial | No — la clave queda inutilizable, sin importar cuántas copias existan |
| OIDC (este módulo, lecciones 3-6): nunca hay ninguna clave de larga vida que commitear | No aplica — no hay nada que filtrar | No aplica — nunca existió una clave permanente |
La fila del medio es la mitigación real hoy, si un secreto ya se filtró: revocar la credencial en el sistema que la emitió, no editar el repositorio. La fila de abajo es lo que este módulo construye de raíz: si nunca existe una clave de larga vida, no hay ninguna clave que pueda terminar, por accidente, en un commit.
Errores comunes
Confundir git rm con "eliminar el secreto" (de nombre engañoso). Qué pasa: alguien ve el comando git rm --cached .secrets y asume, por el verbo "rm", que está removiendo el archivo de la existencia del proyecto. Cómo detectarlo: si tu plan de remediación termina en un comando git rm sin ningún paso adicional de rotación de credenciales. Cómo corregirlo: git rm (con o sin --cached) solo afecta los commits futuros — deja de incluir el archivo desde ese punto en adelante. No toca, ni puede tocar, los commits que ya existen. La demostración de esta lección usa exactamente ese patrón (rm + git add + git commit) para probarlo con evidencia, no con una afirmación.
Pensar que un repositorio privado (no público en GitHub) resuelve el problema (de alcance). Qué pasa: alguien argumenta que, como el repositorio nunca fue público, el secreto commiteado nunca estuvo realmente expuesto. Cómo detectarlo: si tu justificación para no rotar una credencial filtrada es "el repo es privado". Cómo corregirlo: "privado" limita quién puede verlo hoy —cualquier colaborador actual, cualquiera con un token de acceso, cualquier integración de CI/CD conectada, cualquier fork ya hecho—, pero no reduce a cero el riesgo, y no dice nada sobre quién tuvo acceso en el pasado o podría tenerlo en el futuro (un colaborador que se va del equipo, una integración de terceros mal configurada). El nivel de exposición cambia, no desaparece.
Rotar la credencial pero dejarla igual en el historial, sin documentar la rotación (de proceso incompleto). Qué pasa: un equipo rota la clave correctamente en IAM, pero nadie deja constancia de cuándo ni por qué — meses después, alguien encuentra el secreto viejo en el historial y pierde tiempo investigando si sigue siendo válido. Cómo detectarlo: si tu único registro de una rotación de emergencia es la memoria de quien la hizo. Cómo corregirlo: exactamente el mismo principio que ya viste en THREAT-MODEL.md (Módulo 1, lección 7) — un evento de seguridad real se documenta, con fecha y causa, no solo se resuelve en silencio.
Ejercicios
Ejercicio 1 — Reproduce la demostración con tus propios valores. Repite los cuatro pasos de esta lección, pero con un secreto de prueba distinto (por ejemplo, una API key inventada de otro servicio). Confirma que git show <commit>:<archivo> recupera el contenido completo aunque git status muestre un working tree limpio.
Ver solución
Si seguiste los cuatro pasos correctamente, git status --short después del segundo commit debería mostrar una salida vacía (working tree limpio), y git show <hash-del-primer-commit>:<nombre-del-archivo> debería devolver el contenido exacto que escribiste en el Paso 1, sin ninguna diferencia. Si tu git show falla con fatal: path <archivo> does not exist in <hash>, revisa que estés usando el hash del primer commit (el que sí incluía el archivo), no el segundo.
Ejercicio 2 — Explica por qué TM-01 está primero en RISK-MAP.md, con el vocabulario de radio de explosión del Módulo 1. Usando el criterio de la sección Decision de RISK-MAP.md ("identidad primero, porque una identidad acotada reduce el radio de explosión de todo lo demás"), explica en tus propias palabras por qué resolver TM-01 antes que cualquier otro riesgo tiene sentido, más allá de "es el primero de la lista".
Ver solución
Una respuesta completa suena, más o menos, así: "Una credencial de larga vida comprometida no es un riesgo aislado — es un riesgo que amplifica a todos los demás. Si alguien obtiene la clave estática del pipeline, puede hacer cualquier cosa que esa clave permita: modificar cualquier recurso de andes-cargo-infra/, no solo uno. Resolver identidad primero (OIDC, con credenciales que expiran en minutos) reduce ese radio de explosión antes de construir cualquier otro control, exactamente el mismo razonamiento que ya viste en la lección 6 del Módulo 1 sobre el incidente destroy: mientras más ancho el radio de explosión de la identidad que ejecuta un cambio, más daño puede hacer un solo error o una sola credencial filtrada."
Ejercicio 3 — Defiende, con la tabla de esta lección, por qué "borrar el archivo" nunca debería ser el primer paso de un plan de remediación real. Un compañero, ante un secreto recién descubierto en un commit de hace tres meses, propone como primer paso "borrar el archivo y hacer commit". ¿Qué le dirías, usando la tabla de mitigaciones de esta lección?
Ver solución
Una respuesta completa suena, más o menos, así: "Borrar el archivo es, como mucho, el segundo paso — y ni siquiera es estrictamente necesario si la credencial ya se rotó. Lo urgente es la fila del medio de la tabla: rotar o revocar esa credencial específica en el sistema que la emitió (IAM, en este caso), porque eso es lo único que hace que el secreto filtrado deje de ser útil para nadie, sin importar cuántas copias existan en el historial de Git, en clones locales, o en backups. Borrar el archivo del repositorio, sin rotar la credencial, deja la clave activa, expuesta, y ahora también — sin que nadie lo note — con una falsa sensación de que 'ya se arregló'."
Resumen y siguiente paso
En esta lección viste la cita completa de la auditoría de mercado que fija el peso de este módulo —"se sigue enseñando el antipatrón"— y demostraste, con Git real, no con una afirmación, que borrar un archivo y volver a commitear no elimina un secreto del historial: git show <commit>:<archivo> lo recupera completo, para siempre, mientras el working tree se ve perfectamente limpio. Viste la única mitigación real ante un secreto ya filtrado (rotar la credencial, no editar el repositorio) y por qué eso sigue siendo, en el mejor de los casos, control de daños — no prevención.
Antes de avanzar deberías poder: reproducir la demostración de Git de memoria; explicar por qué el historial de Git no tiene mecanismo de expiración por diseño; y defender, con evidencia, por qué "borrar el archivo" nunca es, por sí solo, un plan de remediación completo.
La lección 3 presenta la solución completa a este problema, pieza por pieza: cómo funciona la federación OIDC, del lado de AWS, para que nunca exista, en ningún commit, ninguna credencial de larga vida que filtrar.
Recursos
- Git Docs —
git show— referencia completa del comando usado en el Paso 4 de esta lección para recuperar un archivo de un commit específico. - GitHub Docs — Removing sensitive data from a repository — la guía oficial de GitHub sobre por qué reescribir el historial (
git filter-repo) es necesario, y por qué sigue sin ser suficiente sin rotar la credencial. src/paths/aws-cloud-ecosystem/VALIDACION.md— la fuente completa de la cita de esta lección: "se sigue enseñando el antipatrón".- Este curso, Módulo 1, lección 7 (
THREAT-MODEL.md, hallazgoTM-01) — el registro original de este riesgo, con la evidencia exacta del.secretsheredado decicd-and-gitops-on-aws-guide.