建立第一個機密空間環境

在本指南中,Alex 和 Bola 想找出誰的薪水最高,但不想讓對方知道確切金額。他們決定使用 Confidential Space 保護資料機密性,並同意擔任下列角色

  • Alex:資料協作者、工作負載作者

  • Bola:資料協作者、工作負載運算子

這項安排是為了盡可能簡化本指南的內容。不過,工作負載的作者和運算子可以完全獨立於資料協作者,而且您可以視需要新增任意數量的協作者。

事前準備

本指南會使用單一機構中的單一帳戶,示範機密空間情境,並提供多個專案的存取權,讓您體驗整個流程。在正式部署中,協作者、工作負載作者和工作負載運算子會使用不同的帳戶,並在各自的機構中擁有專案,彼此無法存取,機密資料也會分開存放。

機密空間可與許多 Google Cloud服務互動,以產生結果,包括但不限於:

本指南會使用這些功能,並假設您已具備基本瞭解。

必要的角色

如要取得完成本指南所需的權限,請要求管理員在專案中授予您下列 IAM 角色:

  • 資料協作者 (Alex 和 Bola) 的 Cloud KMS 管理員 (roles/cloudkms.admin)。
  • 資料協作者 (Alex 和 Bola) 的身分與存取權管理 Workload Identity 集區管理員 (roles/iam.workloadIdentityPoolAdmin)。
  • 資料協作者 (Alex 和 Bola) 的服務使用情形管理員 (roles/serviceusage.serviceUsageAdmin)。
  • 資料協作者 (Alex 和 Bola) 和工作負載運算子 (Bola) 的 Storage 管理員 (roles/storage.admin)。
  • 工作負載運算子 (Bola) 的服務帳戶管理員 (roles/iam.serviceAccountAdmin)。
  • 工作負載運算子 (Bola) 的 Compute 管理員 (roles/compute.admin)。
  • 工作負載運算子 (Bola) 的安全管理員 (roles/securityAdmin)。
  • 工作負載作者 (Alex) 的 Artifact Registry 管理員 (roles/artifactregistry.admin)。

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

設定資料協作者資源

Alex 和 Bola 都需要獨立專案,其中包含下列資源:

  • 機密資料本身。

  • 加密金鑰,用於加密資料並確保機密性。

  • 用於儲存加密資料的 Cloud Storage bucket。

  • 工作負載身分集區。處理機密資料的工作負載會使用集區存取私有資料並解密。

如要開始使用,請前往 Google Cloud 控制台:

前往 Google Cloud 控制台

設定 Alex 的資源

如要為 Alex 設定資源,請按照下列操作說明進行。

  1. 按一下「啟用 Cloud Shell」
  2. 在 Cloud Shell 中輸入下列指令,為 Alex 建立專案,並將 ALEX_PROJECT_ID 替換為您選擇的名稱:

    gcloud projects create ALEX_PROJECT_ID
  3. 切換至新建立的專案:

    gcloud config set project ALEX_PROJECT_ID
  4. 以資料協作者和工作負載作者的身分,啟用 Alex 需要的 API:

    gcloud services enable \
        artifactregistry.googleapis.com \
        cloudkms.googleapis.com \
        iamcredentials.googleapis.com
  5. 使用 Cloud Key Management Service 建立金鑰環和加密金鑰:

    gcloud kms keyrings create ALEX_KEYRING_NAME \
        --location=global
    
    gcloud kms keys create ALEX_KEY_NAME \
        --location=global \
        --keyring=ALEX_KEYRING_NAME \
        --purpose=encryption
  6. 授予 Alex cloudkms.cryptoKeyEncrypter 角色,讓他們可以使用新建立的加密金鑰加密資料:

    gcloud kms keys add-iam-policy-binding \
        "projects/ALEX_PROJECT_ID/locations/global/\
    keyRings/ALEX_KEYRING_NAME/\
    cryptoKeys/ALEX_KEY_NAME" \
        --member=user:$(gcloud config get-value account) \
        --role=roles/cloudkms.cryptoKeyEncrypter
  7. 建立 Alex 的 workload identity pool:

    gcloud iam workload-identity-pools create ALEX_POOL_NAME \
        --location=global
  8. 建立 Cloud Storage bucket 來存放輸入資料,並建立另一個 bucket 來存放結果:

    gcloud storage buckets create gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_OUTPUT_BUCKET_NAME
  9. 建立檔案,只包含 Alex 的薪資 (以數字表示):

    echo 123456 > ALEX_SALARY.txt
  10. 加密檔案,然後上傳至 Alex 的 bucket:

    gcloud kms encrypt \
        --ciphertext-file="ALEX_ENCRYPTED_SALARY_FILE" \
        --plaintext-file="ALEX_SALARY.txt" \
        --key="projects/ALEX_PROJECT_ID/locations/global/\
    keyRings/ALEX_KEYRING_NAME/\
    cryptoKeys/ALEX_KEY_NAME"
    gcloud storage cp ALEX_ENCRYPTED_SALARY_FILE gs://ALEX_INPUT_BUCKET_NAME

