La access key que tenía AdministratorAccess
Una IAM access key terminó en un gist público con permisos de admin completos. La rotamos en once minutos. Lo que asustó fue cuánto tiempo estuvo ahí.

Un contratista pegó un ejemplo de curl en un gist público para pedir ayuda con un upload
a S3. El ejemplo incluía AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY y un one-liner que
funcionaba. Lo que el gist no decía era que la key pertenecía a una cuenta de servicio
llamada deploy-bot, y que tenía AdministratorAccess porque alguien había estado “solo
probando algo” tres meses antes.
GitGuardian nos mandó un mail a las 02:14 un martes. A las 02:25 la key estaba muerta. A las 06:00 sabíamos el daño: nada. A las 10:00 sabíamos por qué eso fue en gran parte suerte.
Cómo llegó ahí
El contratista no exfiltró nada. Copió un snippet que funcionaba desde una página interna
del wiki escrita seis meses atrás, cuando crearon deploy-bot. La página del wiki estaba
mal el día que la escribieron y nadie la volvió a abrir.
La key en sí estaba bien al crearla: scope a push en ECR y deploy en ECS en un cluster.
Después un ingeniero de plataforma le attachó AdministratorAccess durante un deploy de
viernes que iba mal, juró que lo sacaría, abrió un ticket, cerró el ticket cuando el deploy
salió verde, y nunca más tocó IAM.
El gist estuvo público cuatro días antes de que el scanner lo encontrara. No sabemos si alguien más lo vio.
Cómo se vieron esos once minutos de respuesta
Este es el orden que importó, no el que se sintió satisfactorio:
- Desactivar la key (no borrar todavía; querés el trail).
- CloudTrail lookup sobre
AccessKeyIdde los últimos 90 días. Filtrar primero sinerrorCode, después leer todo. - Asumir compromiso en todo lo que el principal tocó: buckets S3, env vars de Lambda, paths de Secrets Manager. Rotar lo que era legible, no solo lo que parece sospechoso.
- Emitir una key nueva solo cuando entendés el blast radius. Un reemplazo apurado con la misma policy repite el problema.
La ventana de CloudTrail mostró llamadas Describe* desde un ISP residencial en otro país,
después nada. Pudo ser un scanner. Pudo ser alguien cuidadoso. Tratamos ambos igual.
Qué deberíamos haber tenido antes de las 02:14
| Control | Qué habría cambiado |
|---|---|
| Sin keys de larga duración en humanos o bots | OIDC desde CI a AWS. El gist hubiera tenido un token que expira en minutos |
Permission boundary en deploy-bot |
El attach de admin hubiera fallado en la API |
| Alarma por edad de access key | La key tenía 94 días; no teníamos alerta |
| Secret scanning en wiki interno | El wiki fue el leak real; el gist fue el amplificador |
La policy IAM que quedó:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "iam:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalTag/role": "iam-admin"
}
}
}
]
}
Un permission boundary en cada principal no-admin. deploy-bot no puede darse admin aunque
alguien le attaché una policy más amplia por error.
La parte que me sigue dando vueltas
Celebramos la rotación en once minutos. Seguridad amó el runbook. Dirección amó la slide del timeline.
La falla real tenía tres meses y era invisible: una policy de admin que nadie recordaba haber attachado, en una key que nadie recordaba haber creado, copiada desde una página que nadie recordaba haber escrito. El gist fue solo el momento en que la deuda se hizo visible.
Rotá rápido. Pero el trabajo que previene el próximo es higiene aburrida de IAM: boundaries,
sin keys de larga duración, y tratar AdministratorAccess como un hacha de bomberos. Detrás
del vidrio, logueada, y devuelta al instante.