Lab práctico · Semana 1: Fundamentos de AWS e IAM a fondo
Políticas, roles y lógica de evaluación de IAM en la práctica
Qué vas a construir
Un pequeño «laboratorio de permisos» para ver con tus propios ojos cada regla de la lógica de evaluación:
- Una política administrada por el cliente validada con IAM Access Analyzer.
- Un rol
Lab02Auditorque asumes con AWS STS desde CloudShell (acceso basado en roles y credenciales temporales). - Una política de sesión que recorta una sesión concreta.
- Un rol
Lab02DesarrolloconAdministratorAccesslimitado por un permissions boundary. - Un deny explícito que gana a un Allow.
- El IAM policy simulator desde la CLI y un analizador de acceso externo.
flowchart LR
CS["CloudShell<br/>(sesión AWSReservedSSO)"] -->|"sts:AssumeRole"| AU["Lab02Auditor<br/>ReadOnlyAccess + Deny inline"]
CS -->|"sts:AssumeRole + política de sesión"| AU2["Sesión recortada<br/>de Lab02Auditor"]
CS -->|"sts:AssumeRole"| DE["Lab02Desarrollo<br/>AdministratorAccess<br/>+ boundary Lab02Limite"]
AA["IAM Access Analyzer<br/>(zona de confianza: cuenta)"] -.->|"hallazgo"| EX["Lab02Externo<br/>(confía en otra cuenta)"]
Antes de empezar
- Haber terminado el lab 01: entras con tu usuario de IAM Identity Center (permission set
AdministratorAccess), nunca con el root. - Región
eu-south-2seleccionada en la consola. Abre CloudShell. - Todo lo que crees lleva el prefijo
Lab02para que la limpieza sea fácil.
Prepara variables (si CloudShell se reinicia, vuelve a ejecutarlas):
export AWS_REGION=eu-south-2
CUENTA=$(aws sts get-caller-identity --query Account --output text)
echo "Cuenta: $CUENTA"
mkdir -p ~/lab02 && cd ~/lab02
Paso 1: Escribe y valida una política de identidad
Una política que solo permite leer EC2 y solo en eu-south-2:
cat > lectura-ec2.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LeerEC2SoloEnEspana",
"Effect": "Allow",
"Action": "ec2:Describe*",
"Resource": "*",
"Condition": {
"StringEquals": { "aws:RequestedRegion": "eu-south-2" }
}
}
]
}
EOF
aws accessanalyzer validate-policy \
--policy-type IDENTITY_POLICY \
--policy-document file://lectura-ec2.json
La salida debe tener "findings": []. Ahora comete un error a propósito (una acción que no existe) y observa el hallazgo:
sed 's/ec2:Describe\*/ec2:DescribeTodo/' lectura-ec2.json > mala.json
aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY \
--policy-document file://mala.json \
--query "findings[].[findingType,issueCode]" --output table
Verás un hallazgo de tipo ERROR o WARNING sobre una acción inválida. Así se detectan erratas antes de desplegar. Crea ahora la política buena como customer managed policy:
aws iam create-policy --policy-name Lab02LecturaEC2 \
--policy-document file://lectura-ec2.json \
--query "Policy.Arn" --output text
Paso 2: Crea un rol que puedas asumir
Un rol tiene dos partes: la trust policy (quién puede asumirlo) y las políticas de permisos (qué puede hacer). Confiar en arn:aws:iam::<cuenta>:root significa «delego en mi cuenta»: podrá asumirlo cualquier principal de la cuenta cuyas políticas le permitan sts:AssumeRole, como tu sesión de administrador.
cat > confianza.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${CUENTA}:root" },
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role --role-name Lab02Auditor \
--assume-role-policy-document file://confianza.json \
--max-session-duration 3600
aws iam attach-role-policy --role-name Lab02Auditor \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
Fíjate en que el heredoc no lleva comillas (<<EOF) para que la shell sustituya ${CUENTA}; en el paso 1 sí las llevaba (<<'EOF') porque no había nada que sustituir.
Paso 3: Asume el rol con AWS STS
read -r AK SK ST <<< "$(aws sts assume-role \
--role-arn arn:aws:iam::${CUENTA}:role/Lab02Auditor \
--role-session-name lab02-auditor \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)"
# Función que ejecuta la CLI con las credenciales temporales del rol
como_auditor() { AWS_ACCESS_KEY_ID=$AK AWS_SECRET_ACCESS_KEY=$SK AWS_SESSION_TOKEN=$ST aws "$@"; }
como_auditor sts get-caller-identity
como_auditor ec2 describe-vpcs --query "Vpcs[].VpcId"
como_auditor iam create-user --user-name Lab02Intruso
Resultado esperado:
get-caller-identitymuestraassumed-role/Lab02Auditor/lab02-auditor: eres una sesión de rol.describe-vpcsfunciona (ReadOnlyAccess lo permite).create-userfalla conAccessDenied: nada concedeiam:CreateUseral rol (deny implícito).
Paso 4: Recorta una sesión con una política de sesión
Las políticas de sesión no conceden: el resultado es la intersección entre los permisos del rol y la política que pasas al asumirlo.
cat > sesion.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "ec2:DescribeRegions", "Resource": "*" }
]
}
EOF
read -r AK2 SK2 ST2 <<< "$(aws sts assume-role \
--role-arn arn:aws:iam::${CUENTA}:role/Lab02Auditor \
--role-session-name lab02-recortada \
--policy file://sesion.json \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)"
como_recortada() { AWS_ACCESS_KEY_ID=$AK2 AWS_SECRET_ACCESS_KEY=$SK2 AWS_SESSION_TOKEN=$ST2 aws "$@"; }
como_recortada ec2 describe-regions --query "Regions[0].RegionName"
como_recortada ec2 describe-vpcs
La primera funciona; la segunda falla con UnauthorizedOperation (así llama EC2 al acceso denegado) aunque el rol tenga ReadOnlyAccess: la política de sesión no incluye ec2:DescribeVpcs.
Paso 5: Un administrador con límite (permissions boundary)
Caso real: quieres que un equipo cree sus propios roles sin que pueda escalar privilegios. Le pones un boundary que fija el techo.
cat > limite.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TechoDePermisos",
"Effect": "Allow",
"Action": ["ec2:Describe*", "s3:ListAllMyBuckets"],
"Resource": "*"
}
]
}
EOF
aws iam create-policy --policy-name Lab02Limite --policy-document file://limite.json
aws iam create-role --role-name Lab02Desarrollo \
--assume-role-policy-document file://confianza.json \
--max-session-duration 3600 \
--permissions-boundary arn:aws:iam::${CUENTA}:policy/Lab02Limite
aws iam attach-role-policy --role-name Lab02Desarrollo \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
read -r AK3 SK3 ST3 <<< "$(aws sts assume-role \
--role-arn arn:aws:iam::${CUENTA}:role/Lab02Desarrollo \
--role-session-name lab02-dev \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)"
como_dev() { AWS_ACCESS_KEY_ID=$AK3 AWS_SECRET_ACCESS_KEY=$SK3 AWS_SESSION_TOKEN=$ST3 aws "$@"; }
como_dev ec2 describe-vpcs --query "Vpcs[].VpcId"
como_dev s3api list-buckets --query "Buckets[].Name"
como_dev iam list-roles --max-items 1
Las dos primeras funcionan. iam list-roles falla aunque el rol tenga AdministratorAccess: el permiso efectivo es la intersección entre la política de identidad y el boundary.
Paso 6: El deny explícito siempre gana
Añade al auditor una política inline que deniega ec2:DescribeVpcs:
cat > denegar.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Deny", "Action": "ec2:DescribeVpcs", "Resource": "*" }
]
}
EOF
aws iam put-role-policy --role-name Lab02Auditor \
--policy-name Lab02DenegarVPC --policy-document file://denegar.json
sleep 15 # IAM es eventualmente consistente
como_auditor ec2 describe-vpcs
como_auditor ec2 describe-subnets --query "Subnets[0].SubnetId"
describe-vpcs ahora falla aunque ReadOnlyAccess lo permita, y afecta también a la sesión que ya tenías abierta: las políticas de identidad se evalúan en cada petición. describe-subnets sigue funcionando.
Paso 7: Pregunta al simulador de políticas
El simulador evalúa políticas de identidad y boundaries sin ejecutar nada:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::${CUENTA}:role/Lab02Desarrollo \
--action-names ec2:DescribeVpcs iam:ListRoles s3:ListAllMyBuckets ec2:RunInstances \
--query "EvaluationResults[].[EvalActionName,EvalDecision]" --output table
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::${CUENTA}:role/Lab02Auditor \
--action-names ec2:DescribeVpcs ec2:DescribeSubnets iam:CreateUser \
--query "EvaluationResults[].[EvalActionName,EvalDecision]" --output table
Interpreta: allowed, implicitDeny (nada lo permite, o el boundary no lo incluye) y explicitDeny (hay un Deny). Compara con lo que viste en los pasos 5 y 6.
Paso 8: Detecta accesos externos con IAM Access Analyzer
Crea un analizador con tu cuenta como zona de confianza (el analizador de acceso externo no tiene coste) y un rol que confía en otra cuenta, sin permisos asociados:
aws accessanalyzer create-analyzer --analyzer-name lab02-cuenta --type ACCOUNT
cat > confianza-externa.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "lab02-demo" } }
}
]
}
EOF
aws iam create-role --role-name Lab02Externo \
--assume-role-policy-document file://confianza-externa.json
111122223333 es el ID de ejemplo que usa la documentación de AWS. IAM comprueba que el principal de una política de confianza existe al crear el rol: si responde MalformedPolicyDocument: Invalid principal in policy, cambia 111122223333 por el ID de otra cuenta tuya o de un compañero. El rol no tiene políticas de permisos, así que aunque alguien lo asumiera no podría hacer nada.
Access Analyzer revisa las políticas nuevas en unos 30 minutos; puedes forzar el análisis de ese rol:
ANALIZADOR=$(aws accessanalyzer get-analyzer --analyzer-name lab02-cuenta \
--query "analyzer.arn" --output text)
aws accessanalyzer start-resource-scan --analyzer-arn "$ANALIZADOR" \
--resource-arn arn:aws:iam::${CUENTA}:role/Lab02Externo
sleep 60
aws accessanalyzer list-findings-v2 --analyzer-arn "$ANALIZADOR" \
--query "findings[].[resourceType,resource,status]" --output table
Deberías ver un hallazgo ACTIVE sobre Lab02Externo: un recurso de tu cuenta al que puede acceder un principal de fuera de tu zona de confianza. Revisa también la consola (IAM → Access analyzer). En una empresa, cada hallazgo se archiva si es intencionado o se corrige si no lo es.
Comprueba que funciona
-
como_auditor sts get-caller-identitymostraba una sesiónassumed-role/Lab02Auditor/.... - La sesión con política de sesión no podía hacer
describe-vpcs. -
Lab02Desarrollo, con AdministratorAccess, no podía listar roles por culpa del boundary. - El Deny inline bloqueó
describe-vpcsal auditor. - El simulador devolvió
implicitDenyyexplicitDenydonde esperabas. - Access Analyzer generó un hallazgo para
Lab02Externo.
Limpieza
IAM no cobra, pero no dejes roles asumibles olvidados. Orden: primero desasocia y borra políticas del rol, luego el rol, luego las políticas administradas y por último el analizador.
# Lab02Auditor
aws iam delete-role-policy --role-name Lab02Auditor --policy-name Lab02DenegarVPC
aws iam detach-role-policy --role-name Lab02Auditor \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-role --role-name Lab02Auditor
# Lab02Desarrollo
aws iam detach-role-policy --role-name Lab02Desarrollo \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam delete-role --role-name Lab02Desarrollo
# Lab02Externo
aws iam delete-role --role-name Lab02Externo
# Políticas administradas por el cliente
aws iam delete-policy --policy-arn arn:aws:iam::${CUENTA}:policy/Lab02Limite
aws iam delete-policy --policy-arn arn:aws:iam::${CUENTA}:policy/Lab02LecturaEC2
# Analizador y ficheros
aws accessanalyzer delete-analyzer --analyzer-name lab02-cuenta
cd ~ && rm -rf ~/lab02
unset AK SK ST AK2 SK2 ST2 AK3 SK3 ST3
# Comprobación: no debe quedar nada que empiece por Lab02
aws iam list-roles --query "Roles[?starts_with(RoleName,'Lab02')].RoleName"
aws iam list-policies --scope Local --query "Policies[?starts_with(PolicyName,'Lab02')].PolicyName"
Las credenciales temporales que obtuviste caducan solas (como mucho en 1 hora) y, al borrar los roles, dejan de valer.
Preguntas para pensar como arquitecto
1. En el paso 5, ¿cómo evitarías que el equipo de desarrollo cree un rol nuevo SIN boundary y se lo asigne a sí mismo?
Dándoles iam:CreateRole y iam:PutRolePermissionsBoundary solo con la condición iam:PermissionsBoundary igual al ARN del boundary aprobado, denegando que borren o cambien ese boundary y limitando iam:PassRole a roles con un prefijo concreto. Es el patrón de «delegación segura» de la documentación de permissions boundaries.
2. ¿Por qué el Deny del paso 6 afectó a una sesión que ya estaba abierta, pero una política de sesión no se puede cambiar una vez emitidas las credenciales?
Las políticas de identidad del rol se evalúan en cada petición, así que un cambio se aplica (tras la propagación) a todas las sesiones. La política de sesión viaja «dentro» de las credenciales temporales: queda fija para esa sesión hasta que caduque. Por eso, para cortar sesiones ya emitidas, IAM ofrece revocar sesiones activas de un rol (añade un Deny condicionado a la hora de emisión del token).
3. Un proveedor externo de monitorización necesita leer métricas de CloudWatch de tu cuenta. ¿Qué le das?
Un rol en tu cuenta con una trust policy que confía en la cuenta del proveedor con condición sts:ExternalId (única para ti) y una política de permisos mínima (por ejemplo, lectura de CloudWatch). Nunca claves de un usuario IAM. Access Analyzer mostrará ese rol como acceso externo: archiva el hallazgo porque es intencionado.
4. Si en el paso 2 hubieras puesto como Principal el ARN de tu usuario de Identity Center en vez de la cuenta, ¿qué problema tendrías?
Tu identidad en la consola es una sesión de un rol AWSReservedSSO_… que Identity Center puede recrear; lo razonable es confiar en el rol (su ARN) o en la cuenta y filtrar con condiciones (aws:PrincipalArn, etiquetas). Confiar en la cuenta y controlar sts:AssumeRole con políticas de identidad es el patrón más flexible dentro de una misma cuenta.