Bezpieczne wdrażanie w Cloud Run

1. Przegląd

Zmodyfikujesz domyślne kroki wdrażania usługi w Cloud Run, aby zwiększyć bezpieczeństwo, a następnie dowiesz się, jak bezpiecznie uzyskać dostęp do wdrożonej aplikacji. Aplikacja jest „usługą rejestracji partnerów” aplikacji Cymbal Eats, która jest używana przez firmy współpracujące z Cymbal Eats do przetwarzania zamówień jedzenia.

Czego się nauczysz

Wprowadzając kilka drobnych zmian w minimalnych domyślnych krokach wdrażania aplikacji w Cloud Run, możesz znacznie zwiększyć jej bezpieczeństwo. Weźmiesz istniejącą aplikację i instrukcje wdrażania, a następnie zmienisz kroki wdrażania, aby zwiększyć bezpieczeństwo wdrożonej aplikacji.

Następnie dowiesz się, jak autoryzować dostęp do aplikacji i wysyłać autoryzowane żądania.

Nie jest to wyczerpujące omówienie bezpieczeństwa wdrażania aplikacji, ale raczej spojrzenie na zmiany, które możesz wprowadzić we wszystkich przyszłych wdrożeniach aplikacji, aby zwiększyć ich bezpieczeństwo przy niewielkim wysiłku.

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz ją zaktualizować w dowolnym momencie.
  • Identyfikator projektu jest unikalny we wszystkich projektach Google Cloud i nie można go zmienić po ustawieniu. Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości ćwiczeń z programowania musisz odwoływać się do identyfikatora projektu (zwykle jest on oznaczony jako PROJECT_ID). Jeśli wygenerowany identyfikator Ci się nie podoba, możesz wygenerować inny losowy identyfikator. Możesz też spróbować własnej nazwy i sprawdzić, czy jest dostępna. Po tym kroku nie można go zmienić i będzie obowiązywać przez cały czas trwania projektu.
  • Warto wiedzieć, że istnieje też trzecia wartość, czyli numer projektu, z której korzystają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczania opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Aktywowanie Cloud Shell

  1. W konsoli Cloud kliknij Aktywuj Cloud Shell 853e55310c205094.png.

55efc1aaa7a4d3ad.png

Jeśli uruchamiasz Cloud Shell po raz pierwszy, zobaczysz ekran pośredni (część strony widoczna po przewinięciu) z opisem tego środowiska. W takim przypadku kliknij Dalej, a ten ekran nie będzie się już wyświetlać. Ten wyświetlany jednorazowo ekran wygląda tak:

9c92662c6a846a5c.png

Uzyskanie dostępu do środowiska Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.

9f0e51b578fecce5.png

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Większość zadań w tym module, a być może wszystkie, możesz wykonać w przeglądarce lub na Chromebooku.

Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu.

  1. Aby potwierdzić, że uwierzytelnianie zostało przeprowadzone, uruchom w Cloud Shell to polecenie:
gcloud auth list

Wynik polecenia

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Aby potwierdzić, że polecenie gcloud zna Twój projekt, uruchom w Cloud Shell to polecenie:
gcloud config list project

Wynik polecenia

[core]
project = <PROJECT_ID>

Jeśli nie, możesz go ustawić za pomocą tego polecenia:

gcloud config set project <PROJECT_ID>

Wynik polecenia

Updated property [core/project].

Konfiguracja środowiska

W tym module będziesz uruchamiać polecenia w wierszu poleceń Cloud Shell. Zazwyczaj możesz skopiować polecenia i wkleić je w niezmienionej postaci, ale w niektórych przypadkach musisz zmienić wartości symboli zastępczych na prawidłowe.

  1. Ustaw zmienną środowiskową na identyfikator projektu, aby używać jej w późniejszych poleceniach:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
  1. Włącz interfejs Cloud Run API, który będzie uruchamiać aplikację, interfejs Firestore API, który będzie udostępniać pamięć danych NoSQL, interfejs Cloud Build API, który będzie używany przez polecenie wdrażania, oraz Artifact Registry, który będzie przechowywać kontener aplikacji po jej utworzeniu:
gcloud services enable \
  run.googleapis.com \
  firestore.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  1. Zainicjuj bazę danych Firestore w trybie natywnym. To polecenie korzysta z interfejsu App Engine API, więc musisz go najpierw włączyć.

