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

⏱ 90-120 minDificultad: mediaTask statements: 1.1

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 Lab02Auditor que 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 Lab02Desarrollo con AdministratorAccess limitado 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-2 seleccionada en la consola. Abre CloudShell.
  • Todo lo que crees lleva el prefijo Lab02 para 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-identity muestra assumed-role/Lab02Auditor/lab02-auditor: eres una sesión de rol.
  • describe-vpcs funciona (ReadOnlyAccess lo permite).
  • create-user falla con AccessDenied: nada concede iam:CreateUser al 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-identity mostraba una sesión assumed-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-vpcs al auditor.
  • El simulador devolvió implicitDeny y explicitDeny donde 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.


Volver al módulo