設定 Bola 的資源

如要為 Bola 設定資源,請按照下列操作說明進行。

  1. 在 Cloud Shell 中輸入下列指令,為 Bola 建立專案,並將 BOLA_PROJECT_ID 替換成您選擇的名稱:

    gcloud projects create BOLA_PROJECT_ID
  2. 切換至新建立的專案:

    gcloud config set project BOLA_PROJECT_ID
  3. 以資料協作者和工作負載運算子身分,啟用 Bola 要求的 API:

    gcloud services enable \
        cloudkms.googleapis.com \
        compute.googleapis.com \
        confidentialcomputing.googleapis.com \
        iamcredentials.googleapis.com
  4. 使用 Cloud Key Management Service 建立金鑰環和加密金鑰:

    gcloud kms keyrings create BOLA_KEYRING_NAME \
        --location=global
    
    gcloud kms keys create BOLA_KEY_NAME \
        --location=global \
        --keyring=BOLA_KEYRING_NAME \
        --purpose=encryption
  5. 授予 Bola cloudkms.cryptoKeyEncrypter 角色,讓他們可以使用新建立的加密金鑰加密資料:

    gcloud kms keys add-iam-policy-binding \
        "projects/BOLA_PROJECT_ID/locations/global/\
    keyRings/BOLA_KEYRING_NAME/\
    cryptoKeys/BOLA_KEY_NAME" \
        --member=user:$(gcloud config get-value account) \
        --role=roles/cloudkms.cryptoKeyEncrypter
  6. 建立 Bola 的 workload identity pool:

    gcloud iam workload-identity-pools create BOLA_POOL_NAME \
        --location=global
  7. 建立 Cloud Storage bucket 來存放輸入資料,並建立另一個 bucket 來存放結果:

    gcloud storage buckets create gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_OUTPUT_BUCKET_NAME
  8. 建立檔案,只包含 Bola 的薪資 (以數字表示):

    echo 111111 > BOLA_SALARY.txt
  9. 加密檔案,然後上傳至 Bola 的 bucket:

    gcloud kms encrypt \
        --ciphertext-file="BOLA_ENCRYPTED_SALARY_FILE" \
        --plaintext-file="BOLA_SALARY.txt" \
        --key="projects/BOLA_PROJECT_ID/locations/global/\
    keyRings/BOLA_KEYRING_NAME/\
    cryptoKeys/BOLA_KEY_NAME"
    gcloud storage cp BOLA_ENCRYPTED_SALARY_FILE gs://BOLA_INPUT_BUCKET_NAME

為工作負載建立服務帳戶

在本指南中,Bola 負責運作及執行工作負載,但任何人 (包括第三方) 都能擔任這些角色。Bola 建立的 VM 執行個體會附加服務帳戶,該帳戶有權產生認證權杖、寫入記錄、讀取 Alex 和 Bola 的加密資料,以及將結果寫入特定 Cloud Storage 值區。

在 Bola 的專案中完成下列步驟,設定服務帳戶:

  1. 建立服務帳戶來執行工作負載:

    gcloud iam service-accounts create WORKLOAD_SERVICE_ACCOUNT_NAME
    
  2. iam.serviceAccountUser 角色授予 Bola,這樣他稍後就能將服務帳戶附加至工作負載 VM:

    gcloud iam service-accounts add-iam-policy-binding \
        WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --member=user:$(gcloud config get-value account) \
        --role=roles/iam.serviceAccountUser
    
  3. confidentialcomputing.workloadUser 角色授予服務帳戶,以便產生認證權杖:

    gcloud projects add-iam-policy-binding BOLA_PROJECT_ID \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/confidentialcomputing.workloadUser
    
  4. logging.logWriter 角色授予服務帳戶,以便將記錄寫入 Cloud Logging,方便您查看工作負載的進度:

    gcloud projects add-iam-policy-binding BOLA_PROJECT_ID \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/logging.logWriter
    
  5. 授予服務帳戶讀取 Alex 和 Bola 值區的權限,這些值區包含加密資料,並授予服務帳戶寫入每個結果值區的權限:

    gcloud storage buckets add-iam-policy-binding gs://ALEX_INPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectViewer
    
    gcloud storage buckets add-iam-policy-binding gs://ALEX_OUTPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectAdmin
    
    gcloud storage buckets add-iam-policy-binding gs://BOLA_INPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectViewer
    
    gcloud storage buckets add-iam-policy-binding gs://BOLA_OUTPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectAdmin
    

    前提是授予存取權的使用者,在包含要操作的 Cloud Storage 值區的專案中,具有「儲存空間管理員」roles/storage.admin 角色。

