URLs firmadas en S3 con Lambda

Resumen

Subir imágenes directamente a Amazon S3 desde un navegador o aplicación externa requiere una pieza clave: una URL firmada generada por una función Lambda. Aquí verás cómo conectar API Gateway, Lambda y S3 usando el framework Serverless para construir ese flujo de forma segura, con permisos acotados y tiempo de expiración.

Esto te sirve si estás construyendo aplicaciones serverless que manejan archivos de usuarios y necesitas evitar que la imagen pase por tu backend antes de llegar al bucket.

Cómo funciona el flujo de carga directa al bucket S3

El objetivo es que el cliente final, ya sea un navegador o un servicio externo, suba imágenes directamente a S3 sin pasar por tu servidor. Para lograrlo, una Lambda firma una URL temporal que autoriza esa subida.

El bucket ya existe desde la clase anterior. Ahora faltan dos componentes: la función Lambda que firma la URL y la conexión con API Gateway que expone esa Lambda como endpoint HTTP.

¿Qué es una URL firmada en S3? Es una URL temporal que contiene credenciales codificadas para realizar una operación específica sobre un objeto del bucket, como subir o descargar. Expira tras un tiempo definido.

Qué hace el código de la Lambda que firma la URL

El handler importa el SDK de Amazon y genera un cliente S3. Aquí aparece un detalle importante: se usa la firma versión 4, porque desde esa versión es posible firmar URLs.

Dentro de la función async, se extrae el parámetro fileName desde event.queryStringParameters. Ese nombre es obligatorio porque S3 lo necesita para saber con qué nombre quedará guardado el objeto.

Luego se llama a la función getSignedUrl del cliente S3 con el método putObject. Cuando firmas una URL, debes indicar exactamente qué método se usará: en este caso, subir objetos.

Los parámetros que recibe son tres:

  • Key: la ruta y nombre del objeto, compuesta por la carpeta upload/ más el fileName.
  • Bucket: el nombre del bucket, leído desde una variable de entorno.
  • Expires: 300 segundos de vida útil para la URL firmada.

Si alguien intenta usar la URL después de ese tiempo, Amazon devuelve un error 403 indicando expiración. Esto sigue uno de los pilares del Well-Architected Framework: seguridad por defecto.

Cómo configurar el serverless.yml para la Lambda

La configuración real ocurre en el archivo serverless.yml. Hay que definir variables de entorno, declarar la nueva Lambda, conectarla a un evento HTTP y otorgar permisos sobre el bucket.

Cómo declarar variables de entorno en Serverless Framework

Dentro de la clave provider, se agrega una nueva clave llamada environment. Todo lo que pongas ahí se inyecta en cada Lambda del proyecto.

En este caso, se declara la variable BUCKET con el nombre del bucket creado en la clase anterior. Así, dentro del código de la Lambda puedes acceder al nombre sin hardcodearlo.

¿Por qué usar variables de entorno en Lambda? Porque permiten cambiar valores como nombres de buckets, claves o endpoints sin modificar el código fuente. Tu Lambda queda portable entre ambientes.

Cómo definir la nueva función signedUrl

La función se llama signedUrl. El handler apunta al archivo signedUrl.handler, donde el primer segmento es el nombre del archivo y el segundo es la función exportada.

No hace falta declarar el runtime en cada función porque ya está definido a nivel provider y se hereda. En package, se incluye el archivo JS con la lógica.

El evento que dispara la Lambda es un GET HTTP sobre la ruta /signedUrl. Para validar que llegue el parámetro fileName, se usa la sección request.parameters.querystrings, marcando fileName: true. Eso indica que es obligatorio y que API Gateway rechazará la petición si falta.

Esto es valioso porque la validación ocurre antes de invocar la Lambda. Te ahorras escribir lógica defensiva dentro del código.

Cómo otorgar permisos de S3 a la Lambda

La Lambda necesita permisos explícitos para firmar URLs sobre el bucket. Se agrega una nueva entrada en iamRoleStatements apuntando al servicio S3.

Para especificar el recurso, hay que copiar el ARN del bucket. Lo encuentras en la consola de S3, dentro de Propiedades del bucket. Se concede acceso sobre todo el bucket.

En el ejemplo, los permisos son amplios para fines prácticos del curso. Lo recomendable es restringir los permisos solo a las acciones que realmente uses, como s3:PutObject. La documentación de Serverless Framework explica cómo asignar permisos individuales por Lambda.

Cómo probar la URL firmada con Postman

Después de hacer deploy con sls deploy, aparece la nueva función Lambda y su URL pública termina en /signedUrl.

Al hacer un GET sin parámetros, API Gateway responde indicando que falta el parámetro requerido. Esa es la validación de negocio funcionando antes de tocar la Lambda.

Al enviar la petición con ?fileName=imagenejemplo.png, la respuesta es una URL larga que incluye:

  • Información de seguridad y firma.
  • Tiempo de expiración.
  • Nombre del bucket.
  • Ruta y nombre del archivo.

Cómo subir la imagen al bucket usando la URL firmada

Para cargar el archivo, se hace un nuevo request en Postman pegando la URL completa. El método debe ser PUT, porque así lo exige S3 para subir objetos directamente.

En el body, se selecciona la opción binary y se elige el archivo a subir. Tras unos 1.200 milisegundos, llega el código 200.

Al revisar el bucket en la consola de S3, aparece la carpeta upload/ con el archivo imagenejemplo.png dentro. La aplicación queda integrada con la funcionalidad de carga directa de objetos a S3.

¿Has implementado URLs firmadas en algún proyecto? Cuéntame en los comentarios qué tiempos de expiración usas y cómo restringes los permisos IAM.