Jak przechowywać dane w S3 i umożliwić użytkownikom bezpieczny dostęp za pomocą klienta Rails API / iOS?

93

Jestem nowy w pisaniu Railsów i API. Potrzebuję pomocy z rozwiązaniem pamięci masowej S3. Oto mój problem.

Piszę API dla aplikacji iOS, w której użytkownicy logują się za pomocą Facebook API na iOS. Serwer weryfikuje użytkownika pod kątem tokena wystawionego przez Facebooka użytkownikowi iOS i wystawia tymczasowy token sesji. Od tego momentu użytkownik musi pobrać zawartość przechowywaną w S3. Ta treść należy tylko do użytkownika i części jego znajomych. Ten użytkownik może dodać więcej treści do S3, do których ma dostęp ta sama grupa osób. Wydaje mi się, że jest to podobne do dołączenia pliku do grupy na Facebooku ...

Istnieją 2 sposoby, w jakie użytkownik może wchodzić w interakcję z S3 ... pozostawić to serwerowi lub poprosić serwer o wydanie tymczasowego tokena S3 (nie jest pewien możliwości tutaj), a użytkownik może trafić na adresy URL treści bezpośrednio do S3. Znalazłem to pytanie, mówiąc o podejściach, jednak jest naprawdę przestarzałe (2 lata temu): Pytanie architektoniczne i projektowe dotyczące przesyłania zdjęć z aplikacji na iPhone'a i S3

Więc pytania:

  • Czy istnieje sposób na ograniczenie dostępu użytkownika tylko do niektórych treści w S3, gdy zostanie wystawiony tymczasowy token? Jak mogę to zrobić? Załóżmy, że jest ... powiedzmy 100 000 lub więcej użytkowników.
  • Czy dobrym pomysłem jest umożliwienie urządzeniu z systemem iOS bezpośredniego pobierania tej zawartości?
  • A może powinien pozwolić serwerowi kontrolować wszystkie przekazywanie treści (to oczywiście rozwiązuje problem bezpieczeństwa)? Czy to oznacza, że ​​muszę pobrać całą zawartość na serwer, zanim przekażę ją podłączonym użytkownikom?
  • Jeśli znasz szyny ... czy mogę użyć spinacza i klejnotów aws-sdk, aby uzyskać taką konfigurację?

Przepraszam za wiele pytań i doceniam wszelkie wglądy w problem. Dzięki :)

dineth
źródło
1
znalazłem to i pomyślałem, że skomentuję dla innych szukających docs.aws.amazon.com/AmazonS3/latest/dev/ ...
dibble

Odpowiedzi:

113

Korzystając z klejnotu aws-sdk , możesz uzyskać tymczasowy podpisany adres URL dla dowolnego obiektu S3, wywołując 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

W ten sposób otrzymasz podpisany, tymczasowy adres URL tylko dla tego obiektu w S3. Wygasa po 20 minutach (w tym przykładzie) i nadaje się tylko do tego jednego obiektu.

Jeśli masz wiele obiektów, których potrzebuje klient, musisz wydać wiele podpisanych adresów URL.

A może powinien pozwolić serwerowi kontrolować wszystkie przekazywanie treści (to oczywiście rozwiązuje problem bezpieczeństwa)? Czy to oznacza, że ​​muszę pobrać całą zawartość na serwer, zanim przekażę ją podłączonym użytkownikom?

Należy pamiętać, że nie oznacza to, że serwer musi pobrać każdy obiekt, musi jedynie uwierzytelnić i autoryzować określonych klientów w celu uzyskania dostępu do określonych obiektów w S3.

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

ejdyksen
źródło
3
Dzięki za to @ejdyksen. Rozwiązanie, które wymyśliłem, używało dokładnie tego (nie zaktualizowałem pytania moją odpowiedzią)! Więc moim rozwiązaniem było wykonanie uwierzytelnionych adresów URL dla żądań GET. Jednak gdy użytkownik wnosi treść, tworzy zasoby w określonej lokalizacji / bucket / user / objectname przy użyciu stowarzyszonego tokenu IAM (tymczasowych poświadczeń, które wygasają) z dołączoną zasadą, aby zezwolić na dostęp do zapisu / bucket / user / *. więc żaden użytkownik w systemie nie może szkodzić treściom innych użytkowników. Wydaje się, że działa dobrze. Doceń twoją odpowiedź.
kolacja
5
Jeśli używasz wersji 2 aws-sdk-ruby, zwróć uwagę, że metody są nieco inne: docs.aws.amazon.com/sdkforruby/api/Aws/S3/…
vijucat
2
Czy nie istnieje ryzyko, że użytkownik może zobaczyć mój klucz dostępu? Jest też tajny klucz (przekonwertowany)
user2503775
3
@Dennis chodzi o to, że plik nie musi trafiać na twój serwer. Link nie zawiera Twoich danych uwierzytelniających AWS. Może zawierać ACCESS_KEY_ID(nie pamiętam z czubka głowy), ale to nie ma być tajemnicą.
ejdyksen
2
@ejdyksen masz rację, właśnie potwierdziłem, że adres URL zawiera tylko rozszerzenie AWS_ACCESS_KEY_ID. Początkowo myślałem, że AWS_SECRET_ACCESS_KEYto też się wyświetla, ale tak nie jest.
Dennis,
47

Powyższe odpowiedzi używają starego klejnotu aws-sdk-v1 zamiast nowej wersji 2 aws-sdk-resources.

Nowy sposób to:

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

gdzie twój_klucz_obiektu to ścieżka do twojego pliku. Jeśli chcesz to sprawdzić, użyj czegoś takiego:

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

Ta informacja była zaskakująco trudna do odkrycia, a ja prawie po prostu poddałem się i wykorzystałem starszy klejnot.

Odniesienie

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

chaqke
źródło
1
expires_inoczekuje danych w postaci sekund, po prostu upewnij się, że najpierw je przekonwertujesz.
frillybob
„ArgumentError (oczekiwano: expires_in to liczba sekund)”
Nikita Fedyashev