Cómo Crear una URL Prefirmada de S3 para Acceso Temporal a Archivos Privados

Tienes un archivo privado en S3 y necesitas que un usuario lo descargue sin exponerlo públicamente ni crear credenciales temporales de IAM. La URL prefirmada de S3 es la solución estándar para este patrón: genera un enlace con firma criptográfica que expira en el tiempo que tú definas, sin cambiar los permisos del bucket ni del objeto.

TL;DR: Resumen Rápido

AspectoDetalle
¿Qué es?URL firmada con credenciales IAM que permite acceso temporal a un objeto S3 privado
Expiración típica3600 segundos (1 hora) — configurable
SDK principalAWS SDK v3 para Node.js / Python (boto3)
Operación firmadaGetObject para descarga, PutObject para subida
Permiso IAM requeridos3:GetObject sobre el recurso objetivo
¿Cambia permisos del bucket?No. El acceso es delegado por las credenciales del firmante

Cómo Funciona una URL Prefirmada de S3

Una URL prefirmada no es magia — es una URL de S3 estándar con parámetros de autenticación adicionales incrustados en la query string. Cuando el SDK genera la URL, firma la solicitud usando las credenciales IAM activas en ese momento (usuario, rol, o credenciales temporales de STS). S3 valida esa firma al recibir la petición y verifica que no haya expirado.

El punto crítico que confunde a muchos: el acceso lo autoriza quien firma, no quien usa la URL. Si las credenciales del firmante son revocadas antes de que expire la URL, S3 rechazará las solicitudes aunque la URL sea técnicamente válida. Esto es diferente a un token de sesión — la URL hereda los permisos del firmante en el momento de la solicitud entrante, no en el momento de la generación.

Es como firmar un cheque en blanco con fecha de vencimiento. Quien lo tenga puede cobrarlo, pero si tu cuenta queda bloqueada antes de esa fecha, el cheque rebota.
sequenceDiagram participant B as Tu Backend participant SDK as AWS SDK participant C as Cliente participant S3 as Amazon S3 B->>SDK: generate_presigned_url(bucket, key, ExpiresIn=3600) Note over SDK: Firma construida localmente
sin llamada de red a S3 SDK-->>B: URL prefirmada con firma B-->>C: Entrega URL (API / email / etc.) C->>S3: GET URL prefirmada S3->>S3: Valida firma e IAM del firmante S3-->>C: 200 OK + contenido del objeto
  1. Tu backend llama al SDK con las credenciales IAM activas para generar la URL prefirmada.
  2. El SDK construye la URL con parámetros de firma (algoritmo, fecha, expiración) sin contactar a S3.
  3. Tu backend entrega la URL al cliente (via API, email, etc.).
  4. El cliente realiza una petición HTTP directa a S3 usando esa URL.
  5. S3 valida la firma contra las credenciales IAM del firmante y sirve el objeto si es válida y no ha expirado.

Requisitos IAM para Generar URLs Prefirmadas de S3

Antes de escribir código, el rol o usuario que ejecuta el SDK necesita el permiso correcto. Sin s3:GetObject sobre el objeto objetivo, S3 rechazará la solicitud cuando el cliente intente usar la URL — no cuando la generes. Este desfase temporal es una fuente común de confusión en debugging.

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

Si el bucket tiene Block Public Access activado (recomendado), eso no afecta las URLs prefirmadas — ese control bloquea políticas de bucket públicas y ACLs, no el acceso autenticado via firma. Verifica el estado con:

aws s3api get-public-access-block \
  --bucket nombre-de-tu-bucket

Generando la URL Prefirmada con AWS SDK

Los ejemplos siguientes cubren los dos SDKs más usados en producción. Ambos generan la URL localmente sin hacer una llamada de red a S3 durante la generación — la latencia es despreciable.

Python (boto3)

import boto3
from botocore.exceptions import ClientError

def generar_url_prefirmada(nombre_bucket, clave_objeto, expiracion=3600):
    """
    Genera una URL prefirmada para descargar un objeto S3 privado.

    :param nombre_bucket: Nombre del bucket S3
    :param clave_objeto: Clave (path) del objeto dentro del bucket
    :param expiracion: Tiempo en segundos hasta que expire la URL (default: 3600)
    :return: URL prefirmada como string, o None si ocurre un error
    """
    cliente_s3 = boto3.client('s3')

    try:
        url = cliente_s3.generate_presigned_url(
            'get_object',
            Params={
                'Bucket': nombre_bucket,
                'Key': clave_objeto
            },
            ExpiresIn=expiracion
        )
        return url
    except ClientError as e:
        print(f'Error generando URL prefirmada: {e}')
        return None

# Uso
url = generar_url_prefirmada('mi-bucket-privado', 'documentos/reporte-2024.pdf')
if url:
    print(f'URL válida por 1 hora: {url}')

Node.js (AWS SDK v3)

🔽 Ver código completo — Node.js AWS SDK v3
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const clienteS3 = new S3Client({ region: 'us-east-1' });

async function generarUrlPrefirmada(nombreBucket, claveObjeto, expiracionSegundos = 3600) {
  const comando = new GetObjectCommand({
    Bucket: nombreBucket,
    Key: claveObjeto,
  });

  try {
    const url = await getSignedUrl(clienteS3, comando, {
      expiresIn: expiracionSegundos,
    });
    return url;
  } catch (error) {
    console.error('Error generando URL prefirmada:', error);
    throw error;
  }
}

