IAM avanzado — SCP, RCP, ABAC y confused deputy
Cuándo usar SCP frente a RCP, control de acceso por etiquetas (ABAC), roles entre cuentas y cómo External ID y aws:SourceArn cierran el confused deputy.
Con la lógica de evaluación clara, toca usar las piezas que la hacen escalar en una organización real: las barandillas que limitan cuentas enteras, un modelo de permisos que no obliga a reescribir políticas cada vez que creas un recurso, y los roles que conectan cuentas sin repartir llaves. Y con los roles entre cuentas llega un fallo clásico y sutil —el confused deputy— que conviene saber cerrar antes de abrir el primero.
SCP frente a RCP: dos barandillas complementarias#
Ambas son políticas de AWS Organizations que solo limitan (nunca conceden) y requieren all features habilitadas. La diferencia está en a quién acotan:
- Una SCPSCPPolítica de AWS Organizations que fija el máximo de permisos de los principals (identidades) de una cuenta; solo limita, nunca concede. fija el máximo de permisos de los principals (roles y usuarios) dentro de tus cuentas. Responde a «¿qué puede hacer mi gente?».
- Una RCPRCPPolítica de AWS Organizations que fija el máximo de permisos sobre los recursos de una cuenta, incluso frente a principals externos; solo limita. fija el máximo de permisos sobre los recursos de tus cuentas, incluso frente a principals externos a la organización. Responde a «¿quién puede tocar mis recursos?».
Las RCP llegaron en noviembre de 2024 y cubren un conjunto de servicios en expansión (S3, STS, KMS, SQS, Secrets Manager y más; consulta la lista vigente en la doc). Ninguna de las dos afecta a la cuenta de gestión, y las RCP además no restringen los service-linked roles.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "SoloEuropa", "Effect": "Deny", "NotAction": ["iam:*", "sts:*", "cloudfront:*", "route53:*"], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["eu-west-1", "eu-central-1"] } } } ]}Ningún principal de la cuenta puede operar fuera de esas regiones (con una excepción para servicios globales).
{ "Version": "2012-10-17", "Statement": [ { "Sid": "SoloDentroDeLaOrg", "Effect": "Deny", "Principal": "*", "Action": ["s3:*", "sts:AssumeRole"], "Resource": "*", "Condition": { "StringNotEqualsIfExists": { "aws:PrincipalOrgID": "o-ejemplo123" }, "Bool": { "aws:PrincipalIsAWSService": "false" } } } ]}Ningún principal ajeno a la organización puede acceder a esos recursos, por muy permisiva que sea una bucket policy concreta.
La regla práctica: usa una SCP para contener lo que tus identidades pueden hacer, y una RCP para impedir que tus recursos queden expuestos a cuentas externas. A menudo quieres las dos.
ABAC: permisos por etiqueta, no por lista#
En un enfoque clásico (RBAC) escribes una política por cada conjunto de recursos. No escala: cada equipo o entorno nuevo es otra política. El ABACABACModelo de autorización que concede acceso comparando etiquetas (tags) del principal y del recurso en vez de enumerar permisos uno a uno. invierte el planteamiento: concede acceso comparando etiquetas del principal y del recurso.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": "*", "Condition": { "StringEquals": { "aws:ResourceTag/equipo": "${aws:PrincipalTag/equipo}" } } } ]}Esta única política dice «puedes actuar sobre los recursos cuya etiqueta equipo coincide con la tuya». El rol del equipo de Citas, con equipo=citas, solo toca recursos etiquetados igual. Cuando mañana creéis un bucket nuevo con esa etiqueta, el acceso ya funciona sin tocar IAM.
Roles entre cuentas y trust policies#
Un rol tiene dos políticas de naturaleza distinta. La de permisos dice qué puede hacer; la trust policy (política de confianza) dice quién puede asumirlo. Para acceso entre cuentas, la trust policy nombra al principal externo y la acción sts:AssumeRole:
{ "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::222222222222:role/citas-deploy" }, "Action": "sts:AssumeRole"}Como vimos en la lógica de evaluación, sts:AssumeRole siempre debe permitirse explícitamente en la trust policy: aquí no hay atajo de «la identidad calla pero el recurso concede». Y recuerda que el otro lado también necesita su Allow para sts:AssumeRole sobre este rol.
El confused deputy#
El problema aparece cuando un servicio de terceros asume un rol en tu cuenta en nombre de sus clientes. Si tu trust policy solo comprueba «¿es la cuenta del proveedor?», no distingue a nombre de qué cliente actúa. Un atacante que también sea cliente de ese proveedor puede pedirle que opere sobre el ARN de tu rol: el proveedor —el «diputado»— tiene el permiso, y el atacante aporta el objetivo.
Paso 1 de 5El escenario
Un servicio de terceros (monitorización, backups…) necesita entrar en tu cuenta. Le das un rol asumible: su trust policy permite que la cuenta del proveedor haga sts:AssumeRole.
Para terceros: External ID#
La defensa es un External ID: un identificador único y secreto que el proveedor asigna a cada cliente y que debe incluir en la llamada AssumeRole. Tu trust policy lo exige con sts:ExternalId:
{ "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::999999999999:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "citas-7f3a1c9e-no-adivinable" } }}El valor lo controla el proveedor, no tú ni los demás clientes. Un atacante que pase tu ARN no conoce tu External ID, así que su AssumeRole se deniega.
Para servicios de AWS: aws:SourceArn y aws:SourceAccount#
Cuando el diputado es un servicio de AWS (CloudTrail escribiendo en tu bucket, SNS publicando, etc.), el principal es el service principal del servicio. Ahí la protección vive en la política de recurso y usa aws:SourceArn (el recurso de origen concreto) o aws:SourceAccount (la cuenta de origen):
Sin condición (confused deputy)
Con aws:SourceArn
{ "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::citas-logs/*"}{ "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::citas-logs/*", "Condition": { "StringEquals": { "aws:SourceAccount": "111122223333" }, "ArnLike": { "aws:SourceArn": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/citas-org" } }}Usa aws:SourceArn cuando puedas identificar el recurso de origen exacto (un trail, una fleet…) y aws:SourceAccount cuando quieras permitir cualquier recurso de una cuenta concreta. Con esto, un trail de otra cuenta no puede engañar al servicio para escribir en tu bucket.
Tu turno
Audita tus trust policies y tus accesos externos
Sobre tu cuenta o tu lab:
- Lista los roles cuya trust policy acepta un principal externo (otra cuenta o un
Service). ¿Cuáles deberían tenersts:ExternalId,aws:SourceArnoaws:SourceAccounty no lo tienen? - ¿Hay alguna trust policy con
"Principal": { "AWS": "...:root" }sin condiciones? Anota el riesgo de confused deputy. - Si usas ABAC, revisa quién puede cambiar las etiquetas
equipo/entornoque gobiernan el acceso. - Elige una barandilla que falte: ¿una SCP de regiones, una RCP para cerrar el acceso externo? Escribe el borrador.
Reto
¿Por qué no me invento yo el External ID en lugar de pedírselo al proveedor?
▸Mostrar solución
Porque el objetivo es que el valor lo controle el proveedor, no sus clientes. Si cada cliente eligiera el suyo, un atacante podría registrarse en el proveedor, fijar como External ID el que tú usas (si lo conociera o lo adivinara) y volver a estar en la misma situación. Al generarlo y custodiarlo el proveedor de forma única por cliente, el atacante no puede reproducir el tuyo.
Reto
Tengo una SCP que deniega el acceso externo. ¿Necesito también una RCP?
▸Mostrar solución
Sí, si quieres cubrir el caso de verdad. Una SCP limita a los principals de tus cuentas: no impide que un principal de otra organización acceda a un recurso tuyo cuya política de recurso lo permita. Ese escenario —el externo tocando tu recurso— es exactamente lo que ataja una RCP.
En resumen#
- SCP limita a tus identidades; RCP limita el acceso a tus recursos, incluso desde fuera de la org. Ambas solo recortan y no tocan la cuenta de gestión.
- ABAC concede por etiquetas (
aws:PrincipalTagvsaws:ResourceTag): escala sin reescribir políticas, pero exige gobernar quién etiqueta. - La trust policy decide quién asume un rol;
sts:AssumeRolesiempre se permite explícitamente. - El confused deputy se cierra con External ID (terceros que asumen roles) y
aws:SourceArn/aws:SourceAccount(servicios de AWS sobre tus recursos), y se puede centralizar con RCP yaws:SourceOrgID.