建立工作負載

在本指南中,Alex 會提供工作負載的程式碼,並建構 Docker 映像檔來存放程式碼,但任何人 (包括第三方) 都可以擔任這些角色。

Alex 需要為工作負載建立下列資源:

  • 執行工作負載的程式碼。

  • Artifact Registry 中的 Docker 存放區,執行工作負載的服務帳戶必須具備存取權。

  • 包含並執行工作負載程式碼的 Docker 映像檔。

如要建立及設定資源,請在 Alex 的專案中完成下列步驟:

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 按一下「開啟編輯器」開啟 Cloud Shell 編輯器,然後建立名為 salary.go 的新檔案。將下列程式碼複製到檔案中,然後儲存:

    package main
    
    import (
      "context"
      "fmt"
      "io"
      "os"
      "strconv"
      "strings"
      "time"
    
      kms "cloud.google.com/go/kms/apiv1"
      kmspb "cloud.google.com/go/kms/apiv1/kmspb"
      "cloud.google.com/go/storage"
      "google.golang.org/api/option"
    )
    
    type collaborator struct {
      name         string
      wipName      string
      keyName      string
      inputBucket  string
      inputFile    string
      outputBucket string
    }
    
    // The following values are pulled from environment variables
    
    // Alex's values
    var collaborator1Name string = os.Getenv("COLLAB_1_NAME") // Alex's name
    var collaborator1EncryptedSalaryFileName string = os.Getenv("COLLAB_1_ENCRYPTED_SALARY") // The name of Alex's encrypted salary file.
    var collaborator1BucketInputName string = os.Getenv("COLLAB_1_INPUT_BUCKET") // The name of the storage bucket that contains Alex's encrypted salary file.
    var collaborator1BucketOutputName string = os.Getenv("COLLAB_1_OUTPUT_BUCKET") // The name of the storage bucket to store Alex's results in.
    var collaborator1KMSKeyringName string = os.Getenv("COLLAB_1_KEYRING_NAME") // Alex's Key Management Service key ring.
    var collaborator1KMSKeyName string = os.Getenv("COLLAB_1_KEY_NAME") // Alex's Key Management Service key.
    var collaborator1ProjectName string = os.Getenv("COLLAB_1_PROJECT_ID") // Alex's project ID.
    var collaborator1ProjectNumber string = os.Getenv("COLLAB_1_PROJECT_NUMBER") // Alex's project number.
    var collaborator1PoolName string = os.Getenv("COLLAB_1_POOL_NAME") // Alex's workload identity pool name.
    
    // Bola's values
    var collaborator2Name string = os.Getenv("COLLAB_2_NAME") // Bola's name
    var collaborator2EncryptedSalaryFileName string = os.Getenv("COLLAB_2_ENCRYPTED_SALARY") // The name of Bola's encrypted salary file.
    var collaborator2BucketInputName string = os.Getenv("COLLAB_2_INPUT_BUCKET") // The name of the storage bucket that contains Bola's encrypted salary file.
    var collaborator2BucketOutputName string = os.Getenv("COLLAB_2_OUTPUT_BUCKET") // The name of the storage bucket to store Bola's results in.
    var collaborator2KMSKeyringName string = os.Getenv("COLLAB_2_KEYRING_NAME") // Bola's Key Management Service key ring.
    var collaborator2KMSKeyName string = os.Getenv("COLLAB_2_KEY_NAME") // Bola's Key Management Service key.
    var collaborator2ProjectName string = os.Getenv("COLLAB_2_PROJECT_ID") // Bola's project ID.
    var collaborator2ProjectNumber string = os.Getenv("COLLAB_2_PROJECT_NUMBER") // Bola's project number.
    var collaborator2PoolName string = os.Getenv("COLLAB_2_POOL_NAME") // Bola's workload identity pool name.
    
    var collaborators = [2]collaborator{
      {
        collaborator1Name,
        "projects/" + collaborator1ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator1PoolName + "/providers/attestation-verifier",
        "projects/" + collaborator1ProjectName + "/locations/global/keyRings/" + collaborator1KMSKeyringName + "/cryptoKeys/" + collaborator1KMSKeyName,
        collaborator1BucketInputName,
        collaborator1EncryptedSalaryFileName,
        collaborator1BucketOutputName,
      },
      {
        collaborator2Name,
        "projects/" + collaborator2ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator2PoolName + "/providers/attestation-verifier",
        "projects/" + collaborator2ProjectName + "/locations/global/keyRings/" + collaborator2KMSKeyringName + "/cryptoKeys/" + collaborator2KMSKeyName,
        collaborator2BucketInputName,
        collaborator2EncryptedSalaryFileName,
        collaborator2BucketOutputName,
      },
    }
    
    const credentialConfig = `{
            "type": "external_account",
            "audience": "//iam.googleapis.com/%s",
            "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
            "token_url": "https://sts.googleapis.com/v1/token",
            "credential_source": {
              "file": "/run/container_launcher/attestation_verifier_claims_token"
            }
            }`
    
    func main() {
      fmt.Println("workload started")
      ctx := context.Background()
    
      storageClient, err := storage.NewClient(ctx) // Using the default credential on the Compute Engine VM
      if err != nil {
        panic(err)
      }
    
      // Get and decrypt
      s0, err := getSalary(ctx, storageClient, collaborators[0])
      if err != nil {
        panic(err)
      }
    
      s1, err := getSalary(ctx, storageClient, collaborators[1])
      if err != nil {
        panic(err)
      }
    
      res := ""
      if s0 > s1 {
        res = fmt.Sprintf("%s earns more!\n", collaborators[0].name)
      } else if s1 > s0 {
        res = fmt.Sprintf("%s earns more!\n", collaborators[1].name)
      } else {
        res = "You earn the same!\n"
      }
    
      now := time.Now()
      for _, cw := range collaborators {
        outputWriter := storageClient.Bucket(cw.outputBucket).Object(fmt.Sprintf("comparison-result-%d", now.Unix())).NewWriter(ctx)
    
        _, err = outputWriter.Write([]byte(res))
        if err != nil {
          fmt.Printf("Could not write: %v", err)
          panic(err)
        }
        if err = outputWriter.Close(); err != nil {
          fmt.Printf("Could not close: %v", err)
          panic(err)
        }
      }
    }
    
    func getSalary(ctx context.Context, storageClient *storage.Client, cw collaborator) (float64, error) {
      encryptedBytes, err := getFile(ctx, storageClient, cw.inputBucket, cw.inputFile)
      if err != nil {
        return 0.0, err
      }
      decryptedByte, err := decryptByte(ctx, cw.keyName, cw.wipName, encryptedBytes)
      if err != nil {
        return 0.0, err
      }
      decryptedNumber := strings.TrimSpace(string(decryptedByte))
      num, err := strconv.ParseFloat(decryptedNumber, 64)
      if err != nil {
        return 0.0, err
      }
      return num, nil
    }
    
    func decryptByte(ctx context.Context, keyName, wippro string, encryptedData []byte) ([]byte, error) {
      cc := fmt.Sprintf(credentialConfig, wippro)
      kmsClient, err := kms.NewKeyManagementClient(ctx, option.WithCredentialsJSON([]byte(cc)))
      if err != nil {
        return nil, fmt.Errorf("creating a new KMS client with federated credentials: %w", err)
      }
    
      decryptRequest := &kmspb.DecryptRequest{
        Name:       keyName,
        Ciphertext: encryptedData,
      }
      decryptResponse, err := kmsClient.Decrypt(ctx, decryptRequest)
      if err != nil {
        return nil, fmt.Errorf("could not decrypt ciphertext: %w", err)
      }
    
      return decryptResponse.Plaintext, nil
    }
    
    func getFile(ctx context.Context, c *storage.Client, bucketName string, objPath string) ([]byte, error) {
      bucketHandle := c.Bucket(bucketName)
      objectHandle := bucketHandle.Object(objPath)
    
      objectReader, err := objectHandle.NewReader(ctx)
      if err != nil {
        return nil, err
      }
      defer objectReader.Close()
    
      s, err := io.ReadAll(objectReader)
      if err != nil {
        return nil, err
      }
    
      return s, nil
    }
    
  3. 請確保所有當事人都能閱讀及稽核原始碼。

  4. 在 Cloud Shell 編輯器中建立名為 Dockerfile 的檔案,並在當中加入下列內容:

    # Compile the provided Go code to a statically linked binary
    FROM golang:latest AS build
    WORKDIR /build
    COPY salary.go .
    RUN go mod init salary
    RUN go get cloud.google.com/go/kms/apiv1 cloud.google.com/go/storage google.golang.org/api/option google.golang.org/genproto/googleapis/cloud/kms/v1
    RUN CGO_ENABLED=0 go build -trimpath
    
    # Build the workload container image
    FROM alpine:latest AS run
    WORKDIR /test
    COPY --from=build /build/salary /test/salary
    ENTRYPOINT ["/test/salary"]
    CMD []
    
    # Allow the workload to access the following environment variables
    LABEL "tee.launch_policy.allow_env_override"="\
    COLLAB_1_NAME,\
    COLLAB_2_NAME,\
    COLLAB_1_ENCRYPTED_SALARY,\
    COLLAB_2_ENCRYPTED_SALARY,\
    COLLAB_1_INPUT_BUCKET,\
    COLLAB_2_INPUT_BUCKET,\
    COLLAB_1_OUTPUT_BUCKET,\
    COLLAB_2_OUTPUT_BUCKET,\
    COLLAB_1_KEYRING_NAME,\
    COLLAB_2_KEYRING_NAME,\
    COLLAB_1_KEY_NAME,\
    COLLAB_2_KEY_NAME,\
    COLLAB_1_PROJECT_ID,\
    COLLAB_2_PROJECT_ID,\
    COLLAB_1_PROJECT_NUMBER,\
    COLLAB_2_PROJECT_NUMBER,\
    COLLAB_1_POOL_NAME,\
    COLLAB_2_POOL_NAME"
    

    這個 Dockerfile 會使用多階段建構程序,先編譯 Go 程式碼,然後將編譯後的程式碼複製到最終的工作負載容器。此外,您也可以在該工作負載容器中使用特定環境變數。這些環境變數的值稍後會對應至工作負載運作所需的特定資源。

  5. 按一下「Open Terminal」(開啟終端機) 切換回 Cloud Shell,或從「View」(檢視) 選單叫用 Cloud Shell 編輯器內建的終端機。

  6. 在 Artifact Registry 中建立 Docker 存放區:

    gcloud artifacts repositories create REPOSITORY_NAME \
        --repository-format=docker \
        --location=us
    
  7. 將 Artifact Registry 讀取者 (roles/artifactregistry.reader) 角色授予要執行工作負載的服務帳戶,以便從存放區讀取資料:

    gcloud artifacts repositories add-iam-policy-binding REPOSITORY_NAME \
        --location=us \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/artifactregistry.reader
    
  8. 更新 Docker 憑證,加入 us-docker.pkg.dev 網域名稱:

    gcloud auth configure-docker us-docker.pkg.dev
    
  9. 在終端機中輸入下列指令,從 Dockerfile 建立 Docker 映像檔:

    docker build -t \
        "us-docker.pkg.dev/ALEX_PROJECT_ID/\
    REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest" .
    
  10. 將 Docker 映像檔推送至 Artifact Registry:

    docker push \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME
    
  11. Docker 推送回應會列出映像檔的 SHA256 摘要,稍後授權工作負載時需要用到這個摘要。摘要內容類似下列範例:

    sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
    

    將映像檔摘要 (包括 sha256: 前置字串) 複製到可供參考的位置。您也可以在下列程式碼範例中輸入摘要,預先填寫本指南中需要該值的其餘程式碼範例:

    WORKLOAD_CONTAINER_IMAGE_DIGEST
    
  12. 授權使用 Docker 映像檔前,請確保所有當事人稽核該映像檔,並確認映像檔值得信賴。

