Cómo conectar Django de forma segura a AWS S3 — Dos enfoques recomendados

Cómo conectar Django de forma segura a AWS S3 — Dos enfoques recomendados

Cuando despliegas aplicaciones Django en AWS, manejar el acceso a S3 de forma segura es fundamental. Dejar las credenciales expuestas en el código es un gran riesgo. Por suerte, AWS ofrece dos formas seguras y efectivas para autenticar tu backend cuando trabaja con S3:

🔐 Dos opciones seguras para acceder a S3 desde Django

OpciónDescripciónIdeal paraSeguridad
1. Variables de entorno en Elastic BeanstalkDefines tus claves de AWS directamente como variables en EBConfiguración rápida, entornos de staging o desarrolloSegura (si tu consola está protegida)
2. IAM Role asignado a EC2EC2 recibe credenciales temporales automáticamenteProducción, cumplimiento normativo, escalabilidadLa mejor práctica

Opción 1: Usar variables de entorno

1. El problema: Credenciales escritas en el código

Evita hacer esto en tu settings.py:

AWS_ACCESS_KEY_ID = "AKIA123..."
AWS_SECRET_ACCESS_KEY = "abcd123..."

2. Cargar las credenciales desde variables de entorno

import os

AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")

3. Crear un script de carga: set_env.sh

#!/bin/bash
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="abcd..."
export AWS_STORAGE_BUCKET_NAME="nombre-de-tu-bucket"

Luego:

chmod +x set_env.sh

4. Agregarlo a .gitignore

# Secretos del entorno
set_env.sh

5. Automatizar la carga al activar el entorno virtual

# Al final del archivo envs/bin/activate
source /ruta/completa/a/tu/proyecto/set_env.sh

6. Prueba final

source envs/bin/activate
echo $AWS_ACCESS_KEY_ID

💡 ¿Por qué es importante?

Las variables de entorno mantienen tus claves fuera del código y evitan filtraciones accidentales. Es una excelente solución para desarrollo o staging.

Opción 2: Usar IAM Roles (recomendado para producción)

Con IAM Roles, AWS gestiona la autenticación automáticamente mediante credenciales temporales, sin necesidad de guardar nada.

Paso 1: Crear un IAM Role

  1. Ve a AWS Console > IAM > Roles
  2. Haz clic en Crear rol
  3. Selecciona: AWS Service → EC2
  4. Asocia la política AmazonS3FullAccess (o una personalizada)
  5. Asigna un nombre como DjangoStarsUp-InstanceRole-S3Only
  6. Haz clic en Crear rol

Paso 2: Asignar el rol a Elastic Beanstalk

  1. Ve a Elastic Beanstalk > tu entorno > Configuration
  2. Edita la sección Service access
  3. Selecciona el rol creado en EC2 instance profile
  4. Haz clic en Apply

Problema común: Falla en el health check

Elastic Beanstalk valida la salud de las instancias accediendo a /. Si Django no responde con 200 OK, marcará la instancia como fallida.

Paso 3: Agregar un endpoint de health check en Django

from django.http import JsonResponse
from django.urls import path

urlpatterns += [
    path("health/", lambda request: JsonResponse({"status": "ok"})),
]

Paso 4: Cambiar el health check path en EB

  1. Ve a Configuration > Instance traffic and scaling
  2. Edita el proceso llamado default
  3. Cambia el campo Health check path a /health/
  4. Guarda los cambios

Paso 5: Ajustar settings.py para usar IAM Role

import os

AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")

AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", None)
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", None)

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_S3_REGION_NAME = 'us-east-1'
AWS_QUERYSTRING_AUTH = False

if not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
    print("Usando IAM Role desde el perfil de instancia EC2")

🔧 Resolución de Problemas Real: Actualizar el IAM Role sin Romper el Entorno
Cuando cambias el EC2 Instance Profile en Elastic Beanstalk a un nuevo IAM Role (por ejemplo, DjangoStarsUp-InstanceRole-S3Only), tu entorno entra en modo de actualización continua (rolling update). Esto hace que se lance una nueva instancia EC2 con el nuevo perfil.
Este proceso puede fallar si no se prepara correctamente:
❗ Problema común: Error 4xx y estado “Severe”
Si tu archivo settings.py en Django no incluye la IP privada de la nueva instancia EC2 dentro de ALLOWED_HOSTS, entonces Django rechazará las peticiones del Load Balancer, y Elastic Beanstalk marcará la instancia como “unhealthy”.

Solución Comprobada (Usada en Producción) Paso 1: Permitir todas las IPs temporalmente Antes de cambiar el IAM Role, comenta tu lista de ALLOWED_HOSTS y reemplázala por: pythonCopyEditALLOWED_HOSTS = ['*'] Esto permitirá que cualquier origen acceda temporalmente, evitando errores mientras actualizas.
Paso 2: Ejecuta eb deploy Esto iniciará la nueva instancia EC2 con el nuevo IAM Role, y te permitirá verla activa en el panel de EC2 o ver sus logs para obtener la IP.
Paso 3: Agrega la nueva IP privada a ALLOWED_HOSTS Ejemplo: pythonCopyEditALLOWED_HOSTS = [ '127.0.0.1', 'localhost', 'su-env.us-east-2.elasticbeanstalk.com', 'tu-dominio.com', 'api.tu-dominio.com', 'abcd.cloudfront.net', '172.31.XX.XX', # IP privada de la nueva EC2 ] Puedes obtener esta IP ejecutando dentro de la instancia (vía SSH): bashCopyEditcurl http://169.254.169.254/latest/meta-data/local-ipv4
Paso 4: Haz un nuevo eb deploy Una vez que hayas agregado la IP correctamente, puedes quitar el ['*'] y dejar ALLOWED_HOSTS configurado de forma segura.
Verifica si tu app usa IAM Role o variables de entorno Para confirmar si boto3 está usando el IAM Role y no variables de entorno, entra por SSH a la instancia EC2 y ejecuta: env | grep AWS Si no aparece ninguna variable, significa que boto3 está usando correctamente las credenciales temporales generadas por el IAM Role.
Permisos mínimos recomendados para el IAM Role Además del acceso a S3, Elastic Beanstalk necesita permisos para operar correctamente. Agrega esta política al rol: jsonCopyEdit[ "cloudwatch:PutMetricData", "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "ec2:DescribeInstances", "elasticloadbalancing:DescribeTargetHealth" ] Puedes omitir permisos relacionados con contenedores como ecr:* si no usas Docker.

Resultado final

Tu backend Django ahora se conecta a S3 de forma totalmente segura:

  • Sin claves en el código ni en disco
  • Con credenciales rotadas automáticamente
  • Escalable y en línea con buenas prácticas de AWS
  • Con un health check funcional para EB

Política personalizada de ejemplo

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::nombre-de-tu-bucket",
        "arn:aws:s3:::nombre-de-tu-bucket/*"
      ]
    }
  ]
}

¿Qué sigue?

En la Parte 3 te mostraremos cómo almacenar secretos y claves de forma segura usando AWS Secrets Manager en producción.