Es uno de los momentos más frustrantes trabajando en AWS: tenés un rol con una política que dice explícitamente "Effect": "Allow" sobre el recurso que necesitás, la sintaxis del JSON es correcta, y aun así la petición devuelve AccessDenied. La razón casi siempre es la misma: pensás que existe "una política" que decide el acceso, cuando en realidad IAM evalúa hasta cuatro tipos distintos de política al mismo tiempo, y basta con que uno de ellos no esté de acuerdo para que todo se caiga.
Ya escribí sobre permission boundaries y sobre Service Control Policies. Este post cubre las cuatro piezas que faltaban: identity-based policies, resource-based policies, trust policies y session policies — qué son, en qué se diferencian, y cómo AWS las combina para decidir si tu petición pasa o no.
Effect, Action, Resource, Condition). Lo que cambia es dónde se adjuntan y qué pregunta responden — eso es lo que hay que memorizar, no la sintaxis.
Identity-based policies: "¿qué puede hacer esta identidad?"
Son las que todo el mundo conoce primero. Se adjuntan directamente a un usuario, grupo o rol de IAM y definen qué acciones puede realizar esa identidad. Existen en dos sabores:
- Políticas administradas (managed): reutilizables, versionadas, se pueden adjuntar a múltiples identidades. Pueden ser de AWS (
AmazonS3ReadOnlyAccess) o gestionadas por vos. - Políticas en línea (inline): viven pegadas a una sola identidad, no se pueden reutilizar. Úsalas solo cuando necesitás una relación estrictamente 1 a 1 entre la política y la identidad.
Ejemplo: política identity-based adjunta a un rol de aplicación
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Pedidos"
}
]
}
La pregunta que responde esta política es simple: a esta identidad, ¿qué le permito hacer? No dice nada sobre quién puede usar el recurso desde afuera — eso es trabajo de las resource-based policies.
Resource-based policies: "¿quién puede tocar este recurso?"
En vez de adjuntarse a una identidad, se adjuntan directamente al recurso: un bucket de S3, una cola de SQS, un tema de SNS, una función Lambda, una clave de KMS. La diferencia clave frente a las identity-based: una resource-based policy puede otorgar acceso a un principal de otra cuenta de AWS sin necesidad de un rol intermedio.
Ejemplo: bucket policy que permite lectura desde otra cuenta
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PermitirLecturaCuentaAnalytics",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::998877665544:root"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mi-bucket-reportes/*"
}
]
}
Fijate en el campo Principal: es lo que distingue a una resource-based policy de una identity-based. Las identity-based nunca llevan Principal (la identidad ya está implícita en a quién está adjunta); las resource-based sí, porque tienen que declarar explícitamente quién tiene permiso.
s3:GetObject. Ambas políticas tienen que decir "sí" — ninguna por sí sola alcanza.
Trust policies: el caso especial de los roles
Una trust policy es, técnicamente, una resource-based policy especial: se adjunta a un rol de IAM y define quién tiene permiso de asumir ese rol (no de usar sus permisos directamente, sino de convertirse temporalmente en él vía sts:AssumeRole). Es la que casi siempre causa dolores de cabeza al configurar acceso cross-account o roles para servicios de AWS.
Ejemplo: trust policy que permite a Lambda asumir un rol
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Nota la diferencia con una identity-based policy adjunta al mismo rol: la trust policy responde "¿quién puede convertirse en este rol?", mientras que la identity-based policy adjunta a ese rol responde "una vez que sos este rol, ¿qué podés hacer?". Son dos preguntas distintas, dos políticas distintas, en el mismo rol.
Session policies: el freno temporal que nadie usa
Es la menos conocida de las cuatro. Una session policy no vive adjunta a nada de forma permanente — se pasa dinámicamente en el momento de asumir un rol (vía sts:AssumeRole) o en una federación con identidad web, y solo aplica durante esa sesión temporal. Su función es una sola: restringir aún más los permisos del rol para esa sesión específica. Nunca puede otorgar más de lo que el rol ya tiene — solo puede recortar.
Ejemplo: limitar una sesión asumida a un solo prefijo de S3
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/AccesoReportes \
--role-session-name sesion-cliente-acme \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mi-bucket-reportes/acme/*"
}
]
}'
Aunque el rol AccesoReportes tenga permiso sobre todo el bucket, esta sesión específica queda limitada al prefijo acme/. Es la herramienta perfecta cuando un mismo rol se comparte entre múltiples clientes o tenants y necesitás aislar cada sesión sin crear un rol por cliente.
Cómo se evalúan las cuatro juntas
Este es el punto que la mayoría se salta y después no entiende por qué le niegan el acceso. AWS no elige "la política que gana" — evalúa la intersección de todo lo que aplica:
- ¿Hay un deny explícito en cualquier política? Si sí, se acabó — deny gana siempre, sin excepciones.
- ¿La acción está permitida por la identity-based policy? Si no hay ningún allow, se deniega por defecto.
- Si el recurso tiene una resource-based policy, ¿también permite la acción? (Solo aplica cuando el recurso soporta resource-based policies, como S3 o KMS.)
- ¿Hay una permission boundary? Si existe, actúa como techo: aunque la identity-based policy lo permita, la boundary puede negarlo.
- ¿La sesión se asumió con una session policy? Si sí, también tiene que permitir la acción — es otro filtro que solo puede restringir.
- ¿Hay Service Control Policies (SCP) en la cuenta u OU? Aplican por encima de todo lo anterior, a nivel de organización.
En otras palabras: el acceso final es el resultado de AND lógico entre todas las capas que aplican en ese momento. Si trabajaste con SCP o permission boundaries, ya conocías dos de estas capas — ahora tenés el mapa completo.
| Tipo | Se adjunta a | Pregunta que responde | Puede otorgar cross-account |
|---|---|---|---|
| Identity-based | Usuario, grupo, rol | ¿Qué puede hacer esta identidad? | No |
| Resource-based | El recurso (S3, KMS, SQS, Lambda...) | ¿Quién puede tocar este recurso? | Sí |
| Trust policy | Un rol de IAM (caso especial de resource-based) | ¿Quién puede asumir este rol? | Sí |
| Session policy | Nada permanente — se pasa al hacer AssumeRole | ¿Qué le recorto a esta sesión temporal? | No aplica |
Checklist: cuando IAM te niega algo que "debería funcionar"
- ¿Hay algún
Denyexplícito en cualquier política que aplique? Revisalo primero, siempre gana. - ¿La identity-based policy de quien hace la petición incluye la acción y el recurso exactos?
- Si el recurso soporta resource-based policies, ¿también permite explícitamente a ese principal?
- ¿Existe una permission boundary en la identidad que podría estar recortando el permiso?
- Si la petición viene de un rol asumido, ¿se pasó una session policy que restringe más de lo esperado?
- ¿Hay una SCP a nivel de cuenta u OU que esté bloqueando la acción antes de que IAM la evalúe siquiera?
Conclusión
La mayoría de los errores de "acceso denegado sin razón aparente" en AWS no son bugs — son la evidencia de que hay más de una política decidiendo, y una de ellas está diciendo que no. Entender que identity-based, resource-based, trust y session policies responden preguntas distintas (y se evalúan todas juntas, no una a la vez) es la diferencia entre debuggear IAM por prueba y error, y debuggearlo con certeza.
En el próximo post de esta serie cubro AWS STS y acceso cross-account: cómo funciona realmente AssumeRole, el flujo completo de credenciales temporales, y los patrones de diseño para dar acceso entre cuentas sin compartir llaves de acceso permanentes.