授權工作負載

雙方核准工作負載後,Alex 和 Bola 必須將 Google Cloud 認證新增為工作負載身分集區的提供者。供應商會指定要使用的認證服務,以及工作負載必須符合的屬性,才能在 Alex 或 Bola 的資料上運作。如果惡意行為人變更 Docker 映像檔,或修改其他測量的屬性,工作負載就會遭到拒絕存取。

本指南使用屬性對應,根據映像檔摘要,直接授予工作負載資源存取權。不過,在其他情況下,您可能偏好使用服務帳戶模擬功能來存取資源。詳情請參閱「外部工作負載存取權」。

如要為 Alex 和 Bola 設定供應商,並符合必要條件,請完成下列步驟:

  1. 輸入下列指令,為 Alex 建立供應商:

    gcloud iam workload-identity-pools providers create-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=ALEX_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs\
    ::\"+assertion.submods.container.image_digest+\"\
    ::\"+assertion.submods.gce.project_number+\"\
    ::\"+assertion.submods.gce.instance_id,\
    attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  2. 取得 Alex 的專案編號,以用於下一個指令:

    gcloud projects describe ALEX_PROJECT_ID --format="value(projectNumber)"
    
  3. cloudkms.cryptoKeyDecrypter 角色授予 Alex 的供應商所定義的聯合身分,並指定 image_digest 屬性,這樣只有具有指定摘要的容器才能解密 KMS 金鑰:

    gcloud kms keys add-iam-policy-binding \
        "projects/ALEX_PROJECT_ID/locations/global/\
    keyRings/ALEX_KEYRING_NAME/\
    cryptoKeys/ALEX_KEY_NAME" \
        --member="principalSet://iam.googleapis.com/\
    projects/ALEX_PROJECT_NUMBER/locations/global/\
    workloadIdentityPools/ALEX_POOL_NAME/\
    attribute.image_digest/WORKLOAD_CONTAINER_IMAGE_DIGEST" \
        --role=roles/cloudkms.cryptoKeyDecrypter
    
  4. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  5. 輸入下列指令,為 Bola 建立供應商:

    gcloud iam workload-identity-pools providers create-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=BOLA_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs\
    ::\"+assertion.submods.container.image_digest+\"\
    ::\"+assertion.submods.gce.project_number+\"\
    ::\"+assertion.submods.gce.instance_id,\
    attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  6. 取得 Bola 的專案編號,以用於下一個指令:

    gcloud projects describe BOLA_PROJECT_ID --format="value(projectNumber)"
    
  7. cloudkms.cryptoKeyDecrypter 角色授予 Bola 提供者定義的聯合身分,並指定 image_digest 屬性,這樣只有具有指定摘要的工作負載容器才能解密其 KMS 金鑰:

    gcloud kms keys add-iam-policy-binding \
        "projects/BOLA_PROJECT_ID/locations/global/\
    keyRings/BOLA_KEYRING_NAME/\
    cryptoKeys/BOLA_KEY_NAME" \
        --member="principalSet://iam.googleapis.com/\
    projects/BOLA_PROJECT_NUMBER/locations/global/\
    workloadIdentityPools/BOLA_POOL_NAME/\
    attribute.image_digest/WORKLOAD_CONTAINER_IMAGE_DIGEST" \
        --role=roles/cloudkms.cryptoKeyDecrypter
    

