Bezpieczne wdrażanie w Cloud Run

1. Omówienie

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

Czego się nauczysz

Wprowadzając kilka drobnych zmian w minimalnych domyślnych krokach wdrażania aplikacji do Cloud Run, możesz znacznie zwiększyć jej bezpieczeństwo. Użyjesz istniejącej aplikacji i instrukcji wdrażania, a potem 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 zagadnienia bezpieczeństwa wdrożenia aplikacji, ale przedstawienie zmian, które możesz wprowadzić we wszystkich przyszłych wdrożeniach aplikacji, aby zwiększyć ich bezpieczeństwo bez dużego wysiłku.

2. Konfiguracja i wymagania

Konfigurowanie środowiska we własnym tempie

  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órego nie używają interfejsy API Google. Możesz ją zaktualizować w dowolnym momencie.
  • Identyfikator projektu jest niepowtarzalny w ramach wszystkich projektów Google Cloud i nie można go zmienić (po ustawieniu). Cloud Console automatycznie generuje unikalny ciąg znaków. Zwykle nie ma znaczenia, jaki to ciąg. W większości laboratoriów z kodem musisz odwoływać się do identyfikatora projektu (zazwyczaj jest to PROJECT_ID). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować inny losowy. Możesz też spróbować użyć własnego adresu e-mail i sprawdzić, czy jest on dostępny. Po tym kroku nie można go zmienić. Pozostanie ona niezmieniona przez cały czas trwania projektu.
  • Informacyjnie: istnieje jeszcze 3 wartość, numer projektu, którego używają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć rozliczenia w konsoli Cloud, aby korzystać z zasobów i interfejsów API Cloud. Przejście przez ten moduł Codelab nie powinno wiązać się z wielkimi kosztami, jeśli w ogóle z nimi będzie. Aby wyłączyć zasoby, aby nie generować opłat po zakończeniu 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 Google Cloud kliknij Aktywuj Cloud Shell 853e55310c205094.png.

55efc1aaa7a4d3ad.png

Jeśli nigdy wcześniej nie korzystałeś(-aś) z Cloud Shell, zobaczysz ekran pośredni (poniżej zgięcia), który wyjaśnia, czym jest to środowisko. W takim przypadku kliknij Dalej (nie zobaczysz go już więcej). Oto jak wygląda ekran jednorazowy:

9c92662c6a846a5c.png

Uproszczenie i połączenie z Cloud Shell powinno zająć tylko kilka chwil.

9f0e51b578fecce5.png

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia programistyczne. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie poprawia wydajność sieci i uwierzytelnianie. Większość, jeśli nie wszystkie, czynności w tym ćwiczeniu można wykonać w przeglądarce lub na Chromebooku.

Po połączeniu z Cloud Shell powinieneś zobaczyć, że jesteś już uwierzytelniony i że projekt jest już ustawiony na identyfikator Twojego projektu.

  1. Aby potwierdzić uwierzytelnianie, 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 sprawdzić, czy 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 je ustawić za pomocą tego polecenia:

gcloud config set project <PROJECT_ID>

Wynik polecenia

Updated property [core/project].

Konfiguracja środowiska

W tym module będziesz wydawać polecenia w wierszu poleceń Cloud Shell. Zazwyczaj możesz skopiować polecenia i wkleić je bez zmian, ale w niektórych przypadkach musisz zmienić wartości placeholderów na prawidłowe.

  1. Ustaw zmienną środowiskową na identyfikator projektu, aby używać go 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 API usługi Cloud Run, która będzie uruchamiać aplikację, interfejs Firestore API, który będzie zapewniać przechowywanie danych NoSQL, interfejs Cloud Build API, który będzie używany przez polecenie wdrażania, oraz interfejs Artifact Registry, który będzie przechowywać kontener aplikacji po jego 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 który musimy utworzyć ze względów historycznych, oraz region bazy danych. W przypadku App Engine użyjemy regionu us-central, a do bazy danych nam5, który jest lokalizacją obejmującą 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. Sklonuj repozytorium przykładowej aplikacji i otwórz katalog
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. Przeczytaj plik README.

Otwórz edytor i sprawdź pliki, z których składa się aplikacja. Przejrzyj plik README.md, który zawiera instrukcje wdrażania aplikacji. Niektóre z tych kroków mogą wymagać podjęcia decyzji dotyczących zabezpieczeń. Aby zwiększyć bezpieczeństwo wdrożonej aplikacji, musisz zmienić kilka z tych ustawień:

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 zastosowanie do tworzenia dowolnego oprogramowania, a nie tylko aplikacji wdrożonych w Cloud Run. Ten moduł dotyczy wdrażania, więc nie omawia tego obszaru, ale warto zapoznać się z nim osobno.

Kroki 4 i 5 – edytowanie i uruchamianie deploy.sh

