Comment stocker des données dans S3 et autoriser l'accès des utilisateurs de manière sécurisée avec Rails API / client iOS?

93

Je suis nouveau dans l'écriture de Rails et d'API. J'ai besoin d'aide avec la solution de stockage S3. Voici mon problème.

J'écris une API pour une application iOS où les utilisateurs se connectent avec l'API Facebook sur iOS. Le serveur valide l'utilisateur contre les problèmes de jeton Facebook à l'utilisateur iOS et émet un jeton de session temporaire. À partir de ce moment, l'utilisateur doit télécharger le contenu stocké dans S3. Ce contenu n'appartient qu'à l'utilisateur et à un sous-ensemble de ses amis. Cet utilisateur peut ajouter plus de contenu à S3 qui peut être consulté par le même groupe de personnes. Je suppose que c'est similaire à joindre un fichier à un groupe Facebook ...

Il existe 2 façons pour un utilisateur d'interagir avec S3 ... laisser le serveur au serveur ou demander au serveur d'émettre un jeton S3 temporaire (pas sûr des possibilités ici) et l'utilisateur peut accéder directement aux URL de contenu à S3. J'ai trouvé cette question qui parlait des approches, cependant, elle est vraiment datée (il y a 2 ans): question d' architecture et de conception sur le téléchargement de photos depuis l'application iPhone et S3

Donc les questions:

  • Existe-t-il un moyen de limiter l'accès d'un utilisateur à certains contenus sur S3 lorsqu'un jeton temporaire est émis? Comment puis-je faire ceci? Supposons qu'il y ait ... disons 100 000 utilisateurs ou plus.
  • Est-ce une bonne idée de laisser l'appareil iOS extraire ce contenu directement?
  • Ou devrait-il laisser le serveur contrôler tout le contenu qui passe (cela résout la sécurité bien sûr)? Cela signifie-t-il que je dois télécharger tout le contenu sur le serveur avant de le transmettre aux utilisateurs connectés?
  • Si vous connaissez les rails ... puis-je utiliser des trombones et des gemmes aws-sdk pour réaliser ce genre de configuration?

Toutes mes excuses pour plusieurs questions et j'apprécie tout aperçu du problème. Merci :)

dineth
la source
1
trouvé ceci et j'ai pensé que je ferais un commentaire pour les autres à la recherche de docs.aws.amazon.com/AmazonS3/latest/dev/…
dibble

Réponses:

113

En utilisant le gem aws-sdk , vous pouvez obtenir une URL signée temporaire pour n'importe quel objet S3 en appelant 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

Cela vous donnera une URL d'utilisation temporaire signée uniquement pour cet objet dans S3. Il expire après 20 minutes (dans cet exemple), et il n'est bon que pour cet objet.

Si vous avez beaucoup d'objets dont le client a besoin, vous devrez émettre de nombreuses URL signées.

Ou devrait-il laisser le serveur contrôler tout le contenu qui passe (cela résout la sécurité bien sûr)? Cela signifie-t-il que je dois télécharger tout le contenu sur le serveur avant de le transmettre aux utilisateurs connectés?

Notez que cela ne signifie pas que le serveur doit télécharger chaque objet, il doit uniquement authentifier et autoriser des clients spécifiques à accéder à des objets spécifiques dans S3.

Documents d'API d'Amazon: https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth

ejdyksen
la source
3
Merci pour ça @ejdyksen. La solution que j'ai conçue utilise exactement cela (je n'ai pas mis à jour la question avec ma réponse)! Ma solution était donc de créer des URL authentifiées pour les requêtes GET. Cependant, lorsqu'un utilisateur contribue au contenu, il crée des ressources vers un emplacement spécifique / bucket / user / objectname à l'aide d'un jeton IAM fédéré (informations d'identification temporaires qui expirent) avec la stratégie attachée pour permettre l'accès en écriture à / bucket / user / *. aucun utilisateur du système ne peut donc nuire au contenu des autres utilisateurs. Semble bien fonctionner. Appréciez votre réponse.
dineth le
5
Si vous utilisez la v2 de aws-sdk-ruby, notez que les méthodes sont quelque peu différentes: docs.aws.amazon.com/sdkforruby/api/Aws/S3/…
vijucat
2
N'est-ce pas un risque que l'utilisateur puisse voir ma clé d'accès? Il y a aussi la clé secrète (convertie)
user2503775
3
@Dennis, le point essentiel est que le fichier n'a pas à frapper votre serveur. Le lien ne contient pas vos informations d'identification AWS. Il peut contenir le ACCESS_KEY_ID(je ne me souviens pas du haut de ma tête), mais ce n'est pas censé être un secret.
ejdyksen
2
@ejdyksen vous avez raison, je viens de vérifier que l'URL contient uniquement le fichier AWS_ACCESS_KEY_ID. Au départ, je pensais que cela AWS_SECRET_ACCESS_KEYétait également affiché mais ce n'est pas le cas.
Dennis
46

Les réponses ci-dessus utilisent l'ancienne gemme aws-sdk-v1 plutôt que la nouvelle version 2 de aws-sdk-resources.

La nouvelle façon est:

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

où your_object_key est le chemin de votre fichier. Si vous avez besoin de chercher cela, vous utiliserez quelque chose comme:

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

Cette information était étonnamment difficile à déterrer, et j'ai presque abandonné et utilisé la gemme plus ancienne.

Référence

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

chaqke
la source
1
expires_inattend les données sous forme de secondes, assurez-vous simplement de les convertir en premier.
frillybob
"ArgumentError (attendu: expires_in être un nombre de secondes)"
reedwolf