安全地部署至 Cloud Run

1. 總覽

您將修改將服務部署至 Cloud Run 的預設步驟,藉此提升安全性,然後學習如何以安全的方式存取已部署的應用程式。應用程式為「合作夥伴註冊服務」。

學習目標

只要微幅變更應用程式至 Cloud Run 部署應用程式的預設步驟,即可大幅提高安全性。並變更現有的應用程式和部署操作說明,然後變更部署步驟,提高已部署應用程式的安全性。

隨後您將會看到如何授權存取應用程式並提出授權要求。

本文並未詳盡說明應用程式部署的安全性,而是說明您可以在日後的應用程式部署中變更哪些設定,輕而易舉就能提升安全性。

2. 設定和需求

自修環境設定

  1. 登入 Google Cloud 控制台,建立新專案或重複使用現有專案。如果您還沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 「專案名稱」是這項專案參與者的顯示名稱。這是 Google API 未使用的字元字串。您可以隨時更新。
  • 所有 Google Cloud 專案的專案 ID 均不得重複,而且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常為 PROJECT_ID)。如果您對產生的 ID 不滿意,可以隨機產生一個 ID。此外,您也可以自行嘗試,看看系統是否提供該付款方式。在完成這個步驟後就無法變更,而且在專案期間仍會保持有效。
  • 資訊中的第三個值是專案編號,部分 API 會使用這個編號。如要進一步瞭解這三個值,請參閱說明文件
  1. 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行這個程式碼研究室並不會產生任何費用,如果有的話。如要關閉資源,以免系統產生本教學課程結束後產生的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 的新使用者符合 $300 美元免費試用計畫的資格。

啟用 Cloud Shell

  1. 在 Cloud 控制台中,按一下「啟用 Cloud Shell」圖示 853e55310c205094.png

55efc1aaa7a4d3ad.png

如果您先前從未啟動 Cloud Shell,您會看見中繼畫面 (需捲動位置),說明螢幕內容。如果出現這種情況,請按一下「繼續」 (之後不會再顯示)。以下是單次畫面的外觀:

9c92662c6a846a5c.png

佈建並連線至 Cloud Shell 只需幾分鐘的時間。

9f0e51b578fecce5.png

這個虛擬機器搭載您需要的所有開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。在本程式碼研究室中,您的大部分作業都可以透過瀏覽器或 Chromebook 完成。

連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的專案 ID。

  1. 在 Cloud Shell 中執行下列指令,確認您已通過驗證:
gcloud auth list

指令輸出

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

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. 在 Cloud Shell 中執行下列指令,確認 gcloud 指令知道您的專案:
gcloud config list project

指令輸出

[core]
project = <PROJECT_ID>

如果尚未設定,請使用下列指令進行設定:

gcloud config set project <PROJECT_ID>

指令輸出

Updated property [core/project].

環境設定

在本研究室中,您將在 Cloud Shell 指令列中執行指令。您通常可以複製指令後照原樣貼上,但在某些情況下,您必須變更預留位置值才能修正。

  1. 將環境變數設為專案 ID,供後續指令使用:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
  1. 啟用用來執行應用程式的 Cloud Run 服務 API、提供 NoSQL 資料儲存空間的 Firestore API、部署指令將使用的 Cloud Build API,以及在建構時用於保存應用程式容器的 Artifact Registry:
gcloud services enable \
  run.googleapis.com \
  firestore.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  1. 初始化原生模式的 Firestore 資料庫。該指令會使用 App Engine API,因此必須先啟用。

這個指令必須指定 App Engine 的區域。基於歷史因素,我們不應使用這個區域,且必須建立資料庫的區域。我們將使用 App Engine 的 us-central 和 nam5 作為資料庫。nam5 是美國多區域位置。多區域位置可盡可能提高資料庫的可用性和耐用性。

gcloud services enable appengine.googleapis.com

gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
  1. 複製範例應用程式存放區並前往該目錄
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. 查看 README

開啟編輯器,查看包含應用程式的檔案。查看 README.md,其中會說明部署這個應用程式所需的步驟。其中某些步驟可能涉及隱含或明確的安全性決策,您將變更其中幾個選項,藉此提升已部署應用程式的安全性,說明如下:

步驟 3 - 執行 npm install

請務必瞭解應用程式中任何第三方軟體的來源和完整性。軟體供應鏈安全性的管理與建構軟體相關,不僅限於部署至 Cloud Run 的應用程式。本研究室的重點在於部署,因此請勿探討這個部分,但建議您分別研究這個主題。

步驟 4 和 5:編輯並執行 deploy.sh

這些步驟會將應用程式部署至 Cloud Run,大部分選項都會保留預設值。您將修改這個步驟,以下列兩種方式提高部署作業的安全性:

  1. 請勿允許在未經驗證的情況下存取。您可在探索時嘗試進行試用,是很方便的做法,但這是商業合作夥伴使用的網路服務,且應一律驗證其使用者。
  2. 指明應用程式必須使用專為必要權限的專屬服務帳戶,而非預設帳戶,這類帳戶可能會擁有不必要的 API 和資源存取權。這就是最低權限原則,是應用程式安全性的基本概念。

