创建您的第一个 Confidential Space 环境


在本指南中,Alex 和 Bola 想在不向对方透露薪资数目的情况下找出谁的薪水较高。他们决定使用 Confidential Space 进行数据保密,并同意担任以下角色

  • Alex:数据协作者、工作负载作者

  • Bola:数据协作者、工作负载操作员

这种安排旨在尽可能简化本指南。不过,工作负载作者和操作员可以完全独立于数据协作者,并且您可以拥有任意数量的协作者。

准备工作

本指南演示了在单个组织中使用单个账号访问多个项目的 Confidential Space 场景,因此您可以体验整个流程。在生产部署中,协作者、工作负载作者和工作负载操作员各自在独立的组织中拥有单独的账号和专属项目,彼此无法访问,并将他们的机密数据分开。

Confidential Space 可与许多 Google Cloud服务交互,以生成其结果,包括但不限于:

本指南使用所有这些功能并假设对其有基本的了解。

必需的 API

您必须在指定项目中启用以下 API,才能完成本指南。

API 名称 API 标题 在这些项目中启用
cloudkms.googleapis.com Cloud KMS 数据协作者(Alex 和 Bola 的项目)
iamcredentials.googleapis.com IAM 服务账号凭据 API

数据协作者(Alex 的项目)

在本指南中,只有 Alex 需要启用此 API。但是,如果涉及两方以上,则需要为不托管工作负载服务账号的每个数据协作者项目启用 IAM Service Account Credentials API。

artifactregistry.googleapis.com Artifact Registry 工作负载作者(Alex 的项目)
compute.googleapis.com Compute Engine 工作负载操作员(Bola 的项目)
confidentialcomputing.googleapis.com 机密计算 工作负载操作员(Bola 的项目)

所需的角色

如需获得完成本指南所需的权限,请让您的管理员为您授予项目的以下 IAM 角色:

  • 数据协作者(Alex 和 Bola)的 Cloud KMS Admin (roles/cloudkms.admin)。
  • 数据协作者(Alex 和 Bola)的 IAM Workload Identity Pool Admin (roles/iam.workloadIdentityPoolAdmin)。
  • 数据协作者(Alex 和 Bola)的 Service Usage Admin (roles/serviceusage.serviceUsageAdmin)。
  • 数据协作者(Alex 和 Bola)的 Service Account Admin (roles/iam.serviceAccountAdmin)。
  • 数据协作者(Alex 和 Bola)和工作负载操作员 (Bola) 的 Storage Admin (roles/storage.admin)。
  • 工作负载操作员 (Bola) 的 Compute Admin (roles/compute.admin)。
  • 工作负载操作员 (Bola) 的 Security Admin (roles/securityAdmin)。
  • 工作负载作者 (Alex) 的 Artifact Registry Administrator (roles/artifactregistry.admin)。

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

设置数据协作者资源

Alex 和 Bola 都需要独立的项目,其中包含以下资源:

  • 机密数据本身。

  • 用于加密该数据并进行保密的加密密钥。

  • 用于存储加密数据的 Cloud Storage 存储桶。

  • 此服务账号有权访问加密密钥,因此可以解密机密数据。

  • 此工作负载身份池与该服务账号相关联。处理机密数据的工作负载使用池来模拟服务账号并检索未加密的数据。

首先,请转到 Google Cloud 控制台:

转到 Google Cloud 控制台

设置 Alex 的资源

如需为 Alex 设置资源,请按照以下说明操作。

  1. 点击 激活 Cloud Shell
  2. 在 Cloud Shell 中,输入以下命令并将 ALEX_PROJECT_ID 替换为您指定的名称,为 Alex 创建项目:

    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. 创建工作负载身份池,然后使用 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 存储桶,并创建另一个存储桶以用于存储结果:

    gcloud storage buckets create gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_RESULTS_BUCKET_NAME
  11. 创建一个仅包含 Alex 薪资数目的文件:

    echo 123456 > ALEX_SALARY.txt
  12. 加密该文件,然后将其上传到 Alex 的存储桶:

    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_PROJECT_ID 替换为您指定的名称,为 Bola 创建项目:

    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
  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. 创建工作负载身份池,然后使用 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 存储桶,并创建另一个存储桶以用于存储结果:

    gcloud storage buckets create gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_RESULTS_BUCKET_NAME
  10. 创建一个仅包含 Bola 薪资数目的文件:

    echo 111111 > BOLA_SALARY.txt
  11. 加密该文件,然后将其上传到 Bola 的存储桶:

    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 角色来模拟该服务账号,稍后必须借助该角色才能创建工作负载虚拟机:

    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 存储桶的项目具有 Storage Admin (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 Reader (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 Editor,然后创建一个名为 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. 点击终端 > 新终端,在 Cloud Shell Editor 中打开终端。

  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 Editor 中创建一个名为 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 需要将 Confidential Space 证明验证程序服务作为提供方添加到其工作负载身份池。这样,附加到工作负载的服务账号就可以模拟关联到其池的服务账号并访问其数据,前提是满足某些属性条件。这意味着特性条件充当证明政策。

本指南中使用的特性条件如下:

  • 正在运行的 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 的工作负载身份池以及所需的资源到位后,工作负载操作员便可以运行该工作负载。

如需部署工作负载,请在 Bola 的项目中创建具有以下属性的新机密虚拟机实例:

  • AMD SEV 或 Intel TDX 机密虚拟机实例的受支持的配置

  • 基于 Confidential Space 映像的操作系统。

  • 安全启动功能已启用。

  • 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 \
    --min-cpu-platform="AMD Milan" \
    --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 项目中工作负载的进度。

转到日志浏览器

如需查找 Confidential Space 日志,请按以下日志字段(如果有)过滤:

  • 资源类型:虚拟机实例

  • 实例 ID:虚拟机的实例 ID

  • 日志名称:confidential-space-launcher

如需刷新日志,请点击跳至现在

工作负载完成后,虚拟机实例会停止。如果要更改加密的薪资文件并再次部署工作负载,只需启动现有虚拟机:

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

调试工作负载

您可以使用日志浏览器来排查一些问题,比如资源未正确设置,或是提供方的属性条件与 Confidential Space 工作负载所做的声明不匹配。

为此,您需要进行以下更改:

  • 更新 Alex 和 Bola 的工作负载身份池提供方,以移除 support_attributes 断言。您需要使用 Confidential Space 调试映像才能执行更深入的问题排查,并且该映像没有需要验证的支持属性。

  • 使用 Confidential Space 调试映像创建工作负载虚拟机,并设置虚拟机元数据将 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 调试映像创建新虚拟机,并在元数据中将 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"
    

与生产映像不同,调试映像即便在工作负载完成后仍会让虚拟机保持运行。这意味着您可以使用 SSH 连接到虚拟机以继续调试。

查看结果

工作负载成功完成后,Alex 和 Bola 可以在各自的结果存储桶中查看结果:

  1. 切换到 Alex 的项目:

    gcloud config set project ALEX_PROJECT_ID
    
  2. 列出其结果存储桶中的所有文件:

    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,列出其结果存储桶中的所有文件:

    gcloud storage ls gs://BOLA_RESULTS_BUCKET_NAME
    

    然后读取最新文件:

    gcloud storage cat gs://BOLA_RESULTS_BUCKET_NAME/BOLA_RESULTS_FILE_NAME
    

通过读取文件,Alex 和 Bola 会发现谁的收入更多,却不泄露自己的薪资。

清理

如需移除在本指南中创建的资源,请按照以下说明操作。

清理 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 存储桶:

    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. 删除工作负载虚拟机:

    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 存储桶:

    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 的项目