// Uso
generarUrlPrefirmada('mi-bucket-privado', 'documentos/reporte-2024.pdf')
  .then(url => console.log('URL válida por 1 hora:', url))
  .catch(err => console.error(err));

El paquete @aws-sdk/s3-request-presigner debe instalarse por separado del cliente S3 en SDK v3:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

Verificación desde CLI: Generando una URL Prefirmada de S3

Para validar rápidamente sin escribir código, la CLI de AWS genera URLs prefirmadas directamente. Útil para debugging o scripts de administración:

aws s3 presign s3://nombre-de-tu-bucket/documentos/reporte-2024.pdf \
  --expires-in 3600

La CLI usa las credenciales del perfil activo para firmar. Si estás en un entorno con roles de instancia EC2 o ECS, usará esas credenciales temporales — y la URL expirará cuando antes ocurra: el tiempo que especificaste o la expiración de las credenciales temporales del rol.

Caso Real: El Error que No Aparece al Generar la URL

El escenario clásico: generas la URL sin errores, la entregas al cliente, y el cliente recibe un 403 AccessDenied al intentar usarla. El SDK no lanza excepción durante la generación porque la firma se construye localmente — S3 nunca es consultado en ese paso.

El diagnóstico inicial suele apuntar al bucket policy o al Block Public Access. Ambos son pistas falsas si el problema real es que el rol que firmó la URL no tiene s3:GetObject sobre ese objeto específico. La URL está perfectamente firmada — simplemente firmada por alguien sin permisos suficientes.

Para confirmar qué credenciales está usando tu proceso al generar la URL:

aws sts get-caller-identity

Luego verifica que ese principal tenga el permiso necesario:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/tu-rol-backend \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::nombre-de-tu-bucket/documentos/reporte-2024.pdf

Si el resultado muestra implicitDeny o explicitDeny, ese es tu problema — no la URL en sí.

Consideraciones de Seguridad Operacional

Las URLs prefirmadas son seguras por diseño, pero hay patrones de uso que introducen riesgo en producción:

  • Expiración mínima necesaria: No uses 7 días porque 'es más cómodo'. Define la expiración según el caso de uso real. Para descarga inmediata, 15 minutos suele ser suficiente.
  • No registres la URL en logs de larga retención: Cualquiera con acceso a esos logs puede usar la URL mientras esté vigente.
  • Credenciales temporales como firmante: Si el proceso que genera URLs usa un rol IAM (EC2, Lambda, ECS), la URL no puede sobrevivir más tiempo que las credenciales del rol. Intentar generar URLs con expiración mayor a la duración máxima de sesión del rol resulta en URLs que expiran antes de lo esperado.
  • HTTPS obligatorio: S3 soporta HTTPS para todas las operaciones. Las URLs prefirmadas generadas por el SDK usan HTTPS por defecto.
graph TD A["URL Prefirmada Generada"] --> B{"¿Expiró el tiempo
configurado?"} B -- Sí --> E["S3 rechaza: RequestExpired"] B -- No --> C{"¿Credenciales del
firmante vigentes?"} C -- Revocadas --> F["S3 rechaza: InvalidAccessKeyId"] C -- Vigentes --> D{"¿Firmante tiene
s3:GetObject?"} D -- No --> G["S3 rechaza: AccessDenied"] D -- Sí --> H["S3 sirve el objeto: 200 OK"]
  1. Expiración corta (recomendado): La URL expira según el tiempo configurado. Riesgo acotado si la URL es interceptada.
  2. Credenciales revocadas: Si el rol firmante es revocado antes de la expiración, S3 rechaza la URL aunque no haya expirado técnicamente.
  3. Credenciales temporales: Si el firmante usa credenciales STS, la URL expira cuando antes ocurra — el tiempo configurado o la expiración de la sesión STS.

Wrap-Up y Próximos Pasos con URLs Prefirmadas de S3

Generar una URL prefirmada de S3 con expiración de 1 hora se reduce a tres elementos: credenciales IAM con s3:GetObject, el nombre del bucket y la clave del objeto, y el parámetro ExpiresIn en segundos. El SDK construye la firma localmente — no hay llamada de red durante la generación.

Para profundizar en este patrón y casos de uso relacionados:

Glosario de Términos Clave

TérminoDefinición
URL Prefirmada (Presigned URL)URL de S3 con parámetros de autenticación incrustados que permite acceso temporal a un objeto privado sin credenciales propias del solicitante
Firmante (Signer)El principal IAM (usuario, rol) cuyas credenciales se usan para generar la firma de la URL
ExpiresInParámetro en segundos que define cuánto tiempo es válida la URL desde su generación
GetObjectOperación S3 que recupera un objeto; la acción IAM s3:GetObject es requerida para URLs de descarga
Block Public AccessConfiguración de bucket/cuenta que bloquea acceso público via ACLs y políticas de bucket; no afecta URLs prefirmadas

Related Posts

Comentarios

Entradas populares de este blog

EC2 sin acceso a Internet en VPC personalizada: Internet Gateway y Route Table

Actualizar Contenido en CloudFront: Cómo Crear una Invalidación para Limpiar el Caché del Edge

Aumentar el Timeout de Lambda: Configuración, Límites y Diagnóstico en Producción