API Gateway – Autoryzacja
Jakiś czas temu pojawiły się nowe możliwości autoryzacji zapytań kierowanych do AWS API Gateway. Akurat miałem się tym zająć.
Jakiś czas temu bawiliśmy się API połączonym z Lambdą. Rzadko mamy jednak do czynienia z aplikacjami, z których mogą korzystać wszyscy i to jeszcze anonimowi użytkownicy. Potrzebujemy autoryzacji.
Out of the box AWS oferuje nam autoryzację za pomocą IAM.
Nie chcmy jednak przecież tworzyć użytkowników naszego API w IAM. Udostępniając światu jakieś API, skorzystamy z własnych metod autoryzacji. Na szczęście jest to możliwe.
AWS udostępnia nam dwa sposoby autoryzacji. Możemy wykorzystać Cognito lub funkcje Lambda. Cognito bobawimy się w przyszłości. Dziś za pomocą Lambdy drobimy prostą funkcję autoryzującą requesty. Nie będzie to przykład do zastosowania na produkcji, pokaże on jadnek zasady działania całości. Jeżeli ktoś chce coś na szybko dowedzieć się o Cognito, to proponuję przesłuchanie 209 epizodu podstactu AWS.
Jak to działa? W momencie nadejścia requesta do naszego API, wywoływana jest funkcja Lambda, do której przekazywane są dane z requesta. Tutaj jest opis danych, które przesyłane są do funkcji. W przypadku autoryzacji poprzez token, są to: typ autoryzacji, sam token oraz arn naszego endpointa. Funkcja Lambda waliduje dane, sprawdza czy możemy autoryzować użytkownika i odpowiada naszemu API. Odpowiada zwacając między innymi dokument policy, który zezwala lub nie na użycie naszego API.
{
"Effect": "Allow",
"Action": "execute-api:Invoke"
}
Dodawkowo koniecznie musimy zwrócić z Lambdy id naszego użytkownika "principalId": "userId"
. Jeżeli chcemy, możemy zwrócić w naszej odpowiedzi dodatkowe dane, umieszczając je w obiekcie context. Całość struktury odpowiedzi pochodząca z dokumentacji wygląda tak:
{ "principalId": "yyyyyyyy", // The principal user identification associated with the token sent by the client. "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow|Deny", "Resource": "arn:aws:execute-api:<regionId>:<accountId>:<appId>/<stage>/<httpVerb>/[<resource>/<httpVerb>/[...]]" } ] }, "context": { "stringKey": "value", "numberKey": "1", "booleanKey": "true" } }
Zanim klikniemy więc w Create New Authorizer musimy przygotować Lambdę, która nam tą autoryzację przeprowadzi.
Autoryzujemy
Obsługę autoryzacji podzielimy na dwie funkcje. Pierwsza wygeneruje dla nas odpowiednie policy, a druga przeparsuje dane otrzymane z API Gateway i odeśle mu politykę.
Policy
def policy_generator(resource, token): effect = 'Deny' if token.upper() == 'ALLOW': effect = 'Allow' return { "principalId": "principalId1", "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": effect, "Action": "execute-api:Invoke", "Resource": resource } ] } }
Metoda przyjmyje dwa parametry. Zasób dla którego tworzymy politykę (ARN) oraz token. Logika fukcji sprawdza czy dla danego tokena możemy pozwolić na użycie naszego API (linia 3 i 4), przygotowuje politykę i zwraca ją.
Oczywiście pokazaną autoryzację możemy zastąpić innymi, bardziej skomplikowanymi sposobami na sprawdzenie czy użytkownik jest tym za kogo się podaje i czy ma prawo korzystać z naszego API.
Metoda parsująca dane i zwracająca wynik jest banalna. Poniżej całość kodu, który należy „wkleić” jako handler naszej lambdy autoryzującej
def lambda_handler(event, context): token = event['authorizationToken'] arn = event['methodArn'] return policy_generator(arn, token) def policy_generator(resource, token): effect = 'Deny' if token.upper() == 'ALLOW': effect = 'Allow' return { "principalId": "principalId1", "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": effect, "Action": "execute-api:Invoke", "Resource": resource } ] } }
Authorizer
Mamy już zaimplementowaną logikę naszej autoryzacji, czas na utworzenie authorizera.
Klikamy w ten niebieski guziczek i mamy tochę rzeczy do wypełnienia.
Podajemy jakąś rozsądną nazwę, jako typ wybieramy Lambdę. Wybieramy region, w którym przed chwilą utworzyliśmy naszą funkcję, która nam zapewni autoryzację.
Jako źródło tokena wybierzemy nagłówek o szumnej nazwie token. Pozwolimy także na cachowanie naszej autoryzacji przez 30 sekund.
W sumie wszystko mamy przygotowane i nic nie stoi na przeszkodzie, aby wykonać pierwszy test.
Testujemy
Założyliśmy, że mamy dwa rodzaje tokenów. Jednym z nich jest słowo Allow. Jeżeli prześlemy w nagłówku Allow, Lambda nas autoryzuje pozytywnie. Cokolwiek innego spododuje odrzucenie naszego wywołania. Klikamy więc na Test i
Voila ! Możemy korzystać z naszego API.
Próbujemy przesłać coś innego
i zgodnie z przewidywaniami, Access Denied.
Co nam to dało?
W ten sposób w bardzo prosty sposób zabezpieczyliśmy nasze API przed wykorzystaniem przez każdego. Teraz, aby móc korzystać z naszego endpointa, użytkownik w nagłówku HTTP musi przesłać odpowiednią wartość. My sprawdzamy czy wartość ta jest poprawna i zgodna z oczekiwaniem. Poniżej pokazuje dwie próby połączenia się z naszym endpointem. Za pierwszym razem przekazuję w nagłówkach odpowiednią wartość
i dostajemy oczekiwaną odpowiedź z API. Za drugim token jest niepoprawny i
dostajemy status 403 i informację o braku możliwości autoryzacji.
Proste i skuteczne rozwiązanie.