Al cerrar el módulo 3 dejamos una lista incómoda encima de la mesa. MercadoFresco está en internet,
sirve desde el borde, escala solo y sobrevive a la caída de una zona de disponibilidad; pero el
usuario mercadofresco-admin puede hacer literalmente cualquier cosa en la cuenta 111122223333,
los roles rol-mercadofresco-tienda y rol-lambda-miniaturas se crearon sobre la marcha para que
las cosas funcionaran, y nadie ha escrito todavía una sola política pensando en qué es lo mínimo que
cada pieza necesita.
Eso no es un detalle pendiente. Es la capa de la que depende todo lo demás: si las identidades están mal, el cifrado no sirve de nada, porque quien puede llamar a la API puede leer los datos ya descifrados. Por eso IAM es la primera lección del módulo y la más larga.
AWS Identity and Access Management (IAM) es el servicio que responde, en cada una de los miles de millones de llamadas que recibe la API de AWS cada segundo, a dos preguntas: ¿quién eres? y ¿te dejo hacer esto?. En esta lección Marta convierte esas dos preguntas en políticas JSON concretas para MercadoFresco.
Advertencia. Los ejemplos de esta lección son didácticos y están simplificados para que se entiendan. Cualquier configuración de identidades, permisos o cumplimiento que vaya a aplicarse sobre datos reales de clientes —y más si entra en el ámbito del RGPD o de PCI DSS— debe ser revisada por un profesional de seguridad o de cumplimiento antes de llegar a producción. Todos los identificadores, cuentas y datos de este curso son ficticios.
Contenido
- Autenticación y autorización: dos preguntas distintas
- El vocabulario de IAM
- Anatomía de un ARN
- Usuarios, grupos y el problema de las claves de acceso
- Roles: identidades que nadie posee
sts:AssumeRoley la relación de confianza- Los seis tipos de política
- Anatomía de una política JSON
- Comodines, variables de política y condiciones
- La lógica de evaluación de políticas
- Formalizar
rol-mercadofresco-tienda - Formalizar
rol-lambda-miniaturas - Perfiles de instancia
- Mínimo privilegio en la práctica: leer los
AccessDenied - IAM Access Analyzer y la generación de políticas
- Las personas de MercadoFresco: usuarios y grupos
- MFA obligatorio por condición
- Rotación de claves e informe de credenciales
- IAM Identity Center y federación
- Herramientas de diagnóstico
- Coste y limpieza
Autenticación y autorización: dos preguntas distintas
Se confunden constantemente y son mecanismos separados:
| Autenticación | Autorización | |
|---|---|---|
| Pregunta | ¿Quién eres? | ¿Puedes hacer esto? |
| Mecanismo | Firma criptográfica de la petición (SigV4), contraseña + MFA en la consola | Evaluación de políticas |
| Resultado | Un principal identificado | Allow o Deny |
| Dónde falla | InvalidClientTokenId, SignatureDoesNotMatch |
AccessDenied |
Cuando Luis ejecuta aws s3 ls con el perfil mercadofresco-dev, el CLI firma la petición con la
clave secreta. AWS recalcula la firma; si coincide, sabe quién es Luis (autenticación). Solo
entonces busca todas las políticas aplicables y decide si le deja listar buckets (autorización).
Distinguirlas ahorra horas de depuración: si el error es AccessDenied, las credenciales son
correctas y el problema está en una política. Si el error es de firma, ni siquiera se ha llegado a
mirar los permisos.
El vocabulario de IAM
Estos siete términos aparecen en toda la documentación de AWS y conviene fijarlos ahora.
| Término | Qué es | Ejemplo en MercadoFresco |
|---|---|---|
| Principal | Quien hace la petición | Luis, rol-mercadofresco-tienda, el servicio s3.amazonaws.com |
| Identidad | Objeto de IAM al que se pueden adjuntar políticas | Usuario luis, grupo mercadofresco-desarrollo, rol rol-lambda-miniaturas |
| Entidad | Identidad que AWS puede autenticar | Usuario o rol (un grupo no es entidad: nadie inicia sesión como grupo) |
| Recurso | El objeto sobre el que se actúa | El bucket mercadofresco-catalogo-fotos, la instancia mercadofresco-tienda-01 |
| Acción | Operación de la API | s3:GetObject, ec2:TerminateInstances, kms:Decrypt |
| Política | Documento JSON que concede o niega | La que escribimos dentro de tres apartados |
| Sesión | Credenciales temporales resultado de asumir un rol | La sesión que la instancia obtiene del servicio de metadatos |
La distinción entre identidad y entidad parece pedantería y no lo es: explica por qué no
puedes «iniciar sesión como el grupo de analítica» ni por qué un grupo no puede aparecer como
Principal en una política de bucket. Los grupos son solo un contenedor de permisos para usuarios.
Anatomía de un ARN
El Amazon Resource Name es el identificador único y global de cualquier recurso de AWS. Aparece en todas las políticas, así que hay que saber leerlo carácter a carácter:
| Campo | Qué significa | Valores típicos |
|---|---|---|
arn |
Prefijo fijo | Siempre arn |
partition |
Partición de AWS | aws (global), aws-cn (China), aws-us-gov (GovCloud) |
service |
Espacio de nombres del servicio | s3, ec2, iam, kms, lambda |
region |
Región | eu-west-1; vacío en servicios globales (IAM, S3, Route 53) |
account-id |
Cuenta de 12 dígitos | 111122223333; vacío en S3 |
resource |
Tipo e identificador | Separados por /, : o nada, según el servicio |
Ejemplos reales de MercadoFresco, con la peculiaridad de cada uno:
arn:aws:s3:::mercadofresco-catalogo-fotos
arn:aws:s3:::mercadofresco-catalogo-fotos/productos/tomate-rama.jpg
arn:aws:iam::111122223333:user/luis
arn:aws:iam::111122223333:role/rol-mercadofresco-tienda
arn:aws:iam::aws:policy/ReadOnlyAccess
arn:aws:ec2:eu-west-1:111122223333:instance/i-0abc123def4567890
arn:aws:rds:eu-west-1:111122223333:db:mercadofresco-pedidos
arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos
arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-generar-miniaturas
arn:aws:sqs:eu-west-1:111122223333:mercadofresco-miniaturas-fallidas
arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/alb-mercadofresco-tienda/50dc6c495c0c9188Cuatro observaciones que evitan errores frecuentes:
- S3 no lleva región ni cuenta (
arn:aws:s3:::, tres dos puntos seguidos). Los nombres de bucket son globalmente únicos, así que no hacen falta. Escribirarn:aws:s3:eu-west-1:111122223333:mercadofresco-catalogo-fotoses un error clásico y la política simplemente no coincidirá nunca. - El bucket y los objetos son recursos distintos.
arn:aws:s3:::mercadofresco-catalogo-fotossirve paras3:ListBucket;arn:aws:s3:::mercadofresco-catalogo-fotos/*sirve paras3:GetObject. Confundirlos produce elAccessDeniedmás común del mundo. - IAM es global: región vacía. Y las políticas gestionadas por AWS usan
awsen el campo de cuenta:arn:aws:iam::aws:policy/ReadOnlyAccess. - RDS usa
:como separador (:db:mercadofresco-pedidos), no/. Cada servicio elige, no hay regla general; hay que mirar la documentación o copiar el ARN desde la consola.
Usuarios, grupos y el problema de las claves de acceso
Un usuario de IAM es una identidad permanente con credenciales de larga duración: contraseña
para la consola y, opcionalmente, un par de claves de acceso (AKIA... + clave secreta) para la API.
Y ahí está el problema. Una clave de acceso:
- No caduca nunca por sí sola.
- Es un secreto en texto plano que alguien tiene que guardar en algún sitio.
- Si se filtra a un repositorio de Git, a un ticket de soporte o al portapapeles equivocado, quien la tenga es ese usuario, sin más comprobaciones.
Los bots que rastrean GitHub encuentran claves de AWS filtradas en cuestión de minutos y arrancan instancias para minar criptomonedas. La factura llega antes que el aviso.
De ahí la regla que gobierna el resto de la lección:
Las claves de acceso son el último recurso, no el primero. Una EC2, una Lambda, un contenedor o un pipeline nunca deben llevar claves de acceso. Para eso existen los roles.
Un grupo es una colección de usuarios que comparten permisos. No tiene credenciales, no puede asumirse y no puede anidarse dentro de otro grupo. Su única función es que cuando entre alguien nuevo al equipo de Luis no haya que recordar qué siete políticas hay que adjuntarle.
Roles: identidades que nadie posee
Un rol de IAM es una identidad con políticas de permisos pero sin credenciales permanentes. Nadie «es» un rol: las entidades lo asumen temporalmente y reciben credenciales que caducan.
flowchart LR
A["Instancia<br/>mercadofresco-tienda-01"] -->|"1 pide credenciales<br/>al servicio de metadatos"| B["IMDSv2<br/>169.254.169.254"]
B -->|"2 sts:AssumeRole"| C["rol-mercadofresco-tienda"]
C -->|"3 credenciales temporales<br/>caducan en ~6 h"| A
A -->|"4 llamada firmada"| D["S3<br/>mercadofresco-catalogo-fotos"]
Lo importante del diagrama es el paso 3: las credenciales caducan y se renuevan solas antes de caducar. Si alguien las roba, tiene una ventana de minutos u horas, no de años. Y no hay nada que rotar, ni nada que guardar, ni nada que se pueda filtrar a Git porque no existe en ningún fichero.
Los roles se usan en cuatro escenarios:
| Escenario | Quién asume | Ejemplo |
|---|---|---|
| Rol de servicio | Un servicio de AWS | EC2, Lambda, ECS |
| Rol entre cuentas | Un usuario o rol de otra cuenta | La cuenta de producción y la de desarrollo (módulo 9) |
| Rol federado | Un usuario autenticado por un proveedor externo | Google Workspace, Entra ID, IAM Identity Center |
| Cambio de sombrero | Un usuario de la misma cuenta | Marta asume rol-mercadofresco-emergencia solo cuando lo necesita |
sts:AssumeRole y la relación de confianza
Todo rol tiene dos políticas, y confundirlas es el error conceptual más frecuente de IAM:
| Política | Nombre técnico | Responde a | Cuántas |
|---|---|---|---|
| De confianza | Trust policy / AssumeRolePolicyDocument |
¿Quién puede asumir este rol? | Exactamente 1 |
| De permisos | Políticas adjuntas | ¿Qué puede hacer quien lo asuma? | Hasta 10 gestionadas + inline |
La política de confianza es la única política basada en recurso donde el recurso es el propio rol.
Esta es la de rol-mercadofresco-tienda, que solo puede asumir el servicio EC2:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PermitirQueEC2AsumaElRol",
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}Línea a línea:
Principal.Service: el que asume no es una persona sino el servicio EC2. Cuando lances una instancia con este rol, EC2 llamará asts:AssumeRoleen tu nombre.Action: sts:AssumeRole: la acción especial que emite credenciales temporales. Existen variantes:sts:AssumeRoleWithWebIdentity(Cognito, OIDC) ysts:AssumeRoleWithSAML(federación empresarial).- No hay
Resource: el recurso es implícitamente el rol al que pertenece la política.
Para roles entre cuentas hay una condición imprescindible, el ExternalId, que evita el ataque
del confused deputy (un tercero que conoce el ARN de tu rol y consigue que alguien lo asuma en su
nombre):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::444455556666:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "mercadofresco-proveedor-logistica-2026" },
"Bool": { "aws:MultiFactorAuthPresent": "true" }
}
}
]
}"AWS": "arn:aws:iam::444455556666:root" no significa el usuario root de esa cuenta: significa
«la cuenta 444455556666 puede delegar en quien quiera». Es la forma habitual de escribirlo, y la
cuenta de destino debe además conceder sts:AssumeRole a sus propios usuarios. Los dos lados tienen
que estar de acuerdo: sin eso no hay acceso entre cuentas.
Los seis tipos de política
| Tipo | Se adjunta a | Efecto | Uso en MercadoFresco |
|---|---|---|---|
| Basada en identidad | Usuario, grupo, rol | Concede permisos al principal | La mayoría de las que escribiremos |
| Basada en recurso | Bucket, clave KMS, cola, secreto, rol | Concede acceso al recurso, indicando Principal |
Política de bucket, política de clave KMS |
| Límite de permisos | Usuario o rol | Techo máximo: no concede nada, solo limita | El techo de los roles que cree Luis |
| SCP | Cuenta u OU de Organizations | Techo de toda la cuenta | Se ve en detalle en 09-04 |
| Política de sesión | Se pasa al asumir un rol | Reduce los permisos de esa sesión concreta | Sesiones acotadas del pipeline (módulo 8) |
| ACL | Bucket S3 (heredado) | Mecanismo antiguo, anterior a IAM | No usar; desactivadas por defecto desde 2023 |
Las dos primeras son las que se usan a diario, y hay una diferencia decisiva entre ellas:
- Una política de identidad dice: «Luis puede leer ese bucket». Vive con Luis.
- Una política de recurso dice: «ese bucket deja que lo lea Luis». Vive con el bucket, y además es la única forma de conceder acceso desde otra cuenta sin roles.
Y una consecuencia práctica: cuando el principal y el recurso están en la misma cuenta, basta con que una de las dos conceda el permiso. Cuando están en cuentas distintas, hacen falta las dos.
Las políticas gestionadas por AWS (AmazonS3ReadOnlyAccess, AdministratorAccess...) son cómodas
para empezar y casi siempre demasiado amplias. AmazonS3ReadOnlyAccess concede lectura sobre
todos los buckets de la cuenta, incluido mercadofresco-copias-basedatos. Sirven para prototipar;
no para producción.
Anatomía de una política JSON
Todas las políticas tienen la misma estructura. Esta es la plantilla completa, con todos los elementos posibles:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IdentificadorLegibleOpcional",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda" },
"Action": ["s3:GetObject", "s3:PutObject"],
"NotAction": [],
"Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos/productos/*",
"NotResource": [],
"Condition": {
"StringEquals": { "aws:PrincipalTag/Proyecto": "mercadofresco" }
}
}
]
}| Elemento | Obligatorio | Qué hace |
|---|---|---|
Version |
Sí | Siempre 2012-10-17. No es la fecha de tu política: es la versión del lenguaje. Sin ella no funcionan las variables de política |
Statement |
Sí | Lista de declaraciones. Se evalúan todas, no se para en la primera |
Sid |
No | Identificador legible. Útil para depurar y obligatorio único en políticas de recurso |
Effect |
Sí | Allow o Deny |
Principal |
Solo en políticas de recurso | Quién. En políticas de identidad está prohibido (el principal es el dueño) |
Action |
Sí | servicio:Operación. Admite comodines |
Resource |
Sí (salvo en confianza) | Sobre qué ARN |
Condition |
No | Cuándo se aplica |
NotAction y NotResource significan «todo excepto». Son peligrosos: "NotAction": "s3:*" con
"Effect": "Allow" concede todo lo demás en AWS. Úsalos casi exclusivamente con Deny.
Comodines, variables de política y condiciones
Comodines. * sustituye cualquier secuencia de caracteres, ? un solo carácter:
| Patrón | Coincide con |
|---|---|
s3:Get* |
s3:GetObject, s3:GetBucketPolicy, s3:GetObjectAcl... |
s3:* |
Todas las acciones de S3 |
* |
Todas las acciones de AWS (esto es AdministratorAccess) |
arn:aws:s3:::mercadofresco-* |
Todos los buckets de MercadoFresco |
arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/* |
Solo los objetos bajo ese prefijo |
Cuidado con s3:Get*: incluye s3:GetBucketPolicy, que permite leer la política del bucket, y
s3:GetObjectVersion, que permite leer versiones antiguas incluso de objetos «borrados».
Variables de política. Se sustituyen en el momento de la evaluación:
| Variable | Valor |
|---|---|
${aws:username} |
Nombre del usuario de IAM |
${aws:userid} |
Identificador único del principal |
${aws:PrincipalTag/Clave} |
Etiqueta del principal |
${s3:prefix} |
Prefijo solicitado en una operación de S3 |
Con esto, una sola política da a cada persona su carpeta privada en el bucket de desarrollo:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CadaUnoEnSuCarpeta",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::mercadofresco-tienda-web-desarrollo/personal/${aws:username}/*"
}
]
}Adjuntada al grupo mercadofresco-desarrollo, Luis solo puede tocar personal/luis/, y quien entre
mañana solo personal/<su-nombre>/, sin escribir ni una política más.
Condiciones útiles. El bloque Condition tiene la forma
{ "Operador": { "ClaveDeCondición": "valor" } }:
| Clave | Operador típico | Para qué |
|---|---|---|
aws:SecureTransport |
Bool |
Exigir HTTPS: "Bool": {"aws:SecureTransport": "false"} con Deny |
aws:SourceIp |
IpAddress |
Restringir a la IP de la oficina 192.168.10.0/24 (no funciona si la petición pasa por un endpoint de VPC) |
aws:RequestedRegion |
StringEquals |
Confinar la actividad a eu-west-1 |
aws:PrincipalTag/Clave |
StringEquals |
Control de acceso por atributos (ABAC) |
aws:MultiFactorAuthPresent |
Bool |
Exigir MFA |
aws:CurrentTime |
DateGreaterThan |
Accesos temporales con caducidad |
s3:x-amz-server-side-encryption |
StringEquals |
Obligar a que todo objeto se suba cifrado |
kms:ViaService |
StringEquals |
Que la clave solo se use a través de un servicio concreto (04-02) |
Un aviso importante sobre aws:SourceIp: si la petición viaja por un endpoint de VPC —como
vpce-mercadofresco-s3, que montamos en 03-01—, la IP de origen es privada y la condición falla.
Para ese caso la clave correcta es aws:SourceVpce.
Otro aviso: aws:MultiFactorAuthPresent es falso, no ausente, en las sesiones de rol de un
servicio. Si aplicas esa condición a un rol de EC2, lo rompes.
La lógica de evaluación de políticas
Este es el corazón de IAM y merece memorizarse. AWS reúne todas las políticas aplicables y aplica tres reglas por este orden:
- Todo está denegado por defecto (denegación implícita).
- Un
Allowexplícito en cualquier política aplicable lo permite. - Un
Denyexplícito en cualquier política gana siempre, sin excepción.
Dicho de otra forma: denegación explícita > permiso explícito > denegación implícita.
flowchart TD
A["Petición autenticada"] --> B{"¿Hay un Deny explícito<br/>en ALGUNA política?"}
B -->|"Sí"| Z["DENEGADA"]
B -->|"No"| C{"¿Lo permite la SCP<br/>de Organizations?"}
C -->|"No"| Z
C -->|"Sí o no aplica"| D{"¿Lo permite la política<br/>de recurso?"}
D -->|"Sí, y con Principal explícito"| Y["PERMITIDA"]
D -->|"No concede"| E{"¿Está dentro del<br/>límite de permisos?"}
E -->|"No"| Z
E -->|"Sí o no aplica"| F{"¿Lo permite la política<br/>de sesión?"}
F -->|"No"| Z
F -->|"Sí o no aplica"| G{"¿Lo permite alguna política<br/>basada en identidad?"}
G -->|"Sí"| Y
G -->|"No"| Z
Consecuencias prácticas que hay que interiorizar:
- No existe la «prioridad» entre políticas. No hay números de regla como en las NACL de 03-02. Un
solo
Denyperdido en una política olvidada bloquea todo, y encontrarlo es trabajo de detective. - El orden de las declaraciones dentro de una política es irrelevante. Se evalúan todas.
- Ni el usuario root escapa a un
Denyde una SCP. Es exactamente por eso que las SCP son la herramienta de gobierno de 09-04. - Límite de permisos y política de identidad se cruzan: el permiso efectivo es la intersección de ambos. Si el límite permite solo S3 y la política concede S3 y EC2, el resultado es solo S3.
Y la asimetría entre misma cuenta y cuentas distintas, resumida:
| Situación | Qué hace falta |
|---|---|
| Principal y recurso en la misma cuenta | Basta con que una de las dos políticas conceda (identidad o recurso) |
| Cuentas distintas | Hacen falta las dos: la de identidad en la cuenta origen y la de recurso en la de destino |
| Excepciones (KMS, IAM) | La política de recurso siempre debe conceder, aunque sea la misma cuenta |
Esa última fila es la fuente número uno de sorpresas en 04-02: si la política de la clave KMS no te nombra, no puedes usarla aunque seas administrador de la cuenta.
Formalizar rol-mercadofresco-tienda
Hasta ahora la instancia mercadofresco-tienda-01 funcionaba con permisos genéricos. Vamos a
escribir la política que necesita exactamente, ni un permiso más. La tienda tiene que:
- Leer las fotos de
mercadofresco-catalogo-fotos/productos/. - Escribir las miniaturas generadas en
miniaturas/. - Publicar métricas de negocio (pedidos por hora) en CloudWatch.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LeerFotosDeProducto",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::mercadofresco-catalogo-fotos/productos/*",
"arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/*"
]
},
{
"Sid": "ListarSoloLosPrefijosNecesarios",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos",
"Condition": {
"StringLike": {
"s3:prefix": ["productos/*", "miniaturas/*"]
}
}
},
{
"Sid": "EscribirMiniaturasCifradas",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/*",
"Condition": {
"StringEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "PublicarMetricasDeNegocio",
"Effect": "Allow",
"Action": "cloudwatch:PutMetricData",
"Resource": "*",
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "MercadoFresco/Tienda"
}
}
},
{
"Sid": "ProhibirTraficoSinCifrar",
"Effect": "Deny",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::mercadofresco-catalogo-fotos",
"arn:aws:s3:::mercadofresco-catalogo-fotos/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}Declaración por declaración:
LeerFotosDeProducto: solos3:GetObject, y solo bajo dos prefijos. La tienda no puede leer nada fuera deproductos/yminiaturas/, ni siquiera dentro del mismo bucket. Tampoco puede borrar ni leer versiones antiguas.ListarSoloLosPrefijosNecesarios:s3:ListBucketactúa sobre el bucket, no sobre los objetos, por eso el ARN va sin/*. La condicións3:prefiximpide que alguien liste el bucket entero y descubra qué otros prefijos existen. Es una fuga de información sutil pero real.EscribirMiniaturasCifradas:s3:PutObjectlimitado aminiaturas/, y exigiendo que la subida incluya la cabecera de cifrado con KMS. Si el código olvida cifrar, la subida se rechaza. Esto conecta directamente conalias/mercadofresco-datosen 04-02.PublicarMetricasDeNegocio:cloudwatch:PutMetricDatano admite ARN de recurso, hay que poner"Resource": "*". Pero se acota concloudwatch:namespace, así que la tienda solo puede escribir en su propio espacio de nombres y no puede falsear las métricas de otro componente.ProhibirTraficoSinCifrar: unDenyde refuerzo. Recuerda la regla: unDenygana siempre, incluso frente a losAllowde arriba. Si por lo que sea se hiciera una llamada por HTTP sin TLS, se rechaza.
Fíjate en lo que no hay: ni s3:DeleteObject, ni s3:*, ni acceso a
mercadofresco-copias-basedatos, ni a mercadofresco-informes-analitica, ni ec2:*. Si mañana
alguien consigue ejecutar código en la instancia, esto es todo lo que se lleva.
Creación por CLI:
# 1. Política de confianza (quién puede asumir el rol)
cat > /tmp/confianza-ec2.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
JSON
aws iam create-role \
--role-name rol-mercadofresco-tienda \
--assume-role-policy-document file:///tmp/confianza-ec2.json \
--description "Rol de las instancias de la tienda de MercadoFresco" \
--max-session-duration 3600 \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=tienda Key=Propietario,Value=marta \
Key=CentroCoste,Value=tecnologia \
--profile mercadofresco-dev
# 2. Política de permisos (qué puede hacer)
aws iam create-policy \
--policy-name pol-mercadofresco-tienda \
--policy-document file:///tmp/pol-tienda.json \
--description "Permisos minimos de la tienda: leer catalogo, escribir miniaturas, metricas" \
--profile mercadofresco-dev
# 3. Unir las dos cosas
aws iam attach-role-policy \
--role-name rol-mercadofresco-tienda \
--policy-arn arn:aws:iam::111122223333:policy/pol-mercadofresco-tienda \
--profile mercadofresco-dev--max-session-duration 3600 limita las sesiones a una hora. Para roles de EC2 el servicio de
metadatos renueva automáticamente, así que reducirlo no rompe nada y acorta la ventana de un robo de
credenciales.
Formalizar rol-lambda-miniaturas
La función mercadofresco-generar-miniaturas de 02-05 se dispara cuando aparece un objeto en
productos/, genera la miniatura y la escribe en miniaturas/. Sus necesidades son parecidas pero
no idénticas, y además necesita escribir sus registros y enviar los fallos a la cola.
Política de confianza —cambia solo el Principal:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}Política de permisos:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EscribirRegistrosDeLaPropiaFuncion",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:/aws/lambda/mercadofresco-generar-miniaturas:*"
},
{
"Sid": "LeerElOriginal",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos/productos/*"
},
{
"Sid": "EscribirLaMiniatura",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/*"
},
{
"Sid": "UsarLaClaveDelBucket",
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"Condition": {
"StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
}
},
{
"Sid": "EnviarLosFallosALaCola",
"Effect": "Allow",
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:eu-west-1:111122223333:mercadofresco-miniaturas-fallidas"
}
]
}Cuatro detalles que importan:
logs:CreateLogGroupno está. Se crea el grupo una vez, a mano o con infraestructura como código, y la función solo necesita escribir. Es un permiso menos.- El ARN del grupo de registros acaba en
:*: es la sintaxis de CloudWatch Logs para incluir los flujos. Sin ese sufijo la función no escribe nada y el fallo es silencioso. kms:ViaServicelimita el uso de la clave a peticiones que lleguen a través de S3. Si alguien roba las credenciales de la función, no puede llamar akms:Decryptdirectamente para descifrar otra cosa. Es una de las condiciones más útiles de todo IAM y la desarrollamos en 04-02.- Se nombra la clave por su ARN de clave, no por el alias. Las políticas de identidad admiten alias en algunos contextos, pero el ARN es inequívoco y no cambia si alguien reasigna el alias.
Con el rol asignado, el código de la función no tiene ni una credencial. boto3.client("s3")
encuentra las credenciales temporales por sí solo a través de las variables de entorno que Lambda
inyecta. Esa es toda la magia.
Perfiles de instancia
Aquí hay una pieza que casi nadie entiende hasta que le falla: una instancia EC2 no puede recibir un rol directamente. Necesita un perfil de instancia (instance profile), que es un contenedor con exactamente un rol dentro.
flowchart LR
A["mercadofresco-tienda-01"] --> B["Perfil de instancia<br/>rol-mercadofresco-tienda"]
B --> C["Rol de IAM<br/>rol-mercadofresco-tienda"]
C --> D["Política<br/>pol-mercadofresco-tienda"]
Cuando creas el rol desde la consola eligiendo el caso de uso «EC2», AWS crea el perfil de instancia con el mismo nombre automáticamente y no te enteras. Cuando lo creas por CLI, no. Hay que hacerlo a mano:
aws iam create-instance-profile \
--instance-profile-name rol-mercadofresco-tienda \
--profile mercadofresco-dev
aws iam add-role-to-instance-profile \
--instance-profile-name rol-mercadofresco-tienda \
--role-name rol-mercadofresco-tienda \
--profile mercadofresco-dev
# Asociarlo a la plantilla de lanzamiento (nueva versión)
aws ec2 create-launch-template-version \
--launch-template-name lt-mercadofresco-tienda \
--source-version '$Latest' \
--launch-template-data '{"IamInstanceProfile":{"Name":"rol-mercadofresco-tienda"}}' \
--profile mercadofresco-devSi intentas lanzar una instancia con un rol que no tiene perfil, el error es
Value (rol-mercadofresco-tienda) for parameter iamInstanceProfile.name is invalid, que no dice en
absoluto lo que pasa. Ahora ya lo sabes.
Y una comprobación desde dentro de la instancia, usando IMDSv2 como vimos en 02-01:
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Devuelve: rol-mercadofresco-tienda
# Y con ese nombre se obtienen las credenciales temporales y su fecha de caducidad:
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/rol-mercadofresco-tiendaMínimo privilegio en la práctica: leer los AccessDenied
El principio de mínimo privilegio dice que cada identidad debe tener los permisos mínimos necesarios para su función, y nada más. Todo el mundo está de acuerdo y casi nadie lo aplica, porque adivinar de antemano qué permisos hace falta es imposible.
El método que sí funciona es el contrario:
flowchart TD
A["Empezar SIN permisos"] --> B["Ejecutar el caso de uso real"]
B --> C{"¿AccessDenied?"}
C -->|"Sí"| D["Leer el mensaje:<br/>acción + recurso exactos"]
D --> E["Añadir SOLO ese permiso"]
E --> B
C -->|"No"| F["Repetir con los casos<br/>menos frecuentes"]
F --> G["Política mínima real"]
La clave está en que los mensajes de error de AWS son extraordinariamente precisos:
An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:sts::111122223333:assumed-role/rol-mercadofresco-tienda/i-0abc123def4567890
is not authorized to perform: s3:PutObject
on resource: "arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/tomate-rama.jpg"
because no identity-based policy allows the s3:PutObject actionEse mensaje te da los cuatro datos que necesitas: el principal exacto, la acción exacta, el recurso exacto y por qué falló. Esa última frase es oro:
| Frase final del error | Qué significa |
|---|---|
because no identity-based policy allows |
Denegación implícita: falta un Allow. Añádelo |
with an explicit deny in an identity-based policy |
Hay un Deny en una política adjunta al principal |
with an explicit deny in a resource-based policy |
El Deny está en la política del bucket, cola o clave |
with an explicit deny in a service control policy |
Lo bloquea una SCP de Organizations (09-04) |
with an explicit deny in a permissions boundary |
El límite de permisos no lo incluye |
Fíjate también en el ARN del principal: arn:aws:sts::111122223333:assumed-role/rol-.../i-0abc....
Ese es el aspecto de una sesión de rol asumido: empieza por sts, no por iam, y el último
segmento es el nombre de sesión, que para EC2 es el identificador de la instancia. Verlo así
confirma de inmediato que la instancia está usando el rol y no unas claves olvidadas.
IAM Access Analyzer y la generación de políticas
Hay dos herramientas que hacen este trabajo por ti.
Generación de políticas a partir de la actividad. Access Analyzer lee el historial de CloudTrail (el servicio de auditoría que veremos a fondo en 05-03) de un rol durante un periodo y escribe la política que ese rol realmente ha usado. Es la forma más rápida de recortar permisos existentes:
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn":"arn:aws:iam::111122223333:role/rol-mercadofresco-tienda"}' \
--cloud-trail-details '{
"trails": [{"cloudTrailArn":"arn:aws:cloudtrail:eu-west-1:111122223333:trail/mercadofresco-auditoria",
"regions":["eu-west-1"],"allRegions":false}],
"accessRole":"arn:aws:iam::111122223333:role/rol-access-analyzer",
"startTime":"2026-07-01T00:00:00Z"
}' \
--profile mercadofresco-devAdvertencia importante: la política generada refleja lo que se usó en la ventana analizada. Si el proceso de cierre mensual solo se ejecuta el día 30 y analizas del 1 al 15, ese permiso no aparecerá y romperás el cierre. Analiza al menos un ciclo completo de negocio, y para MercadoFresco eso significa incluir un viernes de pico y un fin de mes.
Hallazgos de acceso externo. El analizador revisa políticas de recurso (buckets, colas, claves, roles) y avisa de todo aquello a lo que puede acceder alguien de fuera de tu cuenta u organización. Es gratuito y detecta en segundos el bucket que alguien abrió «solo para una prueba»:
aws accessanalyzer create-analyzer \
--analyzer-name mercadofresco-acceso-externo \
--type ACCOUNT \
--profile mercadofresco-dev
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:eu-west-1:111122223333:analyzer/mercadofresco-acceso-externo \
--query 'findings[?status==`ACTIVE`].[resource,principal,action]' \
--output table \
--profile mercadofresco-devExiste además el analizador de acceso no utilizado, que señala roles, usuarios y permisos que llevan meses sin usarse. Ese sí tiene coste (del orden de 0,20 USD por identidad analizada al mes) y es la herramienta natural para la limpieza semestral.
Las personas de MercadoFresco: usuarios y grupos
Hasta ahora solo existían mercadofresco-admin y el root. Marta crea la estructura humana:
| Grupo | Quién | Qué puede hacer |
|---|---|---|
mercadofresco-administracion |
Marta | Administración completa con MFA obligatorio |
mercadofresco-desarrollo |
Luis | EC2, Lambda, S3 de desarrollo, lectura de registros; nada en producción |
mercadofresco-analitica |
Sara | Solo lectura de mercadofresco-informes-analitica y consultas |
for g in mercadofresco-administracion mercadofresco-desarrollo mercadofresco-analitica; do
aws iam create-group --group-name "$g" --profile mercadofresco-dev
done
aws iam create-user --user-name marta \
--tags Key=Proyecto,Value=mercadofresco Key=Propietario,Value=marta \
--profile mercadofresco-dev
aws iam add-user-to-group --user-name marta \
--group-name mercadofresco-administracion --profile mercadofresco-devLa política de Sara es el mejor ejemplo de mínimo privilegio para un perfil no técnico:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListarSoloElBucketDeInformes",
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetBucketLocation"],
"Resource": "arn:aws:s3:::mercadofresco-informes-analitica"
},
{
"Sid": "DescargarInformes",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:GetObjectVersion"],
"Resource": "arn:aws:s3:::mercadofresco-informes-analitica/*"
},
{
"Sid": "SoloDesdeEuWest1",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["eu-west-1", "us-east-1"]
}
}
}
]
}- Sara puede listar y descargar, nunca subir ni borrar. Si su portátil se infecta, no puede destruir los informes.
- La tercera declaración confina toda su actividad a
eu-west-1. Se incluyeus-east-1porque los servicios globales (IAM, CloudFront, Route 53) firman sus peticiones contra esa región y sin ella Sara no podría ni cambiar su propia contraseña. - Es una política de identidad, así que no lleva
Principal.
MFA obligatorio por condición
Exigir MFA en la consola es una casilla; exigirlo para la API requiere una política. Esta es la que Marta adjunta a los tres grupos, y es el patrón canónico de AWS:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PermitirGestionarLasPropiasCredenciales",
"Effect": "Allow",
"Action": [
"iam:ChangePassword",
"iam:GetUser",
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:ListMFADevices",
"iam:ResyncMFADevice"
],
"Resource": [
"arn:aws:iam::111122223333:user/${aws:username}",
"arn:aws:iam::111122223333:mfa/${aws:username}"
]
},
{
"Sid": "DenegarTodoLoDemasSinMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken",
"iam:ChangePassword"
],
"Resource": "*",
"Condition": {
"BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
}
}
]
}Tres puntos delicados:
- El primer
Statementes imprescindible: sin él, un usuario nuevo sin MFA no podría registrar su MFA, y quedaría bloqueado para siempre. Es la paradoja del huevo y la gallina de esta política. NotActionaquí sí es correcto porque va conDeny: «deniega todo excepto lo necesario para configurar el MFA».BoolIfExistsen lugar deBool. Si la clave no existe en el contexto —cosa que ocurre en ciertas llamadas de servicio—,Boolharía que la condición no coincidiera y elDenyno se aplicaría.BoolIfExiststrata la ausencia comofalse, es decir, como «sin MFA», que es el comportamiento seguro.
Ojo: adjuntar esto a un rol de servicio lo rompe todo, porque las sesiones de EC2 o Lambda nunca llevan MFA. Es exclusivamente para grupos de humanos.
Rotación de claves e informe de credenciales
Si un usuario necesita claves de acceso (por ejemplo Luis para el perfil mercadofresco-dev en su
portátil), hay que rotarlas. IAM permite dos claves activas simultáneamente, y esa es
exactamente la razón de que existan: rotar sin interrupción.
# 1. Crear la segunda clave
aws iam create-access-key --user-name luis --profile mercadofresco-dev
# 2. Actualizar ~/.aws/credentials con la nueva, probar durante unos días
# 3. Desactivar la antigua (sin borrarla: se puede revertir en segundos)
aws iam update-access-key --user-name luis \
--access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive --profile mercadofresco-dev
# 4. Confirmar que nada se ha roto y borrarla definitivamente
aws iam delete-access-key --user-name luis \
--access-key-id AKIAIOSFODNN7EXAMPLE --profile mercadofresco-devEl informe de credenciales es un CSV con el estado de todos los usuarios de la cuenta y es la revisión trimestral de Marta:
aws iam generate-credential-report --profile mercadofresco-dev
aws iam get-credential-report --query 'Content' --output text \
--profile mercadofresco-dev | base64 --decode > /tmp/credenciales.csvLas columnas que hay que mirar: mfa_active (debe ser true en todos los humanos),
access_key_1_last_used_date (si es N/A, la clave nunca se ha usado: bórrala),
access_key_1_last_rotated (más de 90 días: rótala) y password_last_used (más de 90 días: el
usuario probablemente ya no trabaja aquí).
IAM Identity Center y federación
Todo lo anterior tiene un límite evidente: no escala a personas. Con tres empleados es manejable; con treinta, crear un usuario de IAM por persona y por cuenta es un desastre operativo, y cuando alguien se va hay que acordarse de borrarlo en todas partes.
La solución moderna es AWS IAM Identity Center (antes AWS SSO):
- Los usuarios viven en un único directorio: el propio de Identity Center, Microsoft Entra ID, Okta o Google Workspace.
- Se definen conjuntos de permisos (permission sets), que no son más que roles de IAM que Identity Center crea automáticamente en cada cuenta.
- La persona entra por un portal, elige cuenta y rol, y recibe credenciales temporales. No hay claves de acceso permanentes en ningún sitio.
- Cuando alguien deja la empresa se le desactiva en el directorio corporativo y pierde el acceso a todas las cuentas de AWS al instante.
- Es gratuito y se integra con el CLI v2:
aws configure ssoyaws sso login.
| Usuarios de IAM | IAM Identity Center | |
|---|---|---|
| Credenciales | Permanentes | Temporales |
| Alta/baja | Manual y por cuenta | Centralizada en el directorio |
| Multicuenta | Un usuario por cuenta | Un inicio de sesión, todas las cuentas |
| Coste | Gratis | Gratis |
| Cuándo | Casos residuales | Lo recomendado para personas |
MercadoFresco tiene hoy tres personas y una cuenta, así que los usuarios de IAM son razonables. En cuanto haya una segunda cuenta —lo que ocurrirá en 09-04 al separar desarrollo de producción con Organizations— la migración a Identity Center deja de ser opcional.
Herramientas de diagnóstico
Cuatro herramientas que Marta usa cuando algo no cuadra.
1. sts get-caller-identity. La primera pregunta siempre es «¿quién soy ahora mismo?»:
{
"UserId": "AIDAI23HXD2O5EXAMPLE",
"Account": "111122223333",
"Arn": "arn:aws:iam::111122223333:user/luis"
}Si el Arn no es el que esperabas, el problema no es de permisos sino de credenciales: revisa la
precedencia que vimos en 01-05 (variables de entorno antes que perfil, perfil antes que rol de
instancia).
2. Simulador de políticas. Evalúa una acción sin ejecutarla:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/rol-mercadofresco-tienda \
--action-names s3:DeleteObject s3:GetObject \
--resource-arns "arn:aws:s3:::mercadofresco-catalogo-fotos/productos/tomate-rama.jpg" \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' \
--output table \
--profile mercadofresco-dev-------------------------------------
| SimulatePrincipalPolicy |
+------------------+----------------+
| s3:DeleteObject | implicitDeny |
| s3:GetObject | allowed |
+------------------+----------------+Exactamente lo que queríamos: puede leer, no puede borrar. El simulador no evalúa políticas de recurso de otras cuentas ni todas las condiciones contextuales, así que es una ayuda, no una prueba.
3. --dry-run. Muchas operaciones de EC2 lo aceptan y comprueban permisos sin hacer nada:
aws ec2 terminate-instances --instance-ids i-0abc123def4567890 --dry-run \
--profile mercadofresco-dev
# UnauthorizedOperation -> no tiene permiso
# DryRunOperation -> sí lo tiene (y no ha terminado nada)4. get-account-authorization-details. El volcado completo de usuarios, grupos, roles y
políticas de la cuenta. Es la foto para auditar o versionar en Git:
aws iam get-account-authorization-details --profile mercadofresco-dev \
> /tmp/iam-mercadofresco-$(date +%F).json
# ¿Quién tiene permisos de administrador?
aws iam get-account-authorization-details --profile mercadofresco-dev \
--query 'Policies[?PolicyName==`AdministratorAccess`]'Y en boto3, un chequeo que Marta ejecuta en el repaso trimestral:
import boto3
from datetime import datetime, timezone
sesion = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
iam = sesion.client("iam")
ahora = datetime.now(timezone.utc)
for pagina in iam.get_paginator("list_users").paginate():
for usuario in pagina["Users"]:
nombre = usuario["UserName"]
# ¿Tiene MFA?
mfa = iam.list_mfa_devices(UserName=nombre)["MFADevices"]
sin_mfa = "SIN MFA" if not mfa else "ok"
# ¿Claves antiguas?
for clave in iam.list_access_keys(UserName=nombre)["AccessKeyMetadata"]:
dias = (ahora - clave["CreateDate"]).days
aviso = "ROTAR" if dias > 90 else "ok"
print(f"{nombre:20} {clave['AccessKeyId']} {dias:4} dias {aviso} {sin_mfa}")El script recorre todos los usuarios, comprueba si tienen MFA registrado y calcula la antigüedad de
cada clave de acceso. get_paginator es necesario porque list_users devuelve como máximo 100
usuarios por llamada; con paginador, boto3 se encarga de recorrer todas las páginas.
Coste y limpieza
IAM es gratuito. Usuarios, grupos, roles, políticas y llamadas a STS no cuestan nada. Tampoco cuesta IAM Identity Center ni el analizador de acceso externo de Access Analyzer. Lo único con precio es el analizador de acceso no utilizado, del orden de 0,20 USD por identidad analizada al mes: para las ~8 identidades de MercadoFresco, menos de 2 USD mensuales.
Que sea gratis no significa que no tenga coste: el coste de IAM es operativo. Cada política mal escrita es una brecha o una incidencia. Y hay cuotas que conviene conocer: 5.000 usuarios por cuenta, 1.000 roles, 10 políticas gestionadas por identidad, 6.144 caracteres por política gestionada y 2.048 por política inline de usuario.
Para deshacer lo de esta lección, el orden importa: no se puede borrar un rol con políticas adjuntas ni un perfil de instancia con un rol dentro.
aws iam remove-role-from-instance-profile \
--instance-profile-name rol-mercadofresco-tienda --role-name rol-mercadofresco-tienda \
--profile mercadofresco-dev
aws iam delete-instance-profile --instance-profile-name rol-mercadofresco-tienda \
--profile mercadofresco-dev
aws iam detach-role-policy --role-name rol-mercadofresco-tienda \
--policy-arn arn:aws:iam::111122223333:policy/pol-mercadofresco-tienda --profile mercadofresco-dev
aws iam delete-role --role-name rol-mercadofresco-tienda --profile mercadofresco-dev
aws iam delete-policy --policy-arn arn:aws:iam::111122223333:policy/pol-mercadofresco-tienda \
--profile mercadofresco-devPara borrar un usuario hay que quitar antes contraseña, claves, dispositivos MFA, pertenencias a grupos y políticas adjuntas. Es tedioso a propósito.
Errores Comunes y Consejos
Confundir el ARN del bucket con el de los objetos. arn:aws:s3:::mi-bucket sirve para
ListBucket; arn:aws:s3:::mi-bucket/* para GetObject. Casi todas las políticas de S3 necesitan
las dos declaraciones. Si tu aplicación puede descargar un objeto cuyo nombre conoce pero no
listar el bucket, te falta la primera.
Poner Principal en una política de identidad. Es un error de sintaxis: AWS lo rechaza. El
principal de una política de identidad es la identidad a la que se adjunta.
Usar Bool donde hace falta BoolIfExists. Con Bool, si la clave de condición no está
presente en el contexto de la petición, la condición no coincide y tu Deny no se aplica. Regla
práctica: en condiciones de tipo Deny que dependen de una clave que puede faltar, usa siempre la
variante IfExists.
Adjuntar AdministratorAccess «temporalmente» para desbloquear algo. Nunca es temporal. Si
necesitas desbloquear una entrega a las once de la noche, usa el simulador para saber qué permiso
falta y añade ese permiso concreto; tardas dos minutos más y no dejas una bomba en la cuenta.
Olvidar el perfil de instancia. Crear el rol por CLI no crea el perfil. Recuérdalo cuando el
error hable de un iamInstanceProfile.name inválido.
Poner claves de acceso en user data o en variables de entorno de una instancia. Cualquiera con
acceso a la instancia —o a los metadatos, si IMDSv1 está activo— las lee. Usa el rol. Siempre.
Creer que un Deny de una política se puede «anular» con un Allow en otra. No se puede. Si
algo está denegado y no encuentras dónde, busca en este orden: SCP, límite de permisos, política de
recurso, políticas de identidad. Y usa el mensaje de error, que te dice en cuál está.
Consejo: nombra las políticas con un prefijo propio. pol-mercadofresco-* se distingue de un
vistazo de las gestionadas por AWS en cualquier listado y evita adjuntar la equivocada.
Consejo: versiona las políticas en Git. Un fichero .json por política, revisado por otra
persona antes de aplicarse. Las políticas son código y merecen el mismo trato; en 09-01 y 09-02 las
convertiremos directamente en plantillas de CloudFormation y en constructos de CDK.
Consejo: repasa el informe de credenciales cada tres meses. Cinco minutos que detectan claves de personas que ya no están, usuarios sin MFA y claves de dos años.
Ejercicios
Ejercicio 1: escribir la política de un rol nuevo
MercadoFresco añade una función Lambda llamada mercadofresco-informe-ventas que se ejecuta cada
noche. Necesita: leer todos los objetos de mercadofresco-copias-basedatos bajo el prefijo
exportaciones/, escribir el informe resultante en mercadofresco-informes-analitica bajo
ventas/, escribir sus propios registros en CloudWatch Logs y publicar un mensaje en el tema SNS
alertas-mercadofresco cuando termine. La cuenta es 111122223333 y la región eu-west-1.
Escribe la política de confianza y la política de permisos completas, aplicando mínimo privilegio. Justifica cada ARN.
Ejercicio 2: diagnosticar una denegación
Luis ejecuta un script desde la instancia mercadofresco-tienda-01 y recibe:
An error occurred (AccessDenied) when calling the GetObject operation:
User: arn:aws:sts::111122223333:assumed-role/rol-mercadofresco-tienda/i-0abc123def4567890
is not authorized to perform: s3:GetObject
on resource: "arn:aws:s3:::mercadofresco-informes-analitica/ventas/2026-07.csv"
because no identity-based policy allows the s3:GetObject actionResponde: (a) ¿qué principal está haciendo la llamada exactamente y cómo lo sabes?; (b) ¿el problema
es una denegación explícita o implícita?; (c) ¿cuál es la solución correcta y cuál sería la
solución cómoda y equivocada?; (d) ¿cambiaría algo si el bucket tuviera una política de bucket que
concede s3:GetObject a arn:aws:iam::111122223333:root?
Ejercicio 3: resolver una evaluación de políticas
Sara pertenece al grupo mercadofresco-analitica. Sobre ella actúan estas cuatro políticas:
- Política del grupo:
Allowdes3:GetObjectsobrearn:aws:s3:::mercadofresco-informes-analitica/*. - Política inline del usuario:
Allowdes3:*sobre*. - Política de MFA obligatorio (la del apartado correspondiente), y Sara ha iniciado sesión con MFA.
- Política de bucket de
mercadofresco-copias-basedatos:Denydes3:*a todo principal cuyoaws:PrincipalTag/Departamentono seatecnologia. Sara tiene la etiquetaDepartamento=analitica.
Determina si Sara puede hacer cada una de estas tres operaciones y por qué:
- (a)
s3:GetObjectsobremercadofresco-informes-analitica/ventas/2026-07.csv - (b)
s3:GetObjectsobremercadofresco-copias-basedatos/dump-2026-07-01.sql - (c)
s3:DeleteObjectsobremercadofresco-catalogo-fotos/productos/tomate-rama.jpg
Soluciones
Solución 1
Política de confianza (el principal es el servicio Lambda, igual que en rol-lambda-miniaturas):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}Política de permisos:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Registros",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:/aws/lambda/mercadofresco-informe-ventas:*"
},
{
"Sid": "LeerExportaciones",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mercadofresco-copias-basedatos/exportaciones/*"
},
{
"Sid": "ListarSoloEsePrefijo",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::mercadofresco-copias-basedatos",
"Condition": { "StringLike": { "s3:prefix": "exportaciones/*" } }
},
{
"Sid": "EscribirElInforme",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-informes-analitica/ventas/*"
},
{
"Sid": "AvisarAlTerminar",
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco"
}
]
}Justificación de los ARN:
- El grupo de registros lleva
:*final para abarcar los flujos, y se nombra la función concreta: esta Lambda no puede escribir en los registros de las demás. - La lectura es
GetObjectsobreexportaciones/*únicamente. No hay acceso al resto demercadofresco-copias-basedatos, que contiene las copias completas de la base de datos. ListBucketva sobre el ARN sin/*y acotado pors3:prefix.- La escritura es solo
PutObjectsobreventas/*: no puede borrar informes anteriores. - SNS lleva el ARN del tema exacto, no
*. - No hay
logs:CreateLogGroup(se crea aparte) ni ninguna acción de KMS: si los buckets estuvieran cifrados conalias/mercadofresco-datoshabría que añadirkms:Decryptykms:GenerateDataKeyconkms:ViaService, como veremos en 04-02.
Solución 2
(a) El principal es una sesión de rol asumido: el ARN empieza por arn:aws:sts:: y tiene la
forma assumed-role/<rol>/<nombre-de-sesión>. Para EC2 el nombre de sesión es el identificador de la
instancia, i-0abc123def4567890. Es decir: el código se está ejecutando en la instancia usando
rol-mercadofresco-tienda, exactamente como debe ser. No hay claves de acceso por medio.
(b) Implícita. Lo dice la última línea: because no identity-based policy allows. Si fuera
explícita, el mensaje diría with an explicit deny in....
(c) La solución correcta es preguntarse si la tienda debe leer informes de analítica. Casi
seguro que no: ese bucket es de Sara. Lo que hay que arreglar es el script, no la política. Si de
verdad hiciera falta, se añadiría una declaración con s3:GetObject sobre el prefijo mínimo
necesario. La solución cómoda y equivocada es adjuntar AmazonS3ReadOnlyAccess al rol, que
concedería lectura sobre todos los buckets de la cuenta, incluidas las copias de la base de datos
con datos personales de clientes.
(d) Sí, cambiaría. Con principal y recurso en la misma cuenta, basta con que una de
las dos políticas conceda el permiso. Una política de bucket que concede a
arn:aws:iam::111122223333:root delega en las políticas de identidad de la cuenta... pero ese
root en una política de recurso significa «la cuenta», y por sí solo no concede: sigue haciendo
falta que la política de identidad lo permita. Si en cambio la política de bucket nombrara
explícitamente arn:aws:iam::111122223333:role/rol-mercadofresco-tienda con Allow de
s3:GetObject, entonces sí funcionaría sin tocar la política de identidad. Es una distinción sutil y
es la que más confunde en la práctica.
Solución 3
(a) Permitida. La política del grupo concede s3:GetObject sobre ese bucket, la política inline
también, no hay ningún Deny aplicable (la política 4 solo afecta a mercadofresco-copias-basedatos)
y la condición de MFA se cumple porque Sara inició sesión con MFA.
(b) Denegada. La política de bucket contiene un Deny explícito para principales cuyo
Departamento no sea tecnologia, y el de Sara es analitica. Una denegación explícita gana
siempre, en cualquier política, incluidas las de recurso. Da igual que la política inline le
conceda s3:* sobre *.
(c) Permitida, y ese es justamente el problema. La política inline del punto 2 concede s3:*
sobre *, lo que incluye s3:DeleteObject sobre el catálogo de fotos. Sara, que es analista de
negocio, puede borrar las fotos de producto de la tienda. La política inline anula por completo
el cuidadoso mínimo privilegio del grupo: hay que eliminarla. Este es el patrón real por el que las
cuentas se degradan con el tiempo: alguien añade un permiso amplio «solo para probar» y nadie lo
retira. Un límite de permisos sobre el usuario sara habría evitado el problema, porque el permiso
efectivo es la intersección del límite y de las políticas.
Conclusión
IAM ha dejado de ser la parte que se configura a base de clics hasta que algo funciona. Ahora sabes
que autenticación y autorización son dos cosas distintas y que el tipo de error te dice cuál ha
fallado; sabes leer un ARN campo a campo, incluida la rareza de S3 sin región ni cuenta y la
diferencia entre el ARN del bucket y el de sus objetos, que es el origen de la mitad de los
AccessDenied del mundo.
Conoces los seis tipos de política y, sobre todo, la lógica de evaluación: denegación explícita por encima de permiso explícito, y ambos por encima de la denegación implícita que lo niega todo por defecto. Sabes que en la misma cuenta basta con que una política conceda, que entre cuentas hacen falta las dos, y que KMS es la excepción que obliga siempre a la política de recurso, algo que importará mucho dentro de una lección.
Has entendido por qué una EC2 o una Lambda nunca deben llevar claves de acceso: los roles y
sts:AssumeRole entregan credenciales temporales que se renuevan solas, no se guardan en ningún
fichero y no pueden filtrarse a Git. Y has formalizado por fin los dos roles que arrastrábamos desde
el módulo 2: rol-mercadofresco-tienda, con la política pol-mercadofresco-tienda —lectura de
productos/, escritura cifrada en miniaturas/, métricas acotadas a su propio espacio de nombres y
un Deny que exige TLS— y rol-lambda-miniaturas, con kms:ViaService para que su clave solo
sirva a través de S3. Sabes que a la instancia hay que darle un perfil de instancia, no el rol
directamente.
En el plano humano, MercadoFresco tiene ya sus grupos: mercadofresco-administracion para Marta,
mercadofresco-desarrollo para Luis y mercadofresco-analitica para Sara, esta última con
solo lectura sobre mercadofresco-informes-analitica y confinada a eu-west-1; todos ellos con
MFA obligatorio por condición aws:MultiFactorAuthPresent y con el BoolIfExists que evita el
falso sentido de seguridad. Y tienes el método que de verdad produce mínimo privilegio: empezar sin
permisos, ejecutar, leer el AccessDenied —que te dice acción, recurso y motivo exactos— y añadir
solo lo que falta, ayudándote del simulador de políticas, de --dry-run, del informe de
credenciales y de IAM Access Analyzer, que llega a escribir la política a partir de la
actividad real registrada en CloudTrail (05-03).
Queda un cabo suelto, y es grande. En la política de la tienda hemos exigido que las miniaturas se
suban con s3:x-amz-server-side-encryption: aws:kms, y en la de la Lambda hemos concedido
kms:Decrypt y kms:GenerateDataKey sobre una clave identificada por un UUID. Pero nadie ha
decidido todavía quién administra esa clave, quién puede usarla, cada cuánto rota ni qué ocurre
exactamente cuando S3 cifra un objeto de 4 MB. La clave alias/mercadofresco-datos sigue siendo
un nombre sin política. En la lección 04-02, «AWS Key Management Service (KMS)», la
construiremos de verdad: veremos el cifrado de sobre que explica por qué tus datos nunca viajan
a KMS, la diferencia real entre SSE-S3, SSE-KMS y SSE-C, la política de clave completa para
alias/mercadofresco-datos con Marta como administradora y rol-mercadofresco-tienda como usuario,
y cifraremos por fin el bucket mercadofresco-copias-basedatos, los volúmenes de EBS y la instancia
mercadofresco-pedidos, con datos personales de clientes españoles y el RGPD mirando por encima del
hombro.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