Polecenie musi określać region App Engine, którego nie będziemy używać, ale musimy go utworzyć ze względów historycznych, oraz region bazy danych. W przypadku App Engine użyjemy regionu us-central, a w przypadku bazy danych – nam5. nam5 to lokalizacja obejmująca wiele regionów w Stanach Zjednoczonych. Lokalizacje w wielu regionach maksymalizują dostępność i trwałość bazy danych.

gcloud services enable appengine.googleapis.com

gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
  1. Klonowanie repozytorium przykładowej aplikacji i przechodzenie do katalogu
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. Sprawdź plik README

Otwórz edytor i sprawdź pliki, z których składa się aplikacja. Otwórz plik README.md, który zawiera opis kroków potrzebnych do wdrożenia tej aplikacji. Niektóre z tych kroków mogą wymagać podjęcia jawnych lub niejawnych decyzji dotyczących bezpieczeństwa. Aby zwiększyć bezpieczeństwo wdrożonej aplikacji, zmienisz niektóre z tych ustawień w sposób opisany poniżej:

Krok 3. Uruchom npm install

Ważne jest, aby znać pochodzenie i integralność oprogramowania innych firm używanego w aplikacji. Zarządzanie bezpieczeństwem łańcucha dostaw oprogramowania ma znaczenie przy tworzeniu dowolnego oprogramowania, nie tylko aplikacji wdrażanych w Cloud Run. Ten moduł skupia się na wdrażaniu, więc nie omawia tego obszaru, ale możesz zbadać ten temat osobno.

Kroki 4 i 5. Edytowanie i uruchamianie deploy.sh

W ramach tych kroków wdrożysz aplikację w Cloud Run, pozostawiając większość opcji w ustawieniach domyślnych. Zmodyfikujesz ten krok, aby zwiększyć bezpieczeństwo wdrożenia na 2 główne sposoby:

  1. Nie zezwalaj na dostęp bez uwierzytelniania. Może to być wygodne podczas testowania różnych rozwiązań, ale jest to usługa internetowa przeznaczona dla partnerów komercyjnych, która powinna zawsze uwierzytelniać użytkowników.
  2. Określ, że aplikacja musi używać dedykowanego konta usługi z tylko niezbędnymi uprawnieniami, a nie domyślnego konta, które prawdopodobnie będzie miało większy dostęp do interfejsów API i zasobów niż jest to potrzebne. Jest to tzw. zasada jak najmniejszych uprawnień, która jest podstawową koncepcją bezpieczeństwa aplikacji.

Kroki 6–11. Wysyłanie przykładowych żądań internetowych w celu sprawdzenia prawidłowego działania

Wdrażanie aplikacji wymaga teraz uwierzytelniania, więc te żądania muszą zawierać dowód tożsamości osoby wysyłającej żądanie. Zamiast modyfikować te pliki, będziesz wysyłać żądania bezpośrednio z wiersza poleceń.

4. Bezpieczne wdrażanie usługi

W skrypcie deploy.sh zidentyfikowano 2 zmiany, które należy wprowadzić: nie zezwalać na dostęp bez uwierzytelniania i używać dedykowanego konta usługi o minimalnych uprawnieniach.

Najpierw utworzysz nowe konto usługi, a potem zmodyfikujesz skrypt deploy.sh, aby odwoływał się do tego konta usługi i uniemożliwiał dostęp bez uwierzytelnienia.Następnie wdrożysz usługę, uruchamiając zmodyfikowany skrypt, zanim będziemy mogli uruchomić zmodyfikowany skrypt deploy.sh.

Utwórz konto usługi i przyznaj mu niezbędny dostęp do Firestore lub Datastore.

gcloud iam service-accounts create partner-sa

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role=roles/datastore.user

Edytuj: deploy.sh

Zmodyfikuj plik deploy.sh, aby zablokować dostęp bez uwierzytelniania(–no-allow-unauthenticated) i określić nowe konto usługi(–service-account) dla wdrożonej aplikacji. Popraw GOOGLE_PROJECT_ID, aby był to identyfikator Twojego projektu.

Usuń 2 pierwsze wiersze i zmień 3 pozostałe wiersze, jak pokazano poniżej.

gcloud run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region ${REGION} \
  --no-allow-unauthenticated \
  --project=$PROJECT_ID \
  --service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com

