Como armazenar dados no S3 e permitir o acesso do usuário de forma segura com rails API / cliente iOS?

93

Eu sou novo na escrita de Rails e APIs. Preciso de ajuda com a solução de armazenamento S3. Aqui está meu problema.

Estou escrevendo uma API para um aplicativo iOS onde os usuários fazem login com a API do Facebook no iOS. O servidor valida o usuário em relação aos problemas de token do Facebook para o usuário iOS e emite um token de sessão temporário. A partir deste ponto, o usuário precisa baixar o conteúdo que está armazenado no S3. Este conteúdo pertence apenas ao usuário e a um subconjunto de seus amigos. Este usuário pode adicionar mais conteúdo ao S3, que pode ser acessado pelo mesmo grupo de pessoas. Acho que é semelhante a anexar um arquivo a um grupo do Facebook ...

Existem 2 maneiras de um usuário interagir com o S3 ... deixar isso para o servidor ou fazer com que o servidor emita um token S3 temporário (não tenho certeza das possibilidades aqui) e o usuário pode acessar as URLs de conteúdo diretamente para o S3. Achei esta pergunta falando sobre as abordagens, no entanto, é realmente datado (2 anos atrás): Questão de arquitetura e design sobre upload de fotos do aplicativo para iPhone e S3

Então, as perguntas:

  • Existe uma maneira de limitar o acesso de um usuário a apenas algum conteúdo no S3 quando um token temporário é emitido? Como posso fazer isso? Suponha que haja ... digamos 100.000 ou mais usuários.
  • É uma boa ideia permitir que o dispositivo iOS extraia esse conteúdo diretamente?
  • Ou deveria deixar o servidor controlar a passagem de todo o conteúdo (isso resolve a segurança, é claro)? Isso significa que devo baixar todo o conteúdo para o servidor antes de entregá-lo aos usuários conectados?
  • Se você conhece rails ... posso usar joias de clipe de papel e aws-sdk para conseguir esse tipo de configuração?

Peço desculpas por várias perguntas e agradeço qualquer esclarecimento sobre o problema. Obrigado :)

dineth
fonte
1
encontrei isso e pensei em comentar para outros procurando docs.aws.amazon.com/AmazonS3/latest/dev/…
dibble

Respostas:

113

Usando a gem aws-sdk , você pode obter um url assinado temporário para qualquer objeto S3 chamando 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

Isso fornecerá a você um URL de uso temporário e assinado apenas para aquele objeto no S3. Ele expira após 20 minutos (neste exemplo) e só é bom para aquele objeto.

Se você tiver muitos objetos de que o cliente precisa, precisará emitir muitos URLs assinados.

Ou deveria deixar o servidor controlar a passagem de todo o conteúdo (isso resolve a segurança, é claro)? Isso significa que devo baixar todo o conteúdo para o servidor antes de entregá-lo aos usuários conectados?

Observe que isso não significa que o servidor precisa fazer o download de cada objeto, ele só precisa autenticar e autorizar clientes específicos a acessarem objetos específicos no S3.

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

Ejdyksen
fonte
3
Obrigado por isso @ejdyksen. A solução que criei usou exatamente isso (não atualizei a pergunta com minha resposta)! Portanto, minha solução foi fazer URLs autenticados para solicitações GET. No entanto, quando um usuário contribui com conteúdo, ele cria recursos para um local específico de / bucket / user / objectname usando token federado de IAM (credenciais temporárias que expiram) com a política anexada para permitir acesso de gravação a / bucket / user / *. portanto, nenhum usuário no sistema pode prejudicar o conteúdo de outros usuários. Parece funcionar bem. Agradeço sua resposta.
jantar
5
Se você estiver usando a v2 do aws-sdk-ruby, observe que os métodos são um pouco diferentes: docs.aws.amazon.com/sdkforruby/api/Aws/S3/…
vijucat
2
Não é um risco que o usuário possa ver minha chave de acesso? Há também a chave secreta (convertida)
user2503775 01 de
3
@Dennis a questão toda é que o arquivo não precisa chegar ao seu servidor. O link não contém suas credenciais AWS. Pode conter o ACCESS_KEY_ID(não me lembro de cara), mas isso não é segredo.
ejdyksen
2
@ejdyksen você está certo, acabei de verificar que o URL contém apenas o AWS_ACCESS_KEY_ID. Originalmente, pensei que AWS_SECRET_ACCESS_KEYtambém fosse exibido, mas não é.
Dennis
46

As respostas acima usam o antigo gem aws-sdk-v1 em vez do novo aws-sdk-resources versão 2.

A nova forma é:

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

onde your_object_key é o caminho para o seu arquivo. Se você precisar pesquisar isso, use algo como:

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

Essa informação foi surpreendentemente difícil de desenterrar, e quase desisti e usei a joia mais antiga.

Referência

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

chaqke
fonte
1
expires_inespera os dados na forma de segundos, apenas certifique-se de convertê-los primeiro.
frillybob
"ArgumentError (esperado: expires_in em um número de segundos)"
reedwolf