IAM en AWS: identity-based, resource-based, trust y session policies explicadas

"¿Por qué mi política no funciona si el JSON está perfecto?" Porque en IAM no hay un solo tipo de política — hay cuatro, cada una responde una pregunta distinta, y AWS las evalúa todas juntas antes de decidir si te deja pasar.

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.

💡 Nota
Todas usan el mismo lenguaje (JSON con 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:

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.

⚠️ Acceso cross-account necesita las dos caras de acuerdo
Que el bucket policy permita a la cuenta 998877665544 no es suficiente. La identidad que hace la petición desde esa cuenta también necesita una identity-based policy que le permita 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:

  1. ¿Hay un deny explícito en cualquier política? Si sí, se acabó — deny gana siempre, sin excepciones.
  2. ¿La acción está permitida por la identity-based policy? Si no hay ningún allow, se deniega por defecto.
  3. 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.)
  4. ¿Hay una permission boundary? Si existe, actúa como techo: aunque la identity-based policy lo permita, la boundary puede negarlo.
  5. ¿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.
  6. ¿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"

  1. ¿Hay algún Deny explícito en cualquier política que aplique? Revisalo primero, siempre gana.
  2. ¿La identity-based policy de quien hace la petición incluye la acción y el recurso exactos?
  3. Si el recurso soporta resource-based policies, ¿también permite explícitamente a ese principal?
  4. ¿Existe una permission boundary en la identidad que podría estar recortando el permiso?
  5. Si la petición viene de un rol asumido, ¿se pasó una session policy que restringe más de lo esperado?
  6. ¿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.