Wdrażanie usługi

W wierszu poleceń uruchom skrypt deploy.sh:

./deploy.sh

Po zakończeniu wdrażania w ostatnim wierszu wyniku polecenia wyświetli się URL usługi nowej aplikacji. Zapisz adres URL w zmiennej środowiskowej:

export SERVICE_URL=<URL from last line of command output>

Teraz spróbuj pobrać zamówienie z aplikacji za pomocą narzędzia curl:

curl -i -X GET $SERVICE_URL/partners

Flaga -i w przypadku polecenia curl informuje, że w danych wyjściowych mają być uwzględnione nagłówki odpowiedzi. Pierwszy wiersz danych wyjściowych powinien wyglądać tak:

HTTP/2 403

Aplikacja została wdrożona z opcją odrzucania nieuwierzytelnionych żądań. To polecenie curl nie zawiera informacji o uwierzytelnianiu, więc Cloud Run je odrzuca. Wdrożona aplikacja nie jest nawet uruchamiana ani nie otrzymuje żadnych danych z tego żądania.

5. Wysyłanie uwierzytelnionych żądań

Wdrożona aplikacja jest wywoływana przez wysyłanie żądań internetowych, które muszą być teraz uwierzytelnione, aby Cloud Run na nie zezwoliła. Żądania internetowe są uwierzytelniane przez dodanie nagłówka Authorization w formularzu:

Authorization: Bearer identity-token

Token tożsamości to krótkoterminowy, kryptograficznie podpisany, zakodowany ciąg znaków wydawany przez zaufanego dostawcę uwierzytelniania. W takim przypadku wymagany jest ważny token tożsamości wydany przez Google.

Przesyłanie prośby z konta użytkownika

Narzędzie Google Cloud CLI może udostępnić token domyślnemu uwierzytelnionemu użytkownikowi. Uruchom to polecenie, aby uzyskać token tożsamości dla własnego konta i zapisać go w zmiennej środowiskowej ID_TOKEN:

export ID_TOKEN=$(gcloud auth print-identity-token)

Domyślnie tokeny tożsamości wydane przez Google są ważne przez godzinę. Uruchom to polecenie curl, aby wysłać żądanie, które zostało wcześniej odrzucone z powodu braku autoryzacji. To polecenie będzie zawierać niezbędny nagłówek:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Wynik polecenia powinien zaczynać się od znaku HTTP/2 200, co oznacza, że żądanie jest akceptowane i realizowane. (Jeśli odczekasz godzinę i ponownie spróbujesz wysłać to żądanie, nie powiedzie się ono, ponieważ token wygaśnie). Treść odpowiedzi znajduje się na końcu danych wyjściowych, po pustym wierszu:

{"status":"success","data":[]}

Nie ma jeszcze żadnych partnerów.

Zarejestruj partnerów za pomocą przykładowych danych JSON w katalogu za pomocą 2 poleceń curl:

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner.json" \
  $SERVICE_URL/partner

i

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner2.json" \
  $SERVICE_URL/partner

Powtórz wcześniejsze żądanie GET, aby zobaczyć wszystkich zarejestrowanych partnerów:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Powinny się wyświetlić dane JSON z dużo większą ilością informacji o 2 zarejestrowanych partnerach.

Wysyłanie prośby z nieautoryzowanego konta

Uwierzytelniona prośba z ostatniego kroku została zrealizowana nie tylko dlatego, że została uwierzytelniona, ale też dlatego, że uwierzytelniony użytkownik (Twoje konto) miał odpowiednie uprawnienia. Oznacza to, że konto miało uprawnienia do wywoływania aplikacji. Nie wszystkie uwierzytelnione konta będą miały takie uprawnienia.

Konto domyślne użyte w poprzednim żądaniu zostało autoryzowane, ponieważ to ono utworzyło projekt zawierający aplikację. Domyślnie miało więc uprawnienia do wywoływania dowolnych aplikacji Cloud Run na tym koncie. W razie potrzeby można cofnąć to uprawnienie, co jest pożądane w przypadku aplikacji produkcyjnej. Zamiast tego utworzysz nowe konto usługi bez przypisanych uprawnień ani ról i użyjesz go, aby spróbować uzyskać dostęp do wdrożonej aplikacji.

  1. Utwórz konto usługi o nazwie tester.
