Los Permission Boundaries (límites de permiso) son una de las mejores prácticas de IAM para limitar el alcance de los permisos, incluso cuando se usan políticas amplias como *. En este post explicamos qué son, por qué son cruciales para evitar el uso del usuario root, y cómo crearlos y asignarlos usando AWS CDK en TypeScript y Python. Incluye ejemplos listos para desplegar y recomendaciones para entornos multi‑account y AWS Identity Center.
¿Qué es un Permission Boundary?
Un Permission Boundary es una política gerenciada de IAM que establece el máximo nivel máximo de permisos que una entidad IAM (rol o usuario) puede tener. Incluso si se le adjuntan políticas de permisos que otorgan * (todos los permisos), el boundary actúa como un techo: el permiso efectivo es la intersección entre la política de permisos y el boundary.
¿Por qué son importantes para evitar el usuario root?
El usuario root de AWS tiene acceso ilimitado e irrevocable. Usarlo para operaciones diarias es el error de seguridad más crítico que puedes cometer. Sin embargo, a veces se necesita un usuario con altos privilegios para tareas administrativas. En lugar de crear un usuario con políticas AdministratorAccess (que implica *), puedes:
- Crear un rol o usuario con políticas amplias (por ejemplo,
PowerUserAccesso una política personalizada que permita casi todo). - Adjuntarle un Permission Boundary que niegue acciones peligrosas como
iam:DeleteAccountPasswordPolicy,organizations:DeleteOrganization,account:CloseAccount,billing:*, etc.
Así, incluso si se comprometen las credenciales, el atacante no podrá realizar acciones que el boundary haya denegado.
Diseño de un Permission Boundary seguro
Un buen bloque de permissions debería:
- Negar explícitamente acciones que podrían comprometer la cuenta o la organización:
iam:*que eliminen o modifiquen políticas de contraseñas, MFA, usuarios, grupos, roles.organizations:*que eliminen la organización, unidades organizativas o cuentas.account:*relacionadas con cierre de cuenta.billing:*yaws-portal:*para evitar modificaciones de facturación o cierre de cuenta.sts:AssumeRolehacia roles que no deberían ser asumidos (opcional, según tu modelo).- Permitir el resto de las acciones necesarias para las tareas administrativas.
- Ser reutilizable: crear un boundary gerenciado y adjuntarlo a múltiples roles/usuarios.
Ejemplo de Permission Boundary (política gerenciada)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"iam:DeleteAccountPasswordPolicy",
"iam:DeleteVirtualMFADevice",
"iam:AttachUserPolicy",
"iam:DetachUserPolicy",
"iam:PutUserPolicy",
"iam:DeleteUserPolicy",
"iam:AttachGroupPolicy",
"iam:DetachGroupPolicy",
"iam:PutGroupPolicy",
"iam:DeleteGroupPolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:CreateLoginProfile",
"iam:DeleteLoginProfile",
"iam:UpdateLoginProfile",
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:UpdateAccessKey",
"iam:CreateSigningCertificate",
"iam:DeleteSigningCertificate",
"iam:UpdateSigningCertificate",
"iam:CreateServiceSpecificCredential",
"iam:DeleteServiceSpecificCredential",
"iam:ResetServiceSpecificCredential",
"iam:UpdateServiceSpecificCredential",
"iam:AddUserToGroup",
"iam:RemoveUserFromGroup",
"iam:CreateGroup",
"iam:DeleteGroup",
"iam:CreateRole",
"iam:DeleteRole",
"iam:CreatePolicy",
"iam:DeletePolicy",
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
"organizations:DeleteOrganization",
"organizations:DeleteOrganizationalUnit",
"organizations:DeleteAccount",
"organizations:LeaveOrganization",
"account:CloseAccount",
"billing:*",
"aws-portal:*"
],
"Resource": "*"
}
]
}
Implementación con AWS CDK
A continuación se muestra cómo definir el Permission Boundary como una política gerenciada y luego crear un rol que lo utilice. Se proporcionan ejemplos en TypeScript y Python.
TypeScript
import * as iam from 'aws-cdk-lib/aws-iam';
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class PermissionBoundaryStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 1. Definir el Permission Boundary (policy gerenciada)
const adminBoundary = new iam.ManagedPolicy(this, 'AdminPermissionBoundary', {
managedPolicyName: 'AdminPermissionBoundary',
description: 'Techo de permisos para roles admin - bloquea acciones destructivas',
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['*'],
resources: ['*'],
}),
new iam.PolicyStatement({
effect: iam.Effect.DENY,
actions: [
'iam:DeleteAccountPasswordPolicy',
'iam:DeleteVirtualMFADevice',
'iam:AttachUserPolicy',
'iam:DetachUserPolicy',
'iam:PutUserPolicy',
'iam:DeleteUserPolicy',
'iam:AttachGroupPolicy',
'iam:DetachGroupPolicy',
'iam:PutGroupPolicy',
'iam:DeleteGroupPolicy',
'iam:AttachRolePolicy',
'iam:DetachRolePolicy',
'iam:PutRolePolicy',
'iam:DeleteRolePolicy',
'iam:CreateLoginProfile',
'iam:DeleteLoginProfile',
'iam:UpdateLoginProfile',
'iam:CreateAccessKey',
'iam:DeleteAccessKey',
'iam:UpdateAccessKey',
'iam:CreateSigningCertificate',
'iam:DeleteSigningCertificate',
'iam:UpdateSigningCertificate',
'iam:CreateServiceSpecificCredential',
'iam:DeleteServiceSpecificCredential',
'iam:ResetServiceSpecificCredential',
'iam:UpdateServiceSpecificCredential',
'iam:AddUserToGroup',
'iam:RemoveUserFromGroup',
'iam:CreateGroup',
'iam:DeleteGroup',
'iam:CreateRole',
'iam:DeleteRole',
'iam:CreatePolicy',
'iam:DeletePolicy',
'iam:CreatePolicyVersion',
'iam:DeletePolicyVersion',
'iam:SetDefaultPolicyVersion',
'organizations:DeleteOrganization',
'organizations:DeleteOrganizationalUnit',
'organizations:DeleteAccount',
'organizations:LeaveOrganization',
'account:CloseAccount',
'billing:*',
'aws-portal:*'
],
resources: ['*'],
}),
],
});
// 2. Rol de administración que usa el boundary
const adminRole = new iam.Role(this, 'SecurityAdminRole', {
roleName: 'SecurityAdminRole',
assumedBy: new iam.AccountRootPrincipal(), // o bien un principal de Identity Center
permissionsBoundary: adminBoundary, // ← aquí se aplica el boundary
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('job-function/SecurityAudit')
],
});
// 3. Output para referencia
new cdk.CfnOutput(this, 'BoundaryArn', {
value: adminBoundary.managedPolicyArn,
description: 'ARN del Permission Boundary para auditar',
});
}
}
Python
from aws_cdk import (
aws_iam as iam,
core as cdk,
)
class PermissionBoundaryStack(cdk.Stack):
def __init__(self, scope: cdk.Construct, id: str, **kwargs):
super().__init__(scope, id, **kwargs)
# 1. Permission Boundary
admin_boundary = iam.ManagedPolicy(
self, 'AdminPermissionBoundary',
managed_policy_name='AdminPermissionBoundary',
description='Techo de permisos para roles admin - bloquea acciones destructivas',
statements=[
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=['*'],
resources=['*'],
),
iam.PolicyStatement(
effect=iam.Effect.DENY,
actions=[
'iam:DeleteAccountPasswordPolicy',
'iam:DeleteVirtualMFADevice',
'iam:AttachUserPolicy',
'iam:DetachUserPolicy',
'iam:PutUserPolicy',
'iam:DeleteUserPolicy',
'iam:AttachGroupPolicy',
'iam:DetachGroupPolicy',
'iam:PutGroupPolicy',
'iam:DeleteGroupPolicy',
'iam:AttachRolePolicy',
'iam:DetachRolePolicy',
'iam:PutRolePolicy',
'iam:DeleteRolePolicy',
'iam:CreateLoginProfile',
'iam:DeleteLoginProfile',
'iam:UpdateLoginProfile',
'iam:CreateAccessKey',
'iam:DeleteAccessKey',
'iam:UpdateAccessKey',
'iam:CreateSigningCertificate',
'iam:DeleteSigningCertificate',
'iam:UpdateSigningCertificate',
'iam:CreateServiceSpecificCredential',
'iam:DeleteServiceSpecificCredential',
'iam:ResetServiceSpecificCredential',
'iam:UpdateServiceSpecificCredential',
'iam:AddUserToGroup',
'iam:RemoveUserFromGroup',
'iam:CreateGroup',
'iam:DeleteGroup',
'iam:CreateRole',
'iam:DeleteRole',
'iam:CreatePolicy',
'iam:DeletePolicy',
'iam:CreatePolicyVersion',
'iam:DeletePolicyVersion',
'iam:SetDefaultPolicyVersion',
'organizations:DeleteOrganization',
'organizations:DeleteOrganizationalUnit',
'organizations:DeleteAccount',
'organizations:LeaveOrganization',
'account:CloseAccount',
'billing:*',
'aws-portal:*'
],
resources=['*'],
),
]
)
# 2. Rol que usa el boundary
admin_role = iam.Role(
self, 'SecurityAdminRole',
role_name='SecurityAdminRole',
assumed_by=iam.AccountRootPrincipal(), # o iam.ServicePrincipal('...') para Identity Center
permissions_boundary=admin_boundary,
managed_policies=[
iam.ManagedPolicy.from_aws_managed_policy_name('job-function/SecurityAudit')
]
)
# 3. Output
cdk.CfnOutput(
self, 'BoundaryArn',
value=admin_boundary.managed_policy_arn,
description='ARN del Permission Boundary para auditar'
)
Buenas prácticas
- Nunca uses
AdministratorAccessdirectamente en roles de humanos; siempre aplícale un boundary. - Revisa periódicamente los boundaries con
aws iam list-attached-user-policiesylist-attached-role-policies. - Usa AWS Organizations y SCPs además de los boundaries para defensa en profundidad.
- En entornos con AWS Identity Center (SSO), asigna el boundary al permiso de permiso set (permission set) en lugar de a usuarios individuales.
Conclusión
Los Permission Boundaries son una capa de protección esencial que te permite otorgar permisos amplios para tareas administrativas sin exponer tu cuenta a riesgos innecesarios. Al combinarlos con AWS CDK, puedes definir, versionar y desplegar estos límites como código, asegurando consistencia y reproducibilidad en todos tus entornos y cuentas.
Implementar esta práctica es un paso significativo hacia una postura de seguridad sólida en AWS, y encaja perfectamente dentro de los pilares de identidad y gestión de acceso que valoran los evaluadores del programa AWS Community Builder (Security).