¿Cómo almacenar datos en S3 y permitir el acceso de los usuarios de forma segura con el cliente Rails API / iOS?

93

Soy nuevo en la escritura de Rails y API. Necesito ayuda con la solución de almacenamiento S3. Este es mi problema.

Estoy escribiendo una API para una aplicación de iOS donde los usuarios inician sesión con la API de Facebook en iOS. El servidor valida al usuario contra el token que Facebook emite al usuario de iOS y emite un token de sesión temporal. A partir de este punto, el usuario debe descargar el contenido almacenado en S3. Este contenido solo pertenece al usuario y a un subconjunto de sus amigos. Este usuario puede agregar más contenido a S3 al que puede acceder el mismo grupo de personas. Supongo que es similar a adjuntar un archivo a un grupo de Facebook ...

Hay 2 formas en que un usuario puede interactuar con S3 ... déjelo al servidor o haga que el servidor emita un token S3 temporal (no estoy seguro de las posibilidades aquí) y el usuario puede acceder a las URL de contenido directamente a S3. Encontré esta pregunta hablando sobre los enfoques, sin embargo, está realmente anticuada (hace 2 años): Pregunta arquitectónica y de diseño sobre la carga de fotos desde la aplicación de iPhone y S3

Entonces las preguntas:

  • ¿Hay alguna forma de limitar el acceso de un usuario a solo algunos contenidos en S3 cuando se emite un token temporal? ¿Cómo puedo hacer esto? Suponga que hay ... digamos 100.000 o más usuarios.
  • ¿Es una buena idea dejar que el dispositivo iOS extraiga este contenido directamente?
  • ¿O debería dejar que el servidor controle todo el contenido que pasa (esto resuelve la seguridad, por supuesto)? ¿Significa esto que tengo que descargar todo el contenido al servidor antes de entregárselo a los usuarios conectados?
  • Si conoces rieles ... ¿puedo usar un clip y gemas aws-sdk para lograr este tipo de configuración?

Disculpas por las múltiples preguntas y agradezco cualquier idea del problema. Gracias :)

cena
fuente
1
encontrado esto y pensé en hacer comentarios para otros que buscan docs.aws.amazon.com/AmazonS3/latest/dev/...
dibble

Respuestas:

113

Con la gema aws-sdk , puede obtener una URL firmada temporal para cualquier objeto S3 llamando a url_for:

s3 = AWS::S3.new(
  :access_key_id => 1234,
  :secret_access_key => abcd
)
object = s3.buckets['bucket'].objects['path/to/object']
object.url_for(:get, { :expires => 20.minutes.from_now, :secure => true }).to_s

Esto le dará una URL de uso temporal firmada solo para ese objeto en S3. Caduca después de 20 minutos (en este ejemplo) y solo es bueno para ese objeto.

Si tiene muchos objetos que el cliente necesita, deberá emitir muchas URL firmadas.

¿O debería dejar que el servidor controle todo el contenido que pasa (esto resuelve la seguridad, por supuesto)? ¿Significa esto que tengo que descargar todo el contenido al servidor antes de entregárselo a los usuarios conectados?

Tenga en cuenta que esto no significa que el servidor necesite descargar cada objeto, solo necesita autenticar y autorizar clientes específicos para acceder a objetos específicos en S3.

Documentos de API de Amazon: https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth

ejdyksen
fuente
3
Gracias por eso @ejdyksen. La solución que he ideado usó exactamente eso (¡no he actualizado la pregunta con mi respuesta)! Entonces, mi solución fue hacer URL autenticadas para solicitudes GET. Sin embargo, cuando un usuario contribuye con contenido, crea recursos en una ubicación específica / bucket / user / objectname utilizando un token de IAM federado (credenciales temporales que caducan) con la política adjunta para permitir el acceso de escritura / bucket / user / *. por lo que ningún usuario del sistema puede dañar el contenido de otros usuarios. Parece que funciona bien. Agradezco tu respuesta.
Cena
5
Si está utilizando la versión 2 de aws-sdk-ruby, tenga en cuenta que los métodos son algo diferentes: docs.aws.amazon.com/sdkforruby/api/Aws/S3/…
vijucat
2
¿No es un riesgo que el usuario pueda ver mi clave de acceso? También está la clave secreta (convertida)
user2503775
3
@Dennis, el punto es que el archivo no tiene que llegar a su servidor. El enlace no contiene sus credenciales de AWS. Puede que contenga el ACCESS_KEY_ID(no recuerdo en la parte superior de mi cabeza), pero eso no pretende ser un secreto.
ejdyksen
2
@ejdyksen tienes razón, acabo de verificar que la URL solo contiene el AWS_ACCESS_KEY_ID. Originalmente pensé que AWS_SECRET_ACCESS_KEYtambién se mostraba, pero no lo es.
Dennis
46

Las respuestas anteriores utilizan la antigua gema aws-sdk-v1 en lugar de la nueva versión 2 de aws-sdk-resources.

La nueva forma es:

aws_resource = Aws::S3::Resource::new
aws_resource.bucket('your_bucket').object('your_object_key').presigned_url(:get, expires_in: 1*20.minutes)

donde your_object_key es la ruta a su archivo. Si necesita buscar eso, usaría algo como:

s3 = Aws::S3::Client::new
keys = []
s3.list_objects(bucket: 'your_bucket', prefix: 'your_path').contents.each { |e| 
  keys << e.key
}

Esa información fue sorprendentemente difícil de desenterrar, y casi me rindo y usé la gema más antigua.

Referencia

http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#presigned_url-instance_method

chaqke
fuente
1
expires_inespera los datos en forma de segundos, solo asegúrese de convertirlos primero.
frillybob
"ArgumentError (esperado: expires_in para ser un número de segundos)"
reedwolf