測試工作負載

在 Alex 和 Bola 的 workload identity pool 中新增提供者,並備妥必要資源後,工作負載運算子就可以測試工作負載。

如要測試工作負載,請在 Bola 的專案中建立新的機密 VM 執行個體,並具備下列屬性:

在 Bola 的 Cloud Shell 中輸入下列指令,測試工作負載:

gcloud compute instances create WORKLOAD_VM_2_NAME \
    --confidential-compute-type=SEV \
    --shielded-secure-boot \
    --scopes=cloud-platform \
    --zone=us-west1-b \
    --maintenance-policy=MIGRATE \
    --min-cpu-platform="AMD Milan" \
    --image-project=confidential-space-images \
    --image-family=confidential-space-debug \
    --service-account=WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
    --metadata="^~^tee-image-reference=us-docker.pkg.dev/\
ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest\
~tee-container-log-redirect=true\
~tee-env-COLLAB_1_NAME=Alex\
~tee-env-COLLAB_2_NAME=Bola\
~tee-env-COLLAB_1_ENCRYPTED_SALARY=ALEX_ENCRYPTED_SALARY_FILE\
~tee-env-COLLAB_2_ENCRYPTED_SALARY=BOLA_ENCRYPTED_SALARY_FILE\
~tee-env-COLLAB_1_INPUT_BUCKET=ALEX_INPUT_BUCKET_NAME\
~tee-env-COLLAB_2_INPUT_BUCKET=BOLA_INPUT_BUCKET_NAME\
~tee-env-COLLAB_1_OUTPUT_BUCKET=ALEX_OUTPUT_BUCKET_NAME\
~tee-env-COLLAB_2_OUTPUT_BUCKET=BOLA_OUTPUT_BUCKET_NAME\
~tee-env-COLLAB_1_KEYRING_NAME=ALEX_KEYRING_NAME\
~tee-env-COLLAB_2_KEYRING_NAME=BOLA_KEYRING_NAME\
~tee-env-COLLAB_1_KEY_NAME=ALEX_KEY_NAME\
~tee-env-COLLAB_2_KEY_NAME=BOLA_KEY_NAME\
~tee-env-COLLAB_1_PROJECT_ID=ALEX_PROJECT_ID\
~tee-env-COLLAB_2_PROJECT_ID=BOLA_PROJECT_ID\
~tee-env-COLLAB_1_PROJECT_NUMBER=ALEX_PROJECT_NUMBER\
~tee-env-COLLAB_2_PROJECT_NUMBER=BOLA_PROJECT_NUMBER\
~tee-env-COLLAB_1_POOL_NAME=ALEX_POOL_NAME\
~tee-env-COLLAB_2_POOL_NAME=BOLA_POOL_NAME"

