Mam dużo statycznej zawartości w zasobniku S3, który nie ma publicznego dostępu do odczytu. Chcę zapewnić dostęp do tych treści, generując podpisane adresy URL.

W większości przypadków używam biblioteki Python i Boto3. Mam jednak kilka surowych zapytań SQL, które pobierają adresy URL prosto z bazy danych i zrzucają je bezpośrednio do przeglądarki. Wolałbym nie musieć przeglądać wszystkich wyników na serwerze, konwertować adresów URL na podpisane adresy URL za pomocą Pythona, a następnie wysyłać je do przeglądarki. Wolałbym, żeby istniał odpowiednik SQL tej magii.

Zamiast

select imageFileUrl from photos;

Chciałbym być w stanie coś zrobić

select signed_url(imageFileUrl) from photos;

Wygląda na to, że klasą / funkcją Pythona boto jest botocore.signers.RequestSigner.sign()

Po dokładniejszym przyjrzeniu się, jego prawdziwym mięsem (przynajmniej dla mojego przypadku użycia) wydaje się być botocore.auth.HmacV1QueryAuth i jego funkcje get_signature, canonical_string, sign_string i _inject_signature .

Czy ktoś wcześniej rozwiązał ten problem? Czy jestem na dobrej drodze?

Używam Pythona 3, Django 2 i Postgresql 10.

Edytować: Mogę rozważyć stworzenie własnego kursora za pomocą omawianego tutaj parametru cursor_factory. http://initd.org/psycopg/docs/extras. html#podklasy połączenia-i-kursora

Być może znajdź sposób na przekazanie mapy funkcji, aby każda kolumna, która wymaga szczególnej uwagi, mogła uzyskać ją dokładnie w punkcie początkowym, zamiast ponownie zapętlić zestaw wyników. Zobaczymy.

0
Justin H. 19 grudzień 2019, 02:19

3 odpowiedzi

Myślę, że nie jest to możliwe, nie przychodzi mi do głowy żaden inny sposób na uniknięcie zapętlenia i generowania podpisów. Ponadto s3 nie obsługuje generowania podpisów dla wielu plików za jednym razem.

0
Arun K 19 grudzień 2019, 02:34

Oto część języka Python, który może wygenerować wstępnie podpisany adres URL za pomocą Pythona 2.7 (czyli wersji używanej przez Redshift):

import base64
import hmac
import sha
import urllib
import time

SECRET_KEY = 'abc123'
OBJECT = '/my-bucket/foo'
DURATION = 120

# Expiry time
expiry_epoch = int(time.time() + DURATION)

# Generate signature
h = hmac.new(SECRET_KEY, "GET\n\n\n" + str(expiry_epoch) + "\n" + OBJECT, sha)
signature = urllib.quote_plus(base64.encodestring(h.digest()).strip())

# Put this after the URL
print("?AWSAccessKeyId=AKIAxxx&Expires=" + str(expiry_epoch) + "&Signature=" + signature)

Wyświetli parametry do dołączenia do adresu URL, aby umożliwić dostęp za pośrednictwem wstępnie podpisanego adresu URL.

Będziesz musiał umieścić swój tajny klucz w kodzie.

Miejmy nadzieję, że możesz następnie przekształcić ten kod w funkcję zdefiniowaną przez użytkownika w przesunięciu ku czerwieni.

1
John Rotenstein 19 grudzień 2019, 08:25
Dziękuję. Używamy lokalnego serwera postgresql, ale postgresql umożliwia również funkcje zdefiniowane przez użytkownika w Pythonie. Więc z pewnością byłaby to jedna z opcji.
 – 
Justin H.
19 grudzień 2019, 15:53
Ups! Przepraszam, błędnie założyłem Redshift, ale widzę, że używasz PostgreSQL. Ta sama ogólna metoda nadal miałaby zastosowanie.
 – 
John Rotenstein
19 grudzień 2019, 23:40

Skończyło się na tym, że przeglądałem wyniki. Nadal uważam, że można to zrobić za pomocą niestandardowego kursora. W końcu mogę to zrobić.

Oto streszczenie, które pokazuje rodzaj pomocnika raw sql, który stworzyłem. https://gist.github.com/cspinelive/9ba8c4034b42c00683ef175f54068546 Pozwala na czysty kod w następujący sposób:

from Acme.db import AcmeQuery

sql = 'select id, "pdf_url" from books where status=%(status)s limit 10'
params = {'status': 'A'}

s3signer = AcmeQuery.S3Signer(cols=['pdf_url'])

rows = AcmeQuery(sql, params=params, row_modifier=s3signer).run()

Oto mięso tego (zobacz sedno pełnego kodu)

class AcmeQuery(object):
  def run(self):
    with connection.cursor() as cursor:
          cursor.execute(sql, params)

          # covert list of tuples to list of dictionaries
          rows = dictfetchall(cursor)

          # post process each row with a custom function
          # function must accept the as a dictionary as its first argument
          # other arguments may be accepted as well via kwargs
          if self.row_modifier:
              for row in rows:
                  self.row_modifier.func(row, **self.row_modifier.params)

   return rows

class RowModifier(object):
    def __init__(self, func, params=None):
        self.func = func
        self.params = params or {}
0
Justin H. 19 grudzień 2019, 20:26