步驟 6 到 11:提出範例網路要求,以驗證正確行為

由於應用程式部署作業現在需要驗證,因此這些要求現在必須附上要求者的身分證明。您可以直接透過指令列發出要求,而不是修改這些檔案。

4. 安全地部署服務

deploy.sh 指令碼中有兩項必要變更:禁止未驗證的存取權,以及使用僅具備最低權限的專屬服務帳戶。

您必須先建立新的服務帳戶,然後編輯 deploy.sh 指令碼來參照該服務帳戶,並禁止未經驗證的存取權,然後執行修改後的指令碼來部署服務,再執行修改後的 deploy.sh 指令碼。

建立服務帳戶並授予 Firestore/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

編輯「deploy.sh

修改 deploy.sh 檔案以禁止未經驗證的存取權 (no-allow-unauthenticated),並為已部署的應用程式指定新的服務帳戶(–service-account)。將 GOOGLE_PROJECT_ID 修正為您的專案 ID。

您將刪除前兩行,並變更其他三行,如下所示。

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

部署服務

在指令列中執行 deploy.sh 指令碼:

./deploy.sh

部署完成後,指令輸出內容的最後一行會顯示新應用程式的 Service 網址。將網址儲存在環境變數中:

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

現在,請嘗試使用 curl 工具從應用程式擷取訂單:

curl -i -X GET $SERVICE_URL/partners

curl 指令的 -i 旗標會指示該函式在輸出內容中加入回應標頭。第一行輸出內容應為:

HTTP/2 403

應用程式部署時可選擇「禁止」未經驗證的要求。這個 curl 指令不含任何驗證資訊,因此遭到 Cloud Run 拒絕。實際部署的應用程式甚至不會執行或接收這項要求中的任何資料。

5. 發出已驗證的要求

系統會透過提出網路要求來叫用已部署的應用程式,因此現在必須經過 Cloud Run 驗證,才能允許要求。系統會加入下列格式的 Authorization 標頭,藉此驗證網路要求:

Authorization: Bearer identity-token

Identity-token 是信任的驗證提供者核發的短期、經過加密簽署的編碼字串。在這種情況下,必須提供未過期且有效的 Google 核發識別權杖。

以使用者帳戶提出要求

Google Cloud CLI 工具可以為預設通過驗證的使用者提供權杖。執行下列指令,為您的帳戶取得識別權杖,並將其儲存至 ID_TOKEN 環境變數:

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

根據預設,Google 核發的識別憑證的有效期限為一小時。執行下列 curl 指令,要求先前因未獲授權而遭拒的要求。這個指令會包含必要的標頭:

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

指令輸出內容應以 HTTP/2 200 開頭,表示要求可接受且仍在執行。(如果您在一小時後再次嘗試這項要求,就會因為權杖過期而失敗)。回應內文位於輸出的結尾,這個空白行之後:

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

目前沒有任何合作夥伴。

透過兩個 curl 指令,在目錄中使用範例 JSON 資料來註冊合作夥伴:

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

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

如要立即查看所有已註冊的合作夥伴,請重複先前的 GET 要求:

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

您應該會看到 JSON 資料含有更多的內容,提供兩個註冊合作夥伴的相關資訊。

以未經授權帳戶的身分提出要求

上一個步驟中的已驗證要求不僅已經通過驗證,而且已驗證的使用者 (您的帳戶) 也獲得授權。也就是說,這個帳戶有權叫用應用程式。並非所有已驗證帳戶都有權執行此操作。

先前要求中使用的預設帳戶已獲得授權,因為該帳戶是建立含有該應用程式的專案,而且預設授予該帳戶叫用任何 Cloud Run 應用程式的權限。如有需要,您也可以撤銷該權限,在正式版應用程式中可以予以撤銷。此時,您將建立新的服務帳戶 (沒有相關權限或角色),然後透過該帳戶嘗試存取已部署的應用程式。

  1. 建立名為 tester 的服務帳戶。
gcloud iam service-accounts create tester
  1. 您會取得這個新帳戶的識別憑證,方式與您先前為預設帳戶取得身分權杖的方式大不相同。不過,您的預設帳戶必須具有模擬服務帳戶的權限,才能使用這種做法。將這項權限授予帳戶。
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. 接著執行下列指令,在 TEST_IDENTITY 環境變數中儲存這個新帳戶的識別憑證。如果指令顯示錯誤訊息,請等待一兩分鐘後再試一次。
export TEST_TOKEN=$( \
  gcloud auth print-identity-token \
    --impersonate-service-account \
    "tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
  1. 以先前的方式發出經過驗證的網路要求,但使用這個識別憑證:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

指令輸出內容會再次以 HTTP/2 403 開頭,因為該要求未經過「authenticated」,但未取得授權。新的服務帳戶沒有叫用這個應用程式的權限,

授權帳戶

使用者或服務帳戶必須擁有 Cloud Run 服務的 Cloud Run 叫用者角色,才能向該帳戶提出要求。使用下列指令向測試人員服務帳戶授予該角色:

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}

