Módulo 5: AWS Services for AI (S3, Lambda, SageMaker Basics)
6. IAM y Permisos Mínimos
Descripción
En esta cápsula vas a aprender a configurar IAM (Identity and Access Management) para que tu sistema AI en AWS sea seguro en producción. En LocalStack no hay IAM real — todo funciona sin permisos. En AWS, cada acción requiere autorización explícita. Tu Lambda necesita permiso para leer de S3. Tu Lambda necesita permiso para invocar SageMaker. Y esos permisos deben ser los mínimos necesarios — no más. Un Lambda con rol admin en producción es un incidente de seguridad esperando suceder.
Contexto: Has construido el pipeline S3 + Lambda en las cápsulas anteriores. El código funciona, pero en AWS real necesita permisos para operar. Esta cápsula te enseña a crear roles IAM con least privilege: tu Lambda puede leer de ai-assets-dev/documents/* pero no de ai-assets-dev/secrets/*. Puede invocar gpt-4o-mini pero no borrar el bucket. Al terminar, tu servicio será funcional Y seguro.
IAM Concepts para AI Engineers
Los tres pilares de IAM
IAM (Identity and Access Management):
├── Users & Groups → Quién eres (personas, equipos)
├── Roles → Qué identidad puede asumir un servicio (Lambda, EC2)
└── Policies → Qué permisos tiene esa identidad
Para tu Lambda de AI:
├── Lambda asume un Role
├── El Role tiene Policies adjuntas
└── Las Policies definen qué puede hacer
├── ✅ s3:GetObject en ai-assets-dev/documents/*
├── ✅ s3:PutObject en ai-assets-dev/responses/*
├── ❌ s3:DeleteBucket (no necesita esto)
└── ❌ iam:* (definitivamente no)
Principal: quién actúa
# Un Principal es la entidad que realiza una acción
# Para Lambda, el principal es el servicio lambda.amazonaws.com
# Esto permite que Lambda asuma el rol
trust_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
La anatomía de una Policy
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadDocumentsFromS3", # Identificador descriptivo
"Effect": "Allow", # Allow o Deny
"Action": [ # Qué operaciones
"s3:GetObject",
"s3:ListBucket",
],
"Resource": [ # Sobre qué recursos
"arn:aws:s3:::ai-assets-dev",
"arn:aws:s3:::ai-assets-dev/documents/*",
],
}
],
}
# Cada Statement tiene:
# Effect: Allow (permite) o Deny (deniega, tiene prioridad)
# Action: Operaciones API de AWS (s3:GetObject, lambda:InvokeFunction)
# Resource: ARN del recurso específico (no usar * salvo que sea necesario)
Least Privilege: El Principio Fundamental
Qué significa least privilege
Tu Lambda solo debe poder hacer lo que necesita para funcionar. Nada más.
❌ MAL: Policy demasiado permisiva
{
"Effect": "Allow",
"Action": "s3:*", ← Puede hacer CUALQUIER cosa en S3
"Resource": "*" ← En CUALQUIER bucket de la cuenta
}
❌ PEOR: Admin access
{
"Effect": "Allow",
"Action": "*", ← Puede hacer CUALQUIER cosa
"Resource": "*" ← En CUALQUIER recurso
}
✅ BIEN: Solo lo necesario
{
"Effect": "Allow",
"Action": ["s3:GetObject"], ← Solo leer objetos
"Resource": [
"arn:aws:s3:::ai-assets-dev/documents/*", ← Solo este prefijo
"arn:aws:s3:::ai-assets-dev/prompts/*" ← Y este
]
}
Por qué importa para AI
Imagina que tu Lambda tiene s3:* en *. Si un atacante compromete la API key de tu LLM provider y puede ejecutar código arbitrario (prompt injection → code execution en el peor caso), puede:
- Leer TODOS los buckets de tu cuenta (datos sensibles, credenciales)
- Eliminar buckets (sabotaje)
- Subir malware a S3
Con least privilege, lo máximo que puede hacer es leer documentos de un prefijo específico y escribir responses. Daño contenido.
Crear Roles IAM para Lambda
Role con boto3
import boto3
import json
import os
iam = boto3.client("iam", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
ACCOUNT_ID = os.environ.get("AWS_ACCOUNT_ID", "123456789012")
BUCKET_NAME = os.environ.get("AI_BUCKET", "ai-assets-dev")
def create_lambda_role(role_name: str) -> str:
"""Crea un IAM role que Lambda puede asumir."""
trust_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description="Role for AI inference Lambda function",
)
role_arn = response["Role"]["Arn"]
print(f"Role created: {role_arn}")
return role_arn
Policies específicas para AI Lambda
def attach_s3_read_policy(role_name: str, bucket: str, prefixes: list[str]):
"""Adjunta policy para leer objetos de prefijos específicos en S3."""
resources = [f"arn:aws:s3:::{bucket}"]
resources.extend(
f"arn:aws:s3:::{bucket}/{prefix}*" for prefix in prefixes
)
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [f"arn:aws:s3:::{bucket}"],
"Condition": {
"StringLike": {
"s3:prefix": [f"{p}*" for p in prefixes]
}
},
},
{
"Sid": "ReadObjects",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": [
f"arn:aws:s3:::{bucket}/{prefix}*" for prefix in prefixes
],
},
],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName="s3-read-ai-assets",
PolicyDocument=json.dumps(policy),
)
print(f"S3 read policy attached to {role_name}")
def attach_s3_write_policy(role_name: str, bucket: str, prefixes: list[str]):
"""Adjunta policy para escribir objetos en prefijos específicos de S3."""
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "WriteObjects",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": [
f"arn:aws:s3:::{bucket}/{prefix}*" for prefix in prefixes
],
},
],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName="s3-write-responses",
PolicyDocument=json.dumps(policy),
)
print(f"S3 write policy attached to {role_name}")
def attach_cloudwatch_logs_policy(role_name: str):
"""Adjunta policy para que Lambda pueda escribir logs a CloudWatch."""
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
)
print(f"CloudWatch Logs policy attached to {role_name}")
Configurar el role completo
def setup_ai_lambda_role() -> str:
"""Configura un role IAM completo para Lambda de AI inference."""
role_name = "ai-inference-lambda-role"
role_arn = create_lambda_role(role_name)
# Lambda puede LEER prompts y documentos
attach_s3_read_policy(
role_name,
BUCKET_NAME,
prefixes=["prompts/", "documents/", "config/"],
)
# Lambda puede ESCRIBIR responses
attach_s3_write_policy(
role_name,
BUCKET_NAME,
prefixes=["responses/"],
)
# Lambda puede escribir logs
attach_cloudwatch_logs_policy(role_name)
print(f"\nRole configured: {role_name}")
print(f" Read: s3://{BUCKET_NAME}/prompts/*, documents/*, config/*")
print(f" Write: s3://{BUCKET_NAME}/responses/*")
print(f" Logs: CloudWatch Logs")
return role_arn
Inline vs Managed Policies
Tipos de policies
Managed Policies (AWS-managed):
├── AWSLambdaBasicExecutionRole → Logs de CloudWatch
├── AmazonS3ReadOnlyAccess → Leer TODOS los S3 (demasiado amplia)
├── AmazonS3FullAccess → Todo en S3 (NUNCA en producción)
└── AmazonSageMakerFullAccess → Todo en SageMaker (NUNCA en producción)
Managed Policies (customer-managed):
├── Tú las creas
├── Se versionan (hasta 5 versiones)
├── Se pueden adjuntar a múltiples roles
└── Ideales para policies que comparten varios roles
Inline Policies:
├── Adjuntas directamente al role
├── No se comparten entre roles
├── Se eliminan cuando se elimina el role
└── Ideales para policies únicas de un role específico
Cuándo usar cada una
| Tipo | Cuándo | Ejemplo |
|---|---|---|
| AWS Managed | Permisos estándar bien definidos | AWSLambdaBasicExecutionRole para logs |
| Customer Managed | Permisos compartidos entre múltiples roles | Policy de lectura S3 para 5 Lambdas diferentes |
| Inline | Permisos específicos de un solo role | Lambda X puede escribir SOLO en responses/ |
Customer Managed Policy
def create_ai_s3_managed_policy(bucket: str) -> str:
"""Crea una managed policy reutilizable para acceso AI a S3."""
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadAIAssets",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
f"arn:aws:s3:::{bucket}",
f"arn:aws:s3:::{bucket}/prompts/*",
f"arn:aws:s3:::{bucket}/documents/*",
f"arn:aws:s3:::{bucket}/embeddings/*",
],
},
{
"Sid": "WriteResponses",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": [f"arn:aws:s3:::{bucket}/responses/*"],
},
],
}
response = iam.create_policy(
PolicyName="ai-lambda-s3-access",
PolicyDocument=json.dumps(policy),
Description="S3 access policy for AI Lambda functions",
)
policy_arn = response["Policy"]["Arn"]
print(f"Managed policy created: {policy_arn}")
return policy_arn
SAM Template con IAM Roles
Definir permisos en template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AI Service with least-privilege IAM
Parameters:
BucketName:
Type: String
Default: ai-assets-dev
Resources:
AIBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
DocProcessorFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: ai-doc-processor
Handler: handler.handler
Runtime: python3.11
MemorySize: 512
Timeout: 120
Policies:
# SAM simplifica IAM con policy templates
- S3ReadPolicy:
BucketName: !Ref BucketName
- S3CrudPolicy:
BucketName: !Ref BucketName
- Statement:
- Sid: RestrictWriteToResponses
Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${BucketName}/responses/*"
Events:
NewDocument:
Type: S3
Properties:
Bucket: !Ref AIBucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: prefix
Value: documents/inbox/
SAM Policy Templates
SAM ofrece policy templates predefinidos que simplifican IAM:
Policies:
# Lectura de S3
- S3ReadPolicy:
BucketName: !Ref BucketName
# CRUD completo en S3
- S3CrudPolicy:
BucketName: !Ref BucketName
# Invocar otra Lambda
- LambdaInvokePolicy:
FunctionName: !Ref OtherFunction
# Invocar SageMaker endpoint
- Statement:
- Effect: Allow
Action: sagemaker:InvokeEndpoint
Resource: !Sub "arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:endpoint/*"
# SSM Parameter Store
- SSMParameterReadPolicy:
ParameterName: /ai-service/*
# Secrets Manager
- Statement:
- Effect: Allow
Action: secretsmanager:GetSecretValue
Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:ai-service/*"
Security Best Practices
Checklist de seguridad IAM para AI
Permisos de Lambda:
├── ✅ Role específico por función (no compartir roles entre funciones no relacionadas)
├── ✅ Solo las acciones necesarias (s3:GetObject, no s3:*)
├── ✅ Recursos específicos (arn:aws:s3:::bucket/prefix/*, no *)
├── ✅ AWSLambdaBasicExecutionRole para logs
├── ❌ No usar AmazonS3FullAccess
├── ❌ No usar AdministratorAccess
└── ❌ No usar Resource: "*" si puedes especificar
Secrets:
├── ✅ API keys en Environment Variables (mínimo) o SSM/Secrets Manager
├── ✅ Encriptación at rest (habilitada por defecto en Lambda env vars)
├── ❌ No hardcodear API keys en código
├── ❌ No subir .env a Git
└── ❌ No compartir keys entre entornos (dev, staging, prod)
Buckets S3:
├── ✅ Block Public Access habilitado (default en buckets nuevos)
├── ✅ Encryption at rest (SSE-S3 o SSE-KMS)
├── ✅ Versioning para assets críticos (prompt templates)
├── ❌ No habilitar acceso público salvo que sea explícitamente necesario
└── ❌ No usar ACLs (usar policies en su lugar)
Auditoría de permisos
def audit_lambda_permissions(function_name: str):
"""Audita los permisos de un Lambda function."""
lambda_client = boto3.client("lambda")
config = lambda_client.get_function_configuration(FunctionName=function_name)
role_arn = config["Role"]
role_name = role_arn.split("/")[-1]
print(f"Function: {function_name}")
print(f"Role: {role_name}")
print()
# Managed policies
attached = iam.list_attached_role_policies(RoleName=role_name)
print("Managed policies:")
for policy in attached["AttachedPolicies"]:
print(f" - {policy['PolicyName']}")
if "FullAccess" in policy["PolicyName"]:
print(f" ⚠️ WARNING: FullAccess policy detected!")
print()
# Inline policies
inline = iam.list_role_policies(RoleName=role_name)
print("Inline policies:")
for policy_name in inline["PolicyNames"]:
policy_doc = iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)
document = policy_doc["PolicyDocument"]
for stmt in document.get("Statement", []):
actions = stmt.get("Action", [])
resources = stmt.get("Resource", [])
if isinstance(actions, str):
actions = [actions]
if isinstance(resources, str):
resources = [resources]
has_wildcard_action = any("*" in a for a in actions)
has_wildcard_resource = any(r == "*" for r in resources)
print(f" {policy_name}:")
print(f" Actions: {actions}")
print(f" Resources: {resources}")
if has_wildcard_action:
print(f" ⚠️ WARNING: Wildcard action detected!")
if has_wildcard_resource:
print(f" ⚠️ WARNING: Wildcard resource detected!")
print()
Deny Policies: Protección Adicional
def attach_deny_dangerous_actions(role_name: str):
"""Adjunta una deny policy para proteger contra acciones peligrosas."""
deny_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDangerousActions",
"Effect": "Deny",
"Action": [
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"iam:*",
"organizations:*",
"account:*",
],
"Resource": "*",
},
{
"Sid": "DenyDeleteProductionAssets",
"Effect": "Deny",
"Action": ["s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::ai-assets-prod/prompts/*",
"arn:aws:s3:::ai-assets-prod/models/*",
],
},
],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName="deny-dangerous-actions",
PolicyDocument=json.dumps(deny_policy),
)
print(f"Deny policy attached to {role_name}")
Troubleshooting
Problema 1: "AccessDenied" al hacer s3:GetObject
Tu Lambda no tiene permiso para leer del bucket o prefijo.
# Verifica las policies del role
aws iam list-role-policies --role-name ai-inference-lambda-role
aws iam get-role-policy --role-name ai-inference-lambda-role --policy-name s3-read-ai-assets
# Verifica que el Resource incluye el prefijo correcto
# "arn:aws:s3:::ai-assets-dev/documents/*" ← incluye el /*
Problema 2: "is not authorized to perform: sts:AssumeRole"
El trust policy del role no incluye Lambda como principal.
# Verifica el trust policy
response = iam.get_role(RoleName="ai-inference-lambda-role")
print(json.dumps(response["Role"]["AssumeRolePolicyDocument"], indent=2))
# Debe incluir:
# "Principal": {"Service": "lambda.amazonaws.com"}
Problema 3: Lambda funciona en LocalStack pero falla en AWS con permisos
LocalStack no enforcea IAM por defecto. Tu código funciona sin permisos en local pero falla en AWS real.
# En LocalStack: IAM no se enforcea (todo está permitido)
# En AWS: cada operación se valida contra la policy del role
# Solución: Antes de migrar de LocalStack a AWS,
# define las policies necesarias y pruébalas con IAM Policy Simulator:
# https://policysim.aws.amazon.com/
Problema 4: Policy demasiado grande
Las inline policies tienen límite de 10KB. Si tienes muchos prefijos, usa managed policies.
# Inline policy limit: 10,240 characters
# Managed policy limit: 6,144 characters per version, but can have 5 versions
# Si necesitas más: usa conditions en lugar de listar todos los ARNs
Ejercicios Prácticos
Ejercicio 1: Crear policy least-privilege para un servicio RAG
Tu servicio RAG necesita: leer documentos de documents/, leer embeddings de embeddings/, leer prompts de prompts/, escribir responses en responses/, y NO poder acceder a models/ ni borrar nada. Crea la policy completa.
Ver solución
import boto3
import json
import os
iam = boto3.client("iam", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
BUCKET = "ai-assets-prod"
def create_rag_service_policy(role_name: str):
"""Crea policy least-privilege para servicio RAG."""
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucketWithPrefixes",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [f"arn:aws:s3:::{BUCKET}"],
"Condition": {
"StringLike": {
"s3:prefix": [
"documents/*",
"embeddings/*",
"prompts/*",
"responses/*",
]
}
},
},
{
"Sid": "ReadAIAssets",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:HeadObject"],
"Resource": [
f"arn:aws:s3:::{BUCKET}/documents/*",
f"arn:aws:s3:::{BUCKET}/embeddings/*",
f"arn:aws:s3:::{BUCKET}/prompts/*",
],
},
{
"Sid": "WriteResponses",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": [f"arn:aws:s3:::{BUCKET}/responses/*"],
},
{
"Sid": "DenyModelsAccess",
"Effect": "Deny",
"Action": ["s3:*"],
"Resource": [f"arn:aws:s3:::{BUCKET}/models/*"],
},
{
"Sid": "DenyDeleteOperations",
"Effect": "Deny",
"Action": [
"s3:DeleteObject",
"s3:DeleteBucket",
"s3:PutBucketPolicy",
],
"Resource": [
f"arn:aws:s3:::{BUCKET}",
f"arn:aws:s3:::{BUCKET}/*",
],
},
],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName="rag-service-s3-policy",
PolicyDocument=json.dumps(policy),
)
print(f"RAG service policy attached to {role_name}")
print(f" ✅ Read: documents/*, embeddings/*, prompts/*")
print(f" ✅ Write: responses/*")
print(f" ❌ Deny: models/*, delete operations")
create_rag_service_policy("ai-inference-lambda-role")
Ejercicio 2: Auditar un role existente
Escribe una función que reciba un role name y retorne un reporte detallado de todos los permisos, marcando los que son demasiado amplios (wildcards en actions o resources).
Ver solución
import boto3
import json
import os
iam = boto3.client("iam", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
def audit_role(role_name: str) -> dict:
"""Audita un role IAM y retorna reporte de seguridad."""
report = {
"role_name": role_name,
"managed_policies": [],
"inline_policies": [],
"warnings": [],
"score": "secure",
}
attached = iam.list_attached_role_policies(RoleName=role_name)
for p in attached["AttachedPolicies"]:
name = p["PolicyName"]
report["managed_policies"].append(name)
if "FullAccess" in name:
report["warnings"].append(f"FullAccess managed policy: {name}")
if name == "AdministratorAccess":
report["warnings"].append(f"CRITICAL: AdministratorAccess attached!")
report["score"] = "critical"
inline_names = iam.list_role_policies(RoleName=role_name)["PolicyNames"]
for policy_name in inline_names:
doc = iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)
policy_doc = doc["PolicyDocument"]
for stmt in policy_doc.get("Statement", []):
actions = stmt.get("Action", [])
resources = stmt.get("Resource", [])
effect = stmt.get("Effect", "Allow")
if isinstance(actions, str):
actions = [actions]
if isinstance(resources, str):
resources = [resources]
for action in actions:
if action == "*" and effect == "Allow":
report["warnings"].append(f"Wildcard action in {policy_name}")
report["score"] = "critical"
elif ":*" in action and effect == "Allow":
report["warnings"].append(f"Service wildcard: {action} in {policy_name}")
if report["score"] != "critical":
report["score"] = "warning"
for resource in resources:
if resource == "*" and effect == "Allow":
report["warnings"].append(f"Wildcard resource in {policy_name}")
if report["score"] != "critical":
report["score"] = "warning"
report["inline_policies"].append({
"name": policy_name,
"effect": effect,
"actions": actions,
"resources": resources,
})
return report
report = audit_role("ai-inference-lambda-role")
print(json.dumps(report, indent=2, default=str))
if report["warnings"]:
print(f"\n⚠️ {len(report['warnings'])} warning(s) found:")
for w in report["warnings"]:
print(f" - {w}")
else:
print("\n✅ No security warnings found")
Ejercicio 3: Policy para Lambda que invoca SageMaker
Crea una policy que permita a tu Lambda invocar UN endpoint SageMaker específico y leer de un bucket S3 específico, pero nada más.
Ver solución
import boto3
import json
import os
iam = boto3.client("iam", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
ACCOUNT_ID = os.environ.get("AWS_ACCOUNT_ID", "123456789012")
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
def create_lambda_sagemaker_policy(
role_name: str,
endpoint_name: str,
bucket: str,
read_prefixes: list[str],
):
"""Policy para Lambda que invoca SageMaker + lee S3."""
endpoint_arn = f"arn:aws:sagemaker:{REGION}:{ACCOUNT_ID}:endpoint/{endpoint_name}"
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeSageMakerEndpoint",
"Effect": "Allow",
"Action": ["sagemaker:InvokeEndpoint"],
"Resource": [endpoint_arn],
},
{
"Sid": "ReadFromS3",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": [
f"arn:aws:s3:::{bucket}/{prefix}*"
for prefix in read_prefixes
],
},
{
"Sid": "DenyOtherSageMaker",
"Effect": "Deny",
"Action": [
"sagemaker:CreateEndpoint",
"sagemaker:DeleteEndpoint",
"sagemaker:UpdateEndpoint",
],
"Resource": "*",
},
],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName="lambda-sagemaker-inference",
PolicyDocument=json.dumps(policy),
)
print(f"SageMaker inference policy attached to {role_name}")
print(f" ✅ Can invoke: {endpoint_name}")
print(f" ❌ Cannot create/delete/update endpoints")
create_lambda_sagemaker_policy(
role_name="ai-inference-lambda-role",
endpoint_name="sentiment-classifier-v1",
bucket="ai-assets-prod",
read_prefixes=["documents/", "embeddings/"],
)
Ejercicio 4: Script de setup completo para nuevo servicio AI
Crea un script que configure todo el IAM necesario para un nuevo servicio AI: role, trust policy, policies de S3 (read + write), policy de logs, y una deny policy de seguridad. El script debe ser idempotente (ejecutarlo dos veces no falla).
Ver solución
import boto3
import json
import os
iam = boto3.client("iam", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
def setup_ai_service_iam(
service_name: str,
bucket: str,
read_prefixes: list[str],
write_prefixes: list[str],
) -> str:
"""Setup completo de IAM para un servicio AI. Idempotente."""
role_name = f"{service_name}-lambda-role"
# 1. Create role (idempotent)
trust_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
try:
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description=f"Lambda role for {service_name}",
)
role_arn = response["Role"]["Arn"]
print(f"✅ Role created: {role_name}")
except iam.exceptions.EntityAlreadyExistsException:
response = iam.get_role(RoleName=role_name)
role_arn = response["Role"]["Arn"]
print(f"ℹ️ Role already exists: {role_name}")
# 2. S3 read policy
read_policy = {
"Version": "2012-10-17",
"Statement": [{
"Sid": "ReadAIAssets",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:HeadObject", "s3:ListBucket"],
"Resource": [f"arn:aws:s3:::{bucket}"]
+ [f"arn:aws:s3:::{bucket}/{p}*" for p in read_prefixes],
}],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName=f"{service_name}-s3-read",
PolicyDocument=json.dumps(read_policy),
)
print(f"✅ S3 read policy: {read_prefixes}")
# 3. S3 write policy
write_policy = {
"Version": "2012-10-17",
"Statement": [{
"Sid": "WriteResults",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": [f"arn:aws:s3:::{bucket}/{p}*" for p in write_prefixes],
}],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName=f"{service_name}-s3-write",
PolicyDocument=json.dumps(write_policy),
)
print(f"✅ S3 write policy: {write_prefixes}")
# 4. CloudWatch Logs
try:
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
)
print("✅ CloudWatch Logs policy attached")
except iam.exceptions.ClientError:
print("ℹ️ CloudWatch Logs policy already attached")
# 5. Deny dangerous actions
deny_policy = {
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyDangerous",
"Effect": "Deny",
"Action": ["s3:DeleteBucket", "iam:*", "s3:PutBucketPolicy"],
"Resource": "*",
}],
}
iam.put_role_policy(
RoleName=role_name,
PolicyName=f"{service_name}-deny-dangerous",
PolicyDocument=json.dumps(deny_policy),
)
print("✅ Deny policy for dangerous actions")
print(f"\n{'='*50}")
print(f"Service: {service_name}")
print(f"Role ARN: {role_arn}")
print(f"Read: {', '.join(read_prefixes)}")
print(f"Write: {', '.join(write_prefixes)}")
print(f"{'='*50}")
return role_arn
setup_ai_service_iam(
service_name="rag-service",
bucket="ai-assets-prod",
read_prefixes=["documents/", "embeddings/", "prompts/"],
write_prefixes=["responses/"],
)
Resumen
- IAM controla quién puede hacer qué en AWS. Para Lambda, defines un Role con Policies que especifican acciones y recursos permitidos.
- Least privilege: solo los permisos mínimos necesarios.
s3:GetObjectenbucket/prefix/*, nos3:*en*. - Trust policy permite que Lambda asuma el role. Permission policies definen qué puede hacer con ese role.
- Inline policies para permisos específicos de un role. Managed policies para permisos compartidos entre roles.
- Deny policies protegen contra acciones peligrosas (delete bucket, IAM changes) incluso si otra policy las permite.
- LocalStack no enforcea IAM. Tus policies se validan cuando migras a AWS real. Define policies antes de migrar.
- SAM Policy Templates simplifican IAM en templates —
S3ReadPolicy,S3CrudPolicy,LambdaInvokePolicy.
Recursos Adicionales
- IAM Best Practices — Mejores prácticas oficiales
- IAM Policy Reference — Referencia completa de policies
- IAM Policy Simulator — Herramienta para validar policies
- SAM Policy Templates — Templates predefinidos
- S3 Bucket Policies — Policies a nivel bucket
- Lambda Execution Role — Roles de ejecución de Lambda
- IAM Access Analyzer — Herramienta para detectar permisos excesivos
- AWS Security Hub — Monitoreo de seguridad centralizado