gcloud iam service-accounts create tester
  1. Token tożsamości dla tego nowego konta otrzymasz w podobny sposób jak w przypadku konta domyślnego. Wymaga to jednak, aby Twoje domyślne konto miało uprawnienia do przyjmowania tożsamości kont usługi. Przyznaj swojemu kontu to uprawnienie.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="user:$USER_EMAIL" \
  --role=roles/iam.serviceAccountTokenCreator
  1. Teraz uruchom to polecenie, aby zapisać token tożsamości dla tego nowego konta w zmiennej środowiskowej TEST_IDENTITY. Jeśli polecenie wyświetli komunikat o błędzie, poczekaj minutę lub dwie i spróbuj ponownie.
export TEST_TOKEN=$( \
  gcloud auth print-identity-token \
    --impersonate-service-account \
    "tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
  1. Wyślij uwierzytelnione żądanie internetowe tak jak wcześniej, ale użyj tego tokena tożsamości:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

Dane wyjściowe polecenia ponownie zaczną się od HTTP/2 403, ponieważ żądanie, mimo że jest uwierzytelnione, nie jest autoryzowane. Nowe konto usługi nie ma uprawnień do wywoływania tej aplikacji.

Autoryzowanie konta

Aby użytkownik lub konto usługi mogło wysyłać żądania do usługi Cloud Run, musi mieć w niej rolę Cloud Run Invoker. Nadaj konto usługi testera tę rolę za pomocą tego polecenia:

export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
  --member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
  --role=roles/run.invoker \
  --region=${REGION}

Po odczekaniu minuty lub dwóch na zaktualizowanie nowej roli powtórz uwierzytelnione żądanie. Zapisz nowy TEST_TOKEN, jeśli od pierwszego zapisu minęła co najmniej godzina.

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

Dane wyjściowe polecenia zaczynają się teraz od znaku HTTP/1.1 200 OK, a ostatni wiersz zawiera odpowiedź JSON. To żądanie zostało zaakceptowane przez Cloud Run i przetworzone przez aplikację.

6. Uwierzytelnianie programów a uwierzytelnianie użytkowników

Uwierzytelnione żądania, które zostały do tej pory wysłane, korzystały z narzędzia wiersza poleceń curl. Zamiast nich można było użyć innych narzędzi i języków programowania. Uwierzytelnionych żądań Cloud Run nie można wysyłać za pomocą przeglądarki internetowej ze zwykłymi stronami internetowymi. Jeśli użytkownik kliknie link lub przycisk, aby przesłać formularz na stronie internetowej, przeglądarka nie doda nagłówka Authorization wymaganego przez Cloud Run w przypadku uwierzytelnionych żądań.

Wbudowany mechanizm uwierzytelniania Cloud Run jest przeznaczony do użytku przez programy, a nie przez użytkowników.

Uwaga:

Cloud Run może hostować aplikacje internetowe dla użytkowników, ale w przypadku takich aplikacji należy skonfigurować Cloud Run tak, aby zezwalał na nieuwierzytelnione żądania z przeglądarek użytkowników. Jeśli aplikacje wymagają uwierzytelniania użytkownika, muszą obsługiwać je samodzielnie, zamiast prosić o to Cloud Run. Aplikacja może to zrobić w taki sam sposób jak aplikacje internetowe działające poza Cloud Run. Sposób działania wykracza poza zakres tego laboratorium.

Być może zauważysz, że odpowiedzi na przykładowe żądania były do tej pory obiektami JSON, a nie stronami internetowymi. Dzieje się tak, ponieważ ta usługa rejestracji partnerów jest przeznaczona do użytku przez programy, a format JSON jest dla nich wygodny. Następnie napiszesz i uruchomisz program, który będzie korzystać z tych danych.

Uwierzytelnione żądania z programu w Pythonie

Program może wysyłać uwierzytelnione żądania do zabezpieczonej aplikacji Cloud Run za pomocą standardowych żądań HTTP, ale z nagłówkiem Authorization. Jedynym nowym wyzwaniem dla tych programów jest uzyskanie ważnego, nieprzedawnionego tokena tożsamości, który można umieścić w tym nagłówku. Ten token zostanie zweryfikowany przez Cloud Run za pomocą Google Cloud Identity and Access Management (IAM), więc musi zostać wydany i podpisany przez organ uznawany przez IAM. Biblioteki klienta są dostępne w wielu językach programowania, dzięki czemu programy mogą wysyłać żądania o wydanie takiego tokena. W tym przykładzie użyjemy biblioteki klienta w języku Python google.auth. Istnieje kilka bibliotek Pythona do tworzenia żądań internetowych. W tym przykładzie używamy popularnego modułu requests.

Pierwszym krokiem jest zainstalowanie 2 bibliotek klienta:

pip install google-auth
pip install requests

Kod w Pythonie, który umożliwia wysłanie prośby o token tożsamości dla domyślnego użytkownika:

credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token

Jeśli używasz powłoki poleceń, takiej jak Cloud Shell lub standardowa powłoka terminala na własnym komputerze, domyślnym użytkownikiem jest ten, który został uwierzytelniony w tej powłoce. W Cloud Shell jest to zwykle użytkownik zalogowany w Google. W innych przypadkach jest to użytkownik uwierzytelniony za pomocą polecenia gcloud auth login lub innego polecenia gcloud. Jeśli użytkownik nigdy się nie zalogował, nie będzie domyślnego użytkownika, a ten kod nie zadziała.

W przypadku programu wysyłającego żądania do innego programu zwykle nie chcesz używać tożsamości osoby, ale tożsamości programu wysyłającego żądanie. W takim przypadku można skorzystać z konta usługi. Usługa Cloud Run została wdrożona z użyciem dedykowanego konta usługi, które zapewnia tożsamość używaną podczas wysyłania żądań API, np. do Cloud Firestore. Gdy program jest uruchamiany na platformie Google Cloud, biblioteki klienta automatycznie używają przypisanego do niego konta usługi jako tożsamości domyślnej, dzięki czemu ten sam kod programu działa w obu sytuacjach.

Kod Pythona do wysłania żądania z dodatkowym nagłówkiem autoryzacji:

auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)

Ten kompletny program w języku Python wysyła uwierzytelnione żądanie do usługi Cloud Run, aby pobrać wszystkich zarejestrowanych partnerów, a następnie wyświetla ich nazwy i przypisane identyfikatory. Skopiuj i uruchom to polecenie, aby zapisać ten kod w pliku print_partners.py.

cat > ./print_partners.py << EOF
def print_partners():
    import google.auth
    import google.auth.transport.requests
    import requests

    credentials, _ = google.auth.default()
    credentials.refresh(google.auth.transport.requests.Request())
    identity_token = credentials.id_token

    auth_header = {"Authorization": "Bearer " + identity_token}
    response = requests.get("${SERVICE_URL}/partners", headers=auth_header)

    parsed_response = response.json()
    partners = parsed_response["data"]

    for partner in partners:
        print(f"{partner['partnerId']}: {partner['name']}")


print_partners()
EOF

Uruchomisz ten program za pomocą polecenia powłoki. Najpierw musisz uwierzytelnić się jako użytkownik domyślny, aby program mógł używać tych danych logowania. Uruchom to polecenie gcloud auth:

gcloud auth application-default login

Postępuj zgodnie z instrukcjami, aby się zalogować. Następnie uruchom program z wiersza poleceń:

python print_partners.py

Dane wyjściowe będą wyglądać mniej więcej tak:

10102: Zippy food delivery
67292: Foodful

Żądanie programu dotarło do usługi Cloud Run, ponieważ zostało uwierzytelnione za pomocą Twojej tożsamości, a Ty jesteś właścicielem tego projektu, więc domyślnie masz uprawnienia do jego uruchamiania. Zwykle ten program działa pod tożsamością konta usługi. W przypadku większości usług Google Cloud, takich jak Cloud Run czy App Engine, domyślną tożsamością jest konto usługi, które jest używane zamiast konta osobistego.

7. Gratulacje!

Gratulacje! Codelab został ukończony.

Co dalej?

Zapoznaj się z innymi ćwiczeniami z programowania dotyczącymi Cymbal Eats:

Czyszczenie danych

Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby zużyte w tym samouczku, możesz usunąć projekt zawierający te zasoby lub zachować projekt i usunąć poszczególne zasoby.

Usuwanie projektu

Najprostszym sposobem na uniknięcie płatności jest usunięcie projektu utworzonego w tym samouczku.