查看進度

如要查看 Bola 專案中工作負載的進度,請前往 Logs Explorer

前往「Logs explorer」(記錄檔探索工具)

如要只顯示私密空間記錄項目,請依下列記錄欄位 (如有) 篩選:

  • 資源類型:VM 執行個體

  • 執行個體 ID:VM 的執行個體 ID

  • 記錄名稱:confidential-space-launcher

如要重新整理記錄,請按一下「跳到現在時間」 。你也可以捲動至較早的結果,然後再次捲動至記錄結尾,載入最新項目。

查看結果

如果工作負載工作結束並傳回 0,表示沒有發生任何錯誤,現在可以檢查 Alex 和 Bola 輸出值 bucket 中的輸出內容:

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 列出結果值區中的所有檔案:

    gcloud storage ls gs://ALEX_OUTPUT_BUCKET_NAME
    
  3. 讀取列出的最新檔案,並將 ALEX_OUTPUT_CLOUD_STORAGE_PATH 替換為檔案路徑,包括 gs://

    gcloud storage cat ALEX_OUTPUT_CLOUD_STORAGE_PATH
    

    如果沒有檔案,請偵錯工作負載

  4. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  5. 列出結果值區中的所有檔案:

    gcloud storage ls gs://BOLA_OUTPUT_BUCKET_NAME
    
  6. 讀取列出的最新檔案,並將 BOLA_RESULTS_CLOUD_STORAGE_PATH 替換為檔案路徑,包括 gs://

    gcloud storage cat BOLA_RESULTS_CLOUD_STORAGE_PATH
    

    如果沒有檔案,請偵錯工作負載

  7. 成功讀取結果後,請停止 VM 執行個體:

    gcloud compute instances stop WORKLOAD_VM_2_NAME --zone=us-west1-b
    