等待一到兩分鐘更新新角色後,重複驗證要求。如果 TEST_TOKEN 自首次儲存後已經過了一小時以上,請儲存新的 TEST_TOKEN。

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

指令輸出內容的開頭是 HTTP/1.1 200 OK,最後一行含有 JSON 回應。Cloud Run 已接受這項要求,並由應用程式處理。

6. 驗證計畫和驗證使用者身分

您提出的已驗證要求,現在已使用 curl 指令列工具。或許還有其他工具和程式設計語言。不過,您「無法」使用含有純網頁的網路瀏覽器提出經驗證的 Cloud Run 要求。如果使用者點選連結或點選按鈕來提交表單,則瀏覽器不會為已驗證的要求加上 Cloud Run 所需的 Authorization 標頭。

Cloud Run 的內建驗證機制適用於程式,而非使用者。

注意:

Cloud Run 可代管面向使用者的網頁應用程式,但這類應用程式必須將 Cloud Run 設為允許使用者發出的未經驗證要求網路瀏覽器。如果應用程式需要使用者驗證,應用程式就必須處理,而非要求 Cloud Run 處理。應用程式可以透過與 Cloud Run 以外的相同方式完成這項作業。這不在本程式碼研究室的範圍內。

您可能已經注意到,到目前為止,對範例要求的回應皆為 JSON 物件,而非網頁。這是因為這項合作夥伴註冊服務主要供計畫使用,而 JSON 是讓他們輕鬆取用的表單。接下來,您將編寫並執行程式來取用和使用這些資料。

來自 Python 程式的已驗證要求

程式可以透過標準 HTTP 網路要求 (包括 Authorization 標頭),向安全的 Cloud Run 應用程式發出已驗證要求。這些計畫唯一的新挑戰是取得可置於該標頭的有效、未過期識別權杖。Cloud Run 會使用 Google Cloud Identity and Access Management (IAM) 驗證該權杖,因此該權杖必須由 IAM 辨識的授權核發及簽署。許多程式語言都提供用戶端程式庫,可用來要求系統核發這類憑證。本範例使用的用戶端程式庫是 Python google.auth 應用程式。有多種 Python 程式庫用於一般提出網路要求;此範例使用熱門的 requests 模組。

第一步是安裝兩個用戶端程式庫:

pip install google-auth
pip install requests

預設使用者要求身分權杖的 Python 程式碼如下:

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

如果您在自己的電腦上使用指令殼層 (例如 Cloud Shell 或標準終端機殼層),則預設使用者就是已在該殼層中驗證的使用者。使用者通常會登入 Google。在其他情況下,則是指已使用 gcloud auth login 或其他 gcloud 指令驗證的任何使用者。如果使用者從未登入,就不會有預設使用者,且這組代碼將失敗。

對於發出其他程式的程式,您通常不想使用某人的身分,而非個人的身分,而非要求程式的身分。這時候服務帳戶就能派上用場您使用專屬服務帳戶部署 Cloud Run 服務,該帳戶提供了在發出 API 要求 (例如對 Cloud Firestore) 時使用的身分。在 Google Cloud Platform 中執行程式時,用戶端程式庫會自動使用獲派的服務帳戶做為預設身分,因此相同的程式程式碼在這兩種情況下都能正常運作。

使用新增 Authorization 標頭提出要求的 Python 程式碼為:

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

下列完整的 Python 程式會向 Cloud Run 服務提出驗證要求,以擷取所有已註冊的合作夥伴,然後列印這些合作夥伴的名稱和指派的 ID。複製並執行下列指令,將此程式碼儲存至 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

您將使用殼層指令執行這個程式。您必須先以預設使用者的身分進行驗證,程式才能使用這些憑證。執行下列 gcloud auth 指令:

gcloud auth application-default login

按照操作說明完成登入程序。接著,從指令列執行程式:

python print_partners.py

輸出內容如下所示:

10102: Zippy food delivery
67292: Foodful

程式的要求已傳送至 Cloud Run 服務,因為該要求已使用您的身分進行驗證。而您是這項專案的擁有者,因此在預設情況下有權執行該專案。此程式更常以服務帳戶的身分執行。在大部分的 Google Cloud 產品 (例如 Cloud Run 或 App Engine) 中執行時,預設身分會是服務帳戶,並且會用來取代個人帳戶。

7. 恭喜!

恭喜,您已完成程式碼研究室!

下一步:

探索其他 Cymbal Eats 程式碼研究室:

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取本教學課程所用資源的費用,請刪除含有相關資源的專案,或者保留專案但刪除個別資源。

刪除專案

如要避免付費,最簡單的方法就是刪除您針對教學課程建立的專案。