建立第一個機密空間環境


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

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

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

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

事前準備

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

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

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

必要 API

您必須在指定專案中啟用下列 API,才能完成本指南。

API 名稱 API 名稱 在這些專案中啟用
cloudkms.googleapis.com Cloud KMS 資料協作者 (Alex 和 Bola 的專案)
iamcredentials.googleapis.com IAM Service Account Credentials API

資料協作者 (Alex 和 Bola 的專案)

artifactregistry.googleapis.com Artifact Registry 工作負載作者 (Alex 的專案)
compute.googleapis.com Compute Engine 工作負載運算子 (Bola 的專案)
confidentialcomputing.googleapis.com 機密運算 工作負載運算子 (Bola 的專案)

必要的角色

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

  • 資料協作者 (Alex 和 Bola) 的 Cloud KMS 管理員 (roles/cloudkms.admin)。
  • 資料協作者 (Alex 和 Bola) 的身分與存取權管理 Workload Identity 集區管理員 (roles/iam.workloadIdentityPoolAdmin)。
  • 資料協作者 (Alex 和 Bola) 的服務使用情形管理員 (roles/serviceusage.serviceUsageAdmin)。
  • 資料協作者 (Alex 和 Bola) 的服務帳戶管理員 (roles/iam.serviceAccountAdmin)。
  • 資料協作者 (Alex 和 Bola) 和工作負載運算子 (Bola) 的 Storage 管理員 (roles/storage.admin)。
  • 工作負載運算子 (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 cloudkms.googleapis.com artifactregistry.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. 建立服務帳戶,供工作負載稍後用來解密資料:

    gcloud iam service-accounts create ALEX_SERVICE_ACCOUNT_NAME
  8. cloudkms.cryptoKeyDecrypter 角色授予服務帳戶,讓服務帳戶可以使用您剛建立的加密金鑰解密資料:

    gcloud kms keys add-iam-policy-binding \
        projects/ALEX_PROJECT_ID/locations/global/keyRings/ALEX_KEYRING_NAME/cryptoKeys/ALEX_KEY_NAME \
        --member=serviceAccount:ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/cloudkms.cryptoKeyDecrypter
  9. 建立 workload identity pool,然後使用 iam.workloadIdentityUser 角色將服務帳戶連結至該集區:

    gcloud iam workload-identity-pools create ALEX_POOL_NAME \
        --location=global
    gcloud iam service-accounts add-iam-policy-binding \
        ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com \
        --member="principalSet://iam.googleapis.com/projects/"$(gcloud projects describe ALEX_PROJECT_ID \
            --format="value(projectNumber)")"/locations/global/workloadIdentityPools/ALEX_POOL_NAME/*" \
        --role=roles/iam.workloadIdentityUser
  10. 建立 Cloud Storage bucket 來儲存輸入資料,並建立另一個 bucket 來儲存結果:

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

    echo 123456 > ALEX_SALARY.txt
  12. 加密檔案,然後上傳至 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 iamcredentials.googleapis.com compute.googleapis.com confidentialcomputing.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. 建立服務帳戶,供工作負載稍後用來解密資料:

    gcloud iam service-accounts create BOLA_SERVICE_ACCOUNT_NAME
  7. cloudkms.cryptoKeyDecrypter 角色授予服務帳戶,讓服務帳戶可以使用您剛建立的加密金鑰解密資料:

    gcloud kms keys add-iam-policy-binding \
        projects/BOLA_PROJECT_ID/locations/global/keyRings/BOLA_KEYRING_NAME/cryptoKeys/BOLA_KEY_NAME \
        --member=serviceAccount:BOLA_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/cloudkms.cryptoKeyDecrypter
  8. 建立 workload identity pool,然後使用 iam.workloadIdentityUser 角色將服務帳戶連結至該集區:

    gcloud iam workload-identity-pools create BOLA_POOL_NAME \
        --location=global
    gcloud iam service-accounts add-iam-policy-binding \
        BOLA_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --member="principalSet://iam.googleapis.com/projects/"$(gcloud projects describe BOLA_PROJECT_ID \
            --format="value(projectNumber)")"/locations/global/workloadIdentityPools/BOLA_POOL_NAME/*" \
        --role=roles/iam.workloadIdentityUser
  9. 建立 Cloud Storage bucket 來儲存輸入資料,並建立另一個 bucket 來儲存結果:

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

    echo 111111 > BOLA_SALARY.txt
  11. 加密檔案,然後上傳至 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

為工作負載建立服務帳戶

除了 Alex 和 Bola 設定的服務帳戶 (用於解密資料) 之外,還需要另一個服務帳戶來執行工作負載。由於服務帳戶可用於解密及處理機密資料,因此只有擁有者才能查看資料。

在本指南中,Bola 負責操作及執行工作負載,但任何人 (包括第三方) 都能擔任這些角色。

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

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

    gcloud iam service-accounts create WORKLOAD_SERVICE_ACCOUNT_NAME
    
  2. 授予 Bola iam.serviceAccountUser 角色,模擬服務帳戶,這是必要步驟,因為他們稍後需要建立工作負載 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://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://ALEX_RESULTS_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_RESULTS_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. 在 Artifact Registry 中建立 Docker 存放區:

    gcloud artifacts repositories create REPOSITORY_NAME \
        --repository-format=docker \
        --location=us
    
  3. 將 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
    
  4. 按一下「開啟編輯器」開啟 Cloud Shell 編輯器,然後建立名為 salary.go 的新檔案。將下列程式碼複製到檔案中,然後儲存檔案:

    // READ ME FIRST: Before compiling, customize the details in the USER VARIABLES
    // SECTION starting at line 30.
    
    package main
    
    import (
      kms "cloud.google.com/go/kms/apiv1"
      "cloud.google.com/go/storage"
      "context"
      "fmt"
      "google.golang.org/api/option"
      kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
      "io/ioutil"
      "strconv"
      "strings"
      "time"
    )
    
    type collaborator struct {
      name         string
      wipName      string
      sa           string
      keyName      string
      inputBucket  string
      inputFile    string
      outputBucket string
      outputFile   string
    }
    
    // ============================
    // START USER VARIABLES SECTION
    // You need to customize this section, replacing each const's value with your
    // own.
    
    // To get a project number, use the following command, and substitute
    // <PROJECT_ID> for the data collaborator's project ID.
    // gcloud projects describe <PROJECT_ID> --format="value(projectNumber)"
    
    // Alex's values
    const collaborator1Name string = "Alex"                // Alex's name
    const collaborator1EncryptedSalaryFileName string = "" // The name of Alex's encrypted salary file
    const collaborator1BucketInputName string = ""         // The name of the storage bucket that contains Alex's encrypted salary file
    const collaborator1BucketOutputName string = ""        // The name of the storage bucket to store Alex's results in
    const collaborator1BucketOutputFileName string = ""    // The name of Alex's output file that contains the results
    const collaborator1KMSKeyringName string = ""          // Alex's Key Management Service key ring
    const collaborator1KMSKeyName string = ""              // Alex's Key Management Service key
    const collaborator1ProjectName string = ""             // Alex's project ID
    const collaborator1ProjectNumber string = ""           // Alex's project number
    const collaborator1PoolName string = ""                // Alex's workload identity pool name
    const collaborator1ServiceAccountName string = ""      // The name of Alex's service account that can decrypt their salary
    
    // Bola's values
    const collaborator2Name string = "Bola"                // Bola's name
    const collaborator2EncryptedSalaryFileName string = "" // The name of Bola's encrypted salary file
    const collaborator2BucketInputName string = ""         // The name of the storage bucket that contains Bola's encrypted salary file
    const collaborator2BucketOutputName string = ""        // The name of the storage bucket to store Bola's results in
    const collaborator2BucketOutputFileName string = ""    // The name of Bola's output file that contains the results
    const collaborator2KMSKeyringName string = ""          // Bola's Key Management Service key ring
    const collaborator2KMSKeyName string = ""              // Bola's Key Management Service key
    const collaborator2ProjectName string = ""             // Bola's project ID
    const collaborator2ProjectNumber string = ""           // Bola's project number
    const collaborator2PoolName string = ""                // Bola's workload identity pool name
    const collaborator2ServiceAccountName string = ""      // The name of Bola's service account that can decrypt their salary
    
    // END USER VARIABLES SECTION
    // ==========================
    
    var collaborators = [2]collaborator{
      {
        collaborator1Name,
        "projects/" + collaborator1ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator1PoolName + "/providers/attestation-verifier",
        collaborator1ServiceAccountName + "@" + collaborator1ProjectName + ".iam.gserviceaccount.com",
        "projects/" + collaborator1ProjectName + "/locations/global/keyRings/" + collaborator1KMSKeyringName + "/cryptoKeys/" + collaborator1KMSKeyName,
        collaborator1BucketInputName,
        collaborator1EncryptedSalaryFileName,
        collaborator1BucketOutputName,
        collaborator1BucketOutputFileName,
      },
      {
        collaborator2Name,
        "projects/" + collaborator2ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator2PoolName + "/providers/attestation-verifier",
        collaborator2ServiceAccountName + "@" + collaborator2ProjectName + ".iam.gserviceaccount.com",
        "projects/" + collaborator2ProjectName + "/locations/global/keyRings/" + collaborator2KMSKeyringName + "/cryptoKeys/" + collaborator2KMSKeyName,
        collaborator2BucketInputName,
        collaborator2EncryptedSalaryFileName,
        collaborator2BucketOutputName,
        collaborator2BucketOutputFileName,
      },
    }
    
    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"
            },
            "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken"
            }`
    
    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 = "earns same\n"
      }
    
      now := time.Now()
      for _, cw := range collaborators {
        outputWriter := storageClient.Bucket(cw.outputBucket).Object(fmt.Sprintf("%s-%d", cw.outputFile, 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.sa, 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, trustedServiceAccountEmail, wippro string, encryptedData []byte) ([]byte, error) {
      cc := fmt.Sprintf(credentialConfig, wippro, trustedServiceAccountEmail)
      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 := ioutil.ReadAll(objectReader)
      if err != nil {
        return nil, err
      }
    
      return s, nil
    }
    
  5. 修改原始碼中的 USER VARIABLES SECTION,並根據程式碼註解中的說明,將空白的 const 值替換為相關資源名稱。如果您已按照本指南編輯預留位置變數 (如 ALEX_PROJECT_ID),下列程式碼範例會包含這些值,您可以複製並貼上,覆寫現有程式碼:

    // Alex's values
    const collaborator1Name string = "Alex"                                            // Alex's name
    const collaborator1EncryptedSalaryFileName string = "ALEX_ENCRYPTED_SALARY_FILE" // The name of Alex's encrypted salary file
    const collaborator1BucketInputName string = "ALEX_INPUT_BUCKET_NAME"             // The name of the storage bucket that contains Alex's encrypted salary file
    const collaborator1BucketOutputName string = "ALEX_RESULTS_BUCKET_NAME"          // The name of the storage bucket to store Alex's results in
    const collaborator1BucketOutputFileName string = "ALEX_RESULTS_FILE_NAME"        // The name of Alex's output file that contains the results
    const collaborator1KMSKeyringName string = "ALEX_KEYRING_NAME"                   // Alex's Key Management Service key ring
    const collaborator1KMSKeyName string = "ALEX_KEY_NAME"                           // Alex's Key Management Service key
    const collaborator1ProjectName string = "ALEX_PROJECT_ID"                        // Alex's project ID
    const collaborator1ProjectNumber string = "ALEX_PROJECT_NUMBER"                  // Alex's project number
    const collaborator1PoolName string = "ALEX_POOL_NAME"                            // Alex's workload identity pool name
    const collaborator1ServiceAccountName string = "ALEX_SERVICE_ACCOUNT_NAME"       // The name of Alex's service account that can decrypt their salary
    
    // Bola's values
    const collaborator2Name string = "Bola"                                            // Bola's name
    const collaborator2EncryptedSalaryFileName string = "BOLA_ENCRYPTED_SALARY_FILE" // The name of Bola's encrypted salary file
    const collaborator2BucketInputName string = "BOLA_INPUT_BUCKET_NAME"             // The name of the storage bucket that contains Bola's encrypted salary file
    const collaborator2BucketOutputName string = "BOLA_RESULTS_BUCKET_NAME"          // The name of the storage bucket to store Bola's results in
    const collaborator2BucketOutputFileName string = "BOLA_RESULTS_FILE_NAME"        // The name of Bola's output file that contains the results
    const collaborator2KMSKeyringName string = "BOLA_KEYRING_NAME"                   // Bola's Key Management Service key ring
    const collaborator2KMSKeyName string = "BOLA_KEY_NAME"                           // Bola's Key Management Service key
    const collaborator2ProjectName string = "BOLA_PROJECT_ID"                        // Bola's project ID
    const collaborator2ProjectNumber string = "BOLA_PROJECT_NUMBER"                  // Bola's project number
    const collaborator2PoolName string = "BOLA_POOL_NAME"                            // Bola's workload identity pool name
    const collaborator2ServiceAccountName string = "BOLA_SERVICE_ACCOUNT_NAME"       // The name of Bola's service account that can decrypt their salary
    

    請務必一併更新 Alex 和 Bola 的專案編號。您可以使用下列指令擷取這些值:

    gcloud projects describe PROJECT_ID --format="value(projectNumber)"
    
  6. 請確保所有當事人已閱讀並稽核原始碼。

  7. 依序點選「Terminal」(終端機) >「New Terminal」(新增終端機),在 Cloud Shell 編輯器中開啟終端機。

  8. 在終端機中輸入下列指令,設定 Go 環境:

    go mod init salary
    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
    
  9. 輸入下列指令,將原始碼編譯為靜態連結的二進位檔:

    CGO_ENABLED=0 go build -trimpath
    
  10. 在 Cloud Shell 編輯器中建立名為 Dockerfile 的檔案,並在當中加入下列內容:

    FROM alpine:latest
    WORKDIR /test
    COPY salary /test
    ENTRYPOINT ["/test/salary"]
    CMD []
    
  11. 更新 Docker 憑證,加入 us-docker.pkg.dev 網域名稱:

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

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

    docker push \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME
    
  14. 從 Docker 推送回應中,將 Docker 映像檔的摘要 (包括 sha256: 前置字元) 複製到安全的地方,以供日後使用。

  15. 請確保所有當事人稽核 Docker 映像檔,並驗證其是否值得信任,再授權使用。

授權工作負載

雙方核准工作負載後,Alex 和 Bola 必須將 Google Cloud 驗證新增為工作負載身分集區的提供者。這樣一來,附加至工作負載的服務帳戶就能模擬連結至集區的服務帳戶,並存取這些帳戶的資料,前提是符合特定屬性條件。也就是說,屬性條件會做為認證政策。

本指南使用的屬性條件如下:

  • 正在執行的 Docker 映像檔摘要

  • 執行工作負載的服務帳戶電子郵件地址

如果惡意行為人變更 Docker 映像檔,或將其他服務帳戶附加至工作負載,工作負載就無法存取 Alex 或 Bola 的資料。

如要查看可用的屬性條件,請參閱「認證聲明」。

如要為 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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE' \
    && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  2. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  3. 輸入下列指令,為 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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE' \
    && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    

部署工作負載

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

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

  • AMD SEV 或 Intel TDX 機密 VM 執行個體的支援設定

  • 以 Confidential Space 映像檔為基礎的 OS。

  • 已啟用安全啟動功能。

  • Alex 先前建立的 Docker 映像檔。

  • 執行工作負載的服務帳戶。

在 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"

前往記錄檔瀏覽器,即可查看 Bola 專案的工作負載進度。

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

如要尋找機密空間記錄,請依下列記錄欄位 (如有) 篩選:

  • 資源類型:VM 執行個體

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

  • 記錄檔名稱:confidential-space-launcher

如要重新整理記錄,請按一下「跳到現在時間」

工作負載完成後,VM 執行個體就會停止運作。如要變更加密薪資檔案並再次部署工作負載,只需啟動現有 VM 即可:

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

查看結果

工作負載順利完成後,Alex 和 Bola 都能在各自的結果 bucket 中查看結果:

  1. 切換至 Alex 的專案:

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

    gcloud storage ls gs://ALEX_RESULTS_BUCKET_NAME
    

    然後讀取最新檔案:

    gcloud storage cat gs://ALEX_RESULTS_BUCKET_NAME/ALEX_RESULTS_FILE_NAME
    
  3. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  4. 針對 Bola,列出結果 bucket 中的所有檔案:

    gcloud storage ls gs://BOLA_RESULTS_BUCKET_NAME
    

    然後讀取最新檔案:

    gcloud storage cat gs://BOLA_RESULTS_BUCKET_NAME/BOLA_RESULTS_FILE_NAME
    

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

偵錯工作負載

您可以使用「記錄探索器」排解問題,例如資源未正確設定,或供應商中的屬性條件與 Confidential Space 工作負載提出的聲明不符。

如要這麼做,請進行下列變更:

  • 更新 Alex 和 Bola 的工作負載身分集區提供者,移除 support_attributes 聲明。您需要使用 Confidential Space 偵錯映像檔執行更深入的疑難排解,但該映像檔沒有可供驗證的支援屬性。

  • 使用 Confidential Space 偵錯映像檔建立工作負載 VM,並將 VM 中繼資料設為將 STDOUTSTDERR 重新導向至 Cloud Logging,以擷取工作負載的所有輸出內容。

如要進行變更,請完成下列步驟:

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 更新 Alex 的提供者,移除 support_attributes 判斷:

    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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  3. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  4. 更新 Bola 的供應商,移除 support_attributes 聲明:

    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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  5. 使用 Confidential Space 偵錯映像檔建立新的 VM,並在其中繼資料中tee-container-log-redirect設為 true

    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"
    

與正式版映像檔不同,偵錯映像檔會在工作負載完成後,讓 VM 繼續運作。也就是說,您可以使用 SSH 連線至 VM,繼續進行偵錯。

清除所用資源

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

清除 Alex 的資源

  1. 切換至 Alex 的專案:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 刪除用於解密 Alex 資料的服務帳戶:

    gcloud iam service-accounts delete \
        ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com
    
  3. 刪除 Alex 的工作負載身分集區:

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

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

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

  7. 選用: 關閉 Alex 的專案

清除 Bola 的資源

  1. 切換至 Bola 的專案:

    gcloud config set project BOLA_PROJECT_ID
    
  2. 刪除工作負載 VM:

    gcloud compute instances delete WORKLOAD_VM_NAME --zone=us-west1-b
    
  3. 刪除解密 Bola 資料的服務帳戶,以及執行工作負載的服務帳戶:

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

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

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

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

  8. 選用: 關閉 Bola 的專案