Alex 和 Bola 讀取檔案後,就能知道誰的薪水較高,但彼此不會看到對方的薪資。

偵錯並重新啟動工作負載

機密空間環境包含許多部分,可能因設定錯誤而導致工作負載失敗。

與正式版機密空間映像檔不同,偵錯映像檔會在工作負載完成後,讓 VM 執行個體繼續運作。也就是說,如果記錄檔提供的資訊不足以解決問題,下一步就是透過 SSH 連線至 VM 執行個體,然後繼續偵錯

偵錯完成後,請停止 VM 執行個體:

gcloud compute instances stop WORKLOAD_VM_2_NAME --zone=us-west1-b

如要在偵錯環境中執行工作負載,請再次啟動 VM:

gcloud compute instances start WORKLOAD_VM_2_NAME --zone=us-west1-b

強化正式版環境

成功測試工作負載後,即可強化 Confidential Space 環境,以利部署至正式環境。Alex 和 Bola 需要在供應商中加入support_attributes斷言,確認工作負載使用的是正式版 Confidential Space 映像檔:

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 輸入下列指令,更新 Alex 的供應商:

    gcloud iam workload-identity-pools providers update-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=ALEX_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs\
    ::\"+assertion.submods.container.image_digest+\"\
    ::\"+assertion.submods.gce.project_number+\"\
    ::\"+assertion.submods.gce.instance_id,\
    attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  3. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  4. 輸入下列指令,更新 Bola 的供應商:

    gcloud iam workload-identity-pools providers update-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=BOLA_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs\
    ::\"+assertion.submods.container.image_digest+\"\
    ::\"+assertion.submods.gce.project_number+\"\
    ::\"+assertion.submods.gce.instance_id,\
    attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    

部署正式環境工作負載

Bola 需要建立另一個 VM 執行個體來執行實際工作環境工作負載。與測試工作負載相比,以下項目有所不同:

  • 這個 OS 是以正式版 Confidential Space 映像檔為基礎,這項設定會停用 SSH,且 VM 執行個體會在工作負載完成後停止。

  • 記錄重新導向已移除。Cloud Logging 只會顯示不含機密資訊的基本記錄。

在 Bola 的 Cloud Shell 中輸入下列指令,部署正式版工作負載:

gcloud compute instances create WORKLOAD_VM_NAME \
    --confidential-compute-type=SEV \
    --shielded-secure-boot \
    --scopes=cloud-platform \
    --zone=us-west1-b \
    --maintenance-policy=MIGRATE \
    --image-project=confidential-space-images \
    --image-family=confidential-space \
    --service-account=WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
    --metadata="^~^tee-image-reference=us-docker.pkg.dev/\
ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest\
~tee-env-COLLAB_1_NAME=Alex\
~tee-env-COLLAB_2_NAME=Bola\
~tee-env-COLLAB_1_ENCRYPTED_SALARY=ALEX_ENCRYPTED_SALARY_FILE\
~tee-env-COLLAB_2_ENCRYPTED_SALARY=BOLA_ENCRYPTED_SALARY_FILE\
~tee-env-COLLAB_1_INPUT_BUCKET=ALEX_INPUT_BUCKET_NAME\
~tee-env-COLLAB_2_INPUT_BUCKET=BOLA_INPUT_BUCKET_NAME\
~tee-env-COLLAB_1_OUTPUT_BUCKET=ALEX_OUTPUT_BUCKET_NAME\
~tee-env-COLLAB_2_OUTPUT_BUCKET=BOLA_OUTPUT_BUCKET_NAME\
~tee-env-COLLAB_1_KEYRING_NAME=ALEX_KEYRING_NAME\
~tee-env-COLLAB_2_KEYRING_NAME=BOLA_KEYRING_NAME\
~tee-env-COLLAB_1_KEY_NAME=ALEX_KEY_NAME\
~tee-env-COLLAB_2_KEY_NAME=BOLA_KEY_NAME\
~tee-env-COLLAB_1_PROJECT_ID=ALEX_PROJECT_ID\
~tee-env-COLLAB_2_PROJECT_ID=BOLA_PROJECT_ID\
~tee-env-COLLAB_1_PROJECT_NUMBER=ALEX_PROJECT_NUMBER\
~tee-env-COLLAB_2_PROJECT_NUMBER=BOLA_PROJECT_NUMBER\
~tee-env-COLLAB_1_POOL_NAME=ALEX_POOL_NAME\
~tee-env-COLLAB_2_POOL_NAME=BOLA_POOL_NAME"

查看進度查看結果的方式,與測試工作負載時相同。

生產工作負載完成後,VM 執行個體就會停止。如要查看不同結果,您可以變更薪資、重新加密、重新上傳至對應的 Cloud Storage 值區,然後重新啟動 VM 執行個體,再次執行工作負載:

gcloud compute instances start WORKLOAD_VM_NAME --zone=us-west1-b

清除所用資源

如要移除本指南中建立的資源,請完成下列操作說明。

清除 Alex 的資源

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 刪除 Alex 的工作負載身分集區:

    gcloud iam workload-identity-pools delete ALEX_POOL_NAME \
        --location=global
    
  3. 刪除 Alex 的 Cloud Storage bucket:

    gcloud storage rm gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_OUTPUT_BUCKET_NAME --recursive
    
  4. 刪除 Alex 的薪資檔案、Go 程式碼和 Dockerfile

    rm ALEX_SALARY.txt \
        ALEX_ENCRYPTED_SALARY_FILE \
        salary.go \
        Dockerfile
    
  5. 選用:停用銷毀 Alex 的 Cloud Key Management Service 金鑰。

  6. 選用: 關閉 Alex 的專案

清除 Bola 的資源

  1. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  2. 刪除執行測試工作流程的 VM:

    gcloud compute instances delete WORKLOAD_VM_2_NAME --zone=us-west1-b
    
  3. 刪除執行正式版工作流程的 VM:

    gcloud compute instances delete WORKLOAD_VM_NAME --zone=us-west1-b
    
  4. 刪除執行工作負載的服務帳戶:

    gcloud iam service-accounts delete \
        WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com
    
  5. 刪除 Bola 的工作負載身分集區:

    gcloud iam workload-identity-pools delete BOLA_POOL_NAME \
        --location=global
    
  6. 刪除 Bola 的 Cloud Storage bucket:

    gcloud storage rm gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_OUTPUT_BUCKET_NAME --recursive
    
  7. 刪除 Bola 的薪資檔案:

    rm BOLA_SALARY.txt \
        BOLA_ENCRYPTED_SALARY_FILE
    
  8. 選用:停用銷毀 Bola 的 Cloud Key Management Service 金鑰。

  9. 選用: 關閉 Bola 的專案