Te czynności wdrażają aplikację do Cloud Run, pozostawiając większość opcji w ich domyślnych ustawieniach. Aby zwiększyć bezpieczeństwo wdrożenia, zmodyfikujesz ten krok na 2 sposoby:

  1. Nie zezwalaj na dostęp bez uwierzytelnienia. Może to być wygodne, gdy chcesz coś wypróbować podczas eksploracji, ale ta usługa internetowa jest przeznaczona dla partnerów komercyjnych i powinna zawsze uwierzytelniać użytkowników.
  2. Określ, że aplikacja musi używać dedykowanego konta usługi z wyłącznie niezbędnymi uprawnieniami, a nie domyślnego konta, które prawdopodobnie będzie miało więcej dostępu do interfejsów API i zasobów niż jest to potrzebne. Jest to tzw. zasada minimalnego wymaganego poziomu uprawnień i jest fundamentalną koncepcją bezpieczeństwa aplikacji.

Kroki 6–11. Przesyłanie przykładowych żądań sieci do weryfikacji prawidłowego działania

Ponieważ wdrażanie aplikacji wymaga teraz uwierzytelniania, te prośby muszą zawierać dowód tożsamości osoby przesyłającej prośbę. Zamiast modyfikować te pliki, będziesz wysyłać zapytania bezpośrednio z poziomu wiersza poleceń.

4. Bezpieczne wdrażanie usługi

W skrypcie deploy.sh konieczne były 2 zmiany: nie zezwalanie na dostęp bez uwierzytelnienia oraz używanie dedykowanego konta usługi o minimalnych uprawnieniach.

Najpierw utwórz nowe konto usługi, a następnie zmodyfikuj skrypt deploy.sh, aby odwoływał się do tego konta i nie zezwalał na nieautoryzowany dostęp.Następnie wprowadź usługę, uruchamiając zmodyfikowany skrypt, zanim będziemy mogli uruchomić zmodyfikowany skrypt deploy.sh.

Utwórz konto usługi i nadaj mu wymagany 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ć nieautoryzowany dostęp(–no-allow-unauthenticated) i określić nowe konto usługi(–service-account) dla wdrożonej aplikacji. Popraw identyfikator GOOGLE_PROJECT_ID na identyfikator własnego projektu.

Usuń 2 pierwsze wiersze i zmień pozostałe 3 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 danych wyjściowych polecenia wyświetli się adres 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 polecenia curl wskazuje, że w wyjściu 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ą zabrania nieautoryzowanych żądań. To polecenie curl nie zawiera informacji uwierzytelniających, więc Cloud Run odrzuca je. Rzeczywista wdrożona aplikacja nie jest nawet uruchamiana i 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 teraz muszą być uwierzytelniane, aby Cloud Run mógł je zaakceptować. Żądania internetowe są uwierzytelniane przez dodanie nagłówka Authorization w takim formacie:

Authorization: Bearer identity-token

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

Przesyłanie żądania z poziomu konta użytkownika

Narzędzie Google Cloud CLI może udostępnić token dla domyślnego uwierzytelnionego użytkownika. Uruchom to polecenie, aby uzyskać token tożsamości dla swojego konta i zapisz go w zmiennej środowiskowej ID_TOKEN:

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

Domyślnie tokeny tożsamości wydawane przez Google są ważne przez 1 godzinę. Aby przesłać żądanie, które zostało odrzucone, ponieważ nie było autoryzowane, uruchom to polecenie curl: 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 HTTP/2 200, co oznacza, że prośba jest akceptowalna i jest honorowana. (jeśli poczekasz godzinę i spróbujesz ponownie wysłać to żądanie, nie uda Ci się to, ponieważ token wygaśnie). Treść odpowiedzi znajduje się na końcu danych wyjściowych, po pustej linii:

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

Nie ma jeszcze żadnych partnerów.

Zarejestruj partnerów, używając przykładowych danych JSON w katalogu za pomocą 2 komend 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śniejszą prośbę GET, aby wyświetlić wszystkich zarejestrowanych partnerów:

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

Powinny się wyświetlić dane w formacie JSON zawierające znacznie więcej informacji o 2 zarejestrowanych partnerach.

Przesyłanie prośby z nieautoryzowanego konta

Uwierzytelnione żądanie przesłane na ostatnim etapie zakończyło się powodzeniem nie tylko dlatego, że zostało uwierzytelnione, ale też dlatego, że uwierzytelniony użytkownik (Twoje konto) został autoryzowany. Oznacza to, że konto miało uprawnienia do wywołania aplikacji. Nie wszystkie uwierzytelnione konta mają takie uprawnienia.

Domyślne konto użyte w poprzednim żądaniu zostało autoryzowane, ponieważ to właśnie na nim utworzono projekt zawierający aplikację, a domyślnie ma ono uprawnienia do wywoływania wszystkich aplikacji Cloud Run na tym koncie. W razie potrzeby można cofnąć to uprawnienie, co jest pożądane w przypadku aplikacji produkcyjnych. Zamiast tego utworzysz nowe konto usługi bez przypisanych uprawnień ani ról i użyjesz go do próby uzyskania dostępu 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 wcześniej dla konta domyślnego. Wymaga to jednak, aby konto domyślne miało uprawnienia do podszywania się pod konta usługi. Przyznaj temu 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. Uruchom teraz to polecenie, aby zapisać token tożsamości tego nowego konta w zmiennej środowiskowej TEST_IDENTITY. Jeśli po wykonaniu polecenia pojawi się komunikat o błędzie, odczekaj 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 sieciowe tak jak wcześniej, ale z użyciem tego tokenu tożsamości:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

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

Autoryzowanie konta

Aby wysyłać do niej żądania, użytkownik lub konto usługi musi mieć rolę Cloud Run Invoker w usłudze Cloud Run. Przypisz tej roli konto usługi testera 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}

Poczekaj minutę lub dwie na zaktualizowanie nowej roli, a potem powtórz żądanie uwierzytelnione. Zapisz nowy identyfikator TEST_TOKEN, jeśli od ostatniego zapisu minęła co najmniej godzina.

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

Wynik polecenia zaczyna się teraz od 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

Dotychczasowe uwierzytelnione żądania zostały wysłane za pomocą narzędzia wiersza poleceń curl. Można było użyć innych narzędzi i języków programowania. Nie można jednak przesyłać uwierzytelnionych żądań Cloud Run za pomocą przeglądarki z 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 użytkowników końcowych.

Uwaga:

Cloud Run może hostować aplikacje internetowe przeznaczone dla użytkowników, ale tego typu aplikacje muszą być skonfigurowane w taki sposób, aby zezwalać na żądania nieuwierzytelnione z przeglądarek użytkowników. Jeśli aplikacje wymagają uwierzytelniania użytkownika, aplikacja musi wykonać to samodzielnie, a nie prosić Cloud Run o wykonanie tej czynności. Aplikacja może to robić w taki sam sposób jak aplikacje internetowe poza Cloud Run. Sposób, w jaki to działa, wykracza poza zakres tego CodeLab.

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

uwierzytelnione żądania z programu Pythona;

Program może przesyłać uwierzytelnione żądania do zabezpieczonej aplikacji Cloud Run za pomocą standardowych żądań HTTP, ale z uwzględnieniem nagłówka Authorization. Jedynym nowym wyzwaniem w przypadku tych programów jest uzyskanie ważnego, niewygasłego tokena tożsamości, który można umieścić w nagłówku. Ten token zostanie zweryfikowany przez Cloud Run za pomocą usługi Google Cloud Identity and Access Management (IAM), więc musi zostać wydany i podpisany przez urząd uznawany przez IAM. Biblioteki klienta są dostępne w wielu językach, dzięki czemu programy mogą prosić o wydanie takiego tokena. W tym przykładzie użyjemy biblioteki klienta google.auth w Pythonie. Istnieje kilka bibliotek Pythona do ogólnego wysyłania żądań HTTP. W tym przykładzie użyto popularnej biblioteki requests.

Najpierw zainstaluj 2 biblioteki klienta:

pip install google-auth
pip install requests

Kod Pythona do żądania tokenu 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 swoim komputerze, domyślnym użytkownikiem jest ten, który został uwierzytelniony w tej powłoce. W Cloud Shell jest to zazwyczaj użytkownik zalogowany w Google. W innych przypadkach jest to dowolny użytkownik uwierzytelniony za pomocą polecenia gcloud auth login lub innego polecenia gcloud. Jeśli użytkownik nigdy się nie logował, nie będzie użytkownika domyślnego i ten kod nie zadziała.

W przypadku programu wysyłającego żądania do innego programu zazwyczaj nie używa się 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 dedykowanym kontem usługi, które zapewnia tożsamość używaną podczas wysyłania żądań do interfejsu API, np. do Cloud Firestore. Gdy program działa na platformie Google Cloud, biblioteki klienta automatycznie używają przypisanego do niego konta usługi jako domyślnej tożsamości, więc ten sam kod programu działa w obu sytuacjach.

Kod Pythona, który umożliwia wysłanie żądania z dodanym nagłówkiem Authorization, to:

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

Poniższy program Pythona wyśle uwierzytelnione żądanie do usługi Cloud Run, aby pobrać wszystkich zarejestrowanych partnerów, a potem wydrukować ich nazwy i przypisane identyfikatory. Skopiuj i uruchom podane niżej 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 w powłoce. Najpierw musisz się uwierzytelnić jako domyślny użytkownik, aby program mógł używać tych danych logowania. Uruchom podane niżej polecenie gcloud auth:

gcloud auth application-default login

Postępuj zgodnie z instrukcjami, aby się zalogować. Następnie uruchom program z poziomu 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. Jesteś właścicielem tego projektu, więc masz domyślne uprawnienia do jego uruchamiania. Zwykle program jest uruchamiany z użyciem tożsamości konta usługi. W przypadku większości usług Google Cloud, takich jak Cloud Run czy App Engine, domyślna tożsamość to konto usługi, które będzie używane zamiast konta osobistego.

7. Gratulacje!

Gratulacje! Masz ukończone zajęcia.

Co dalej:

Inne ćwiczenia z programowania dotyczące 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 na potrzeby samouczka.