Crie o seu primeiro ambiente de espaço confidencial


Neste guia, o Alex e a Bola querem saber quem tem o salário mais elevado sem revelarem os números um ao outro. Decidem usar o espaço confidencial para manter os respetivos dados confidenciais e aceitam assumir as seguintes funções:

  • Alex: colaborador de dados, autor de cargas de trabalho

  • Bola: colaborador de dados, operador de carga de trabalho

Esta disposição foi concebida para manter as coisas o mais simples possível para este guia. No entanto, é possível que o autor e o operador da carga de trabalho sejam totalmente independentes dos colaboradores de dados, e pode ter quantos colaboradores quiser.

Antes de começar

Este guia demonstra um cenário do espaço confidencial com uma única conta numa única organização com acesso a vários projetos, para que possa experimentar todo o processo. Numa implementação de produção, os colaboradores, os autores de cargas de trabalho e os operadores de cargas de trabalho têm contas separadas e os seus próprios projetos contidos em organizações discretas, inacessíveis entre si e mantendo os respetivos dados confidenciais separados.

O espaço confidencial pode interagir com muitos dos serviços do Google Cloudpara produzir os respetivos resultados, incluindo, entre outros:

Este guia usa e pressupõe uma compreensão básica de todas estas funcionalidades.

APIs necessárias

Tem de ativar as seguintes APIs nos projetos especificados para poder concluir este guia.

Nome da API Título da API Ative nestes projetos
cloudkms.googleapis.com Cloud KMS Colaboradores de dados (projetos de Alex e Bola)
iamcredentials.googleapis.com API IAM Service Account Credentials

Colaboradores de dados (projetos de Alex e Bola)

artifactregistry.googleapis.com Artifact Registry Autor da carga de trabalho (projeto do Alex)
compute.googleapis.com Compute Engine Operador de cargas de trabalho (projeto de Bola)
confidentialcomputing.googleapis.com Computação confidencial Operador de cargas de trabalho (projeto de Bola)

Funções necessárias

Para receber as autorizações de que precisa para concluir este guia, peça ao seu administrador para lhe conceder as seguintes funções da IAM no projeto:

  • Administrador do Cloud KMS (roles/cloudkms.admin) para os colaboradores de dados (Alex e Bola).
  • Administrador do Workload Identity Pool da IAM (roles/iam.workloadIdentityPoolAdmin) para os colaboradores de dados (Alex e Bola).
  • Administrador de utilização de serviços (roles/serviceusage.serviceUsageAdmin) para os colaboradores de dados (Alex e Bola).
  • Administrador de armazenamento (roles/storage.admin) para os colaboradores de dados (Alex e Bola) e o operador da carga de trabalho (Bola).
  • Administrador da conta de serviço (roles/iam.serviceAccountAdmin) para o operador da carga de trabalho (Bola).
  • Administrador de computação (roles/compute.admin) para o operador de cargas de trabalho (Bola).
  • Administrador de segurança (roles/securityAdmin) para o operador da carga de trabalho (Bola).
  • Administrador do Artifact Registry (roles/artifactregistry.admin) para o autor da carga de trabalho (Alex).

Para mais informações sobre a atribuição de funções, consulte o artigo Faça a gestão do acesso a projetos, pastas e organizações.

Também pode conseguir as autorizações necessárias através de funções personalizadas ou outras funções predefinidas.

Configure recursos de colaborador de dados

O Alex e a Bola precisam de projetos independentes que contenham os seguintes recursos:

  • Os próprios dados confidenciais.

  • Uma chave de encriptação para encriptar esses dados e mantê-los confidenciais.

  • Um contentor do Cloud Storage para armazenar os dados encriptados.

  • Um Workload Identity Pool. A carga de trabalho que processa os dados confidenciais usa o conjunto para aceder aos dados privados e desencriptá-los.

Para começar, aceda à Google Cloud consola:

Aceda à Google Cloud consola

Configure os recursos do Alex

Para configurar os recursos para o Alex, conclua as seguintes instruções.

  1. Clique em Ativar Cloud Shell.
  2. No Cloud Shell, introduza o seguinte comando para criar um projeto para Alex, substituindo ALEX_PROJECT_ID por um nome à sua escolha:

    gcloud projects create ALEX_PROJECT_ID
  3. Mude para o projeto recém-criado:

    gcloud config set project ALEX_PROJECT_ID
  4. Se ainda não o fez, ative as APIs que o Alex requer como colaborador de dados e autor de cargas de trabalho:

    gcloud services enable cloudkms.googleapis.com artifactregistry.googleapis.com iamcredentials.googleapis.com
  5. Crie um conjunto de chaves e uma chave de encriptação com o 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. Conceda à Alex a função cloudkms.cryptoKeyEncrypter para que possa usar a chave de encriptação criada recentemente para encriptar dados:

    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. Crie o Workload Identity Pool do Alex:

    gcloud iam workload-identity-pools create ALEX_POOL_NAME \
        --location=global
  8. Crie um contentor do Cloud Storage para os dados de entrada e outro para armazenar os resultados:

    gcloud storage buckets create gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_RESULTS_BUCKET_NAME
  9. Crie um ficheiro que contenha apenas o salário de Alex como um número:

    echo 123456 > ALEX_SALARY.txt
  10. Encriptar o ficheiro e, de seguida, carregá-lo para o contentor de 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

Configure os recursos da Bola

Para configurar os recursos para o Bola, conclua as seguintes instruções.

  1. No Cloud Shell, introduza o seguinte comando para criar um projeto para a Bola, substituindo BOLA_PROJECT_ID por um nome à sua escolha:

    gcloud projects create BOLA_PROJECT_ID
  2. Mude para o projeto recém-criado:

    gcloud config set project BOLA_PROJECT_ID
  3. Se ainda não o fez, ative as APIs que o Bola requer como colaborador de dados e operador de carga de trabalho:

    gcloud services enable cloudkms.googleapis.com iamcredentials.googleapis.com compute.googleapis.com confidentialcomputing.googleapis.com
  4. Crie um conjunto de chaves e uma chave de encriptação com o 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. Conceda à Bola a função cloudkms.cryptoKeyEncrypter para que possa usar a chave de encriptação criada recentemente para encriptar dados:

    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. Crie o Workload Identity Pool da Bola:

    gcloud iam workload-identity-pools create BOLA_POOL_NAME \
        --location=global
  7. Crie um contentor do Cloud Storage para os dados de entrada e outro para armazenar os resultados:

    gcloud storage buckets create gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_RESULTS_BUCKET_NAME
  8. Crie um ficheiro que contenha apenas o salário de Bola como um número:

    echo 111111 > BOLA_SALARY.txt
  9. Encriptar o ficheiro e, em seguida, carregá-lo para o contentor de 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

Crie uma conta de serviço para a carga de trabalho

Neste guia, a Bola opera e executa a carga de trabalho, mas qualquer pessoa pode assumir estas funções, incluindo um terceiro. A instância de VM que o Bola cria para executar a carga de trabalho tem uma conta de serviço anexada, que tem autorização para gerar tokens de atestação, escrever registos, ler os dados encriptados do Alex e do Bola, e escrever resultados em contentores específicos do Cloud Storage.

Conclua os passos seguintes no projeto de Bola para configurar a conta de serviço:

  1. Crie uma conta de serviço para executar a carga de trabalho:

    gcloud iam service-accounts create WORKLOAD_SERVICE_ACCOUNT_NAME
    
  2. Conceda à Bola a função de iam.serviceAccountUser para que possa anexar a conta de serviço à VM de carga de trabalho mais tarde:

    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. Conceda à conta de serviço a função confidentialcomputing.workloadUser para que possa gerar um token de atestação:

    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. Conceda à conta de serviço a função logging.logWriter para escrever registos no Cloud Logging, para que possa verificar o progresso da carga de trabalho:

    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. Conceda à conta de serviço acesso de leitura aos contentores de Alex e Bola que contêm os respetivos dados encriptados, e acesso de escrita aos contentores de resultados de cada um:

    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
    

    Isto pressupõe que o utilizador que concede o acesso tem a função de administrador do armazenamento (roles/storage.admin) para o projeto que contém o contentor do Cloud Storage no qual está a ser realizada a operação.

Crie a carga de trabalho

Neste guia, o Alex fornece o código para a carga de trabalho e cria uma imagem do Docker para a conter, mas qualquer pessoa pode assumir estas funções, incluindo terceiros.

O Alex tem de criar os seguintes recursos para a carga de trabalho:

  • O código que executa a carga de trabalho.

  • Um repositório Docker no Artifact Registry ao qual a conta de serviço que executa a carga de trabalho tem acesso.

  • Uma imagem do Docker que contém e executa o código da carga de trabalho.

Para criar e configurar os recursos, conclua os seguintes passos no projeto de Alex:

  1. Mudar para o projeto do Alex:

    gcloud config set project ALEX_PROJECT_ID
    
  2. Crie um repositório Docker no Artifact Registry:

    gcloud artifacts repositories create REPOSITORY_NAME \
        --repository-format=docker \
        --location=us
    
  3. Conceda à conta de serviço que vai executar a carga de trabalho a função Leitor do Artifact Registry (roles/artifactregistry.reader) para que possa ler a partir do repositório:

    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. Clique em Abrir editor para abrir o editor do Cloud Shell e, de seguida, crie um novo ficheiro denominado salary.go. Copie o código seguinte para o ficheiro e, de seguida, guarde-o:

    // READ ME FIRST: Before compiling, customize the details in the USER VARIABLES
    // SECTION starting at line 30.
    
    package main
    
    import (
      "context"
      "fmt"
      "io"
      "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
      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
    
    // 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
    
    // END USER VARIABLES SECTION
    // ==========================
    
    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,
        collaborator1BucketOutputFileName,
      },
      {
        collaborator2Name,
        "projects/" + collaborator2ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator2PoolName + "/providers/attestation-verifier",
        "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"
            }
            }`
    
    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.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
    }
    
  5. Modifique o USER VARIABLES SECTION no código fonte, substituindo os valores const vazios pelos nomes dos recursos relevantes, conforme descrito nos comentários do código. Se tiver editado as variáveis de marcadores de posição, como ALEX_PROJECT_ID, apresentadas neste guia, os valores são incluídos no seguinte exemplo de código, que pode copiar e colar sobre o código existente:

    // 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
    
    // 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
    

    Certifique-se de que também atualiza os números dos projetos de ambos os funcionários, Alex e Bola. Pode obtê-los com o seguinte comando:

    gcloud projects describe PROJECT_ID --format="value(projectNumber)"
    
  6. Certifique-se de que todas as partes leem e auditam o código-fonte.

  7. Clique em Terminal > Novo terminal para abrir um terminal no editor do Cloud Shell.

  8. Introduza os seguintes comandos no terminal para configurar o ambiente 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. Introduza o seguinte comando para compilar o código-fonte num ficheiro binário estaticamente associado:

    CGO_ENABLED=0 go build -trimpath
    
  10. Crie um ficheiro denominado Dockerfile no editor do Cloud Shell com o seguinte conteúdo:

    FROM alpine:latest
    WORKDIR /test
    COPY salary /test
    ENTRYPOINT ["/test/salary"]
    CMD []
    
  11. Atualize as suas credenciais do Docker para incluir o nome do domínio us-docker.pkg.dev:

    gcloud auth configure-docker us-docker.pkg.dev
    
  12. Crie uma imagem de Docker a partir de Dockerfile introduzindo o seguinte comando no terminal:

    docker build -t \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest .
    
  13. Envie a imagem de Docker para o Artifact Registry:

    docker push \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME
    
  14. Na resposta do comando Docker push, copie o resumo da imagem Docker (incluindo o prefixo sha256:) para um local seguro. É usado mais tarde para autorizar a carga de trabalho.

  15. Certifique-se de que todas as partes auditam a imagem do Docker e verificam se é fidedigna antes de autorizar a respetiva utilização.

Autorize a carga de trabalho

Com a carga de trabalho aprovada por ambas as partes, Alex e Bola têm de adicionar a atestação do Google Cloud como um fornecedor aos respetivos conjuntos de identidades da carga de trabalho. O fornecedor especifica o serviço de atestação a usar e as propriedades que a carga de trabalho tem de corresponder para poder operar nos dados de Alex ou Bola. Se um interveniente malicioso alterar a imagem do Docker ou alterar outra propriedade medida, o acesso ao volume de trabalho é recusado.

Este guia usa mapeamentos de atributos para fornecer acesso direto aos recursos à carga de trabalho com base no resumo da imagem. No entanto, noutras situações, pode preferir usar a representação da conta de serviço para aceder aos recursos. Consulte o artigo Acesso a cargas de trabalho externas para saber mais.

Para configurar os fornecedores para o Alex e a Bola com as condições necessárias, conclua os seguintes passos:

  1. Introduza o seguinte comando para criar o fornecedor para o 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' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  2. Conceda à identidade federada definida pelo fornecedor de Alex a função cloudkms.cryptoKeyDecrypter, especificando o atributo image_digest, para que apenas os contentores de carga de trabalho com o resumo especificado possam desencriptar as respetivas chaves do 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
    
  3. Mudar para o projeto de Bola:

    gcloud config set project BOLA_PROJECT_ID
    
  4. Introduza o seguinte comando para criar o fornecedor para 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' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  5. Conceda à identidade federada definida pelo fornecedor da Bola a função cloudkms.cryptoKeyDecrypter, especificando o atributo image_digest para que apenas os contentores de cargas de trabalho com o resumo especificado possam desencriptar as respetivas chaves do 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
    

Implemente a carga de trabalho

Com os fornecedores adicionados aos Workload Identity Pools de Alex e Bola e os recursos necessários implementados, é altura de o operador da carga de trabalho executar a carga de trabalho.

Para implementar a carga de trabalho, crie uma nova instância de VM confidencial no projeto de Bola com as seguintes propriedades:

  • Uma configuração suportada para uma instância de VM confidencial com AMD SEV, Intel TDX ou Intel TDX com NVIDIA Confidential Computing (pré-visualização).

  • Um SO baseado na imagem do Confidential Space.

  • Arranque seguro ativado.

  • A imagem do Docker anexada que o Alex criou anteriormente.

  • A conta de serviço anexada que executa a carga de trabalho.

Introduza o seguinte comando no Cloud Shell de Bola para implementar a carga de trabalho:

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"

Pode ver o progresso da carga de trabalho no projeto de Bola acedendo ao Explorador de registos.

Aceda ao Explorador de registos

Para encontrar os registos do espaço confidencial, filtre pelos seguintes campos de registo, se estiverem disponíveis:

  • Tipo de recurso: instância de VM

  • ID da instância: o ID da instância da VM

  • Nome do registo: confidential-space-launcher

Para atualizar o registo, clique em Ir para agora.

Quando a carga de trabalho termina, a instância de VM para. Se quiser alterar os ficheiros de salários encriptados e implementar novamente a carga de trabalho, só tem de iniciar a VM existente:

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

Veja os resultados

Após a conclusão bem-sucedida da carga de trabalho, o Alex e a Bola podem ver os resultados nos respetivos contentores de resultados:

  1. Mudar para o projeto do Alex:

    gcloud config set project ALEX_PROJECT_ID
    
  2. Liste todos os ficheiros no respetivo conjunto de resultados:

    gcloud storage ls gs://ALEX_RESULTS_BUCKET_NAME
    

    Em seguida, leia o ficheiro mais recente:

    gcloud storage cat gs://ALEX_RESULTS_BUCKET_NAME/ALEX_RESULTS_FILE_NAME
    
  3. Mudar para o projeto de Bola:

    gcloud config set project BOLA_PROJECT_ID
    
  4. Para Bola, liste todos os ficheiros no respetivo contentor de resultados:

    gcloud storage ls gs://BOLA_RESULTS_BUCKET_NAME
    

    Em seguida, leia o ficheiro mais recente:

    gcloud storage cat gs://BOLA_RESULTS_BUCKET_NAME/BOLA_RESULTS_FILE_NAME
    

Ao lerem os ficheiros, o Alex e a Bola descobrem quem ganha mais sem nunca revelarem os seus salários um ao outro.

Depure a carga de trabalho

Pode usar o Explorador de registos para resolver problemas como recursos não configurados corretamente ou condições de atributos em fornecedores que não correspondem às reivindicações feitas pela carga de trabalho do espaço confidencial.

Para o fazer, tem de efetuar as seguintes alterações:

  • Atualize os fornecedores do Workload Identity Pool da Alex e do Bola para remover a declaração support_attributes. Tem de usar a imagem de depuração do Confidential Space para realizar uma resolução de problemas mais detalhada, e essa imagem não tem atributos de suporte para validar.

  • Crie a VM de carga de trabalho com a imagem de depuração do espaço confidencial e defina os metadados da VM para redirecionar STDOUT e STDERR para o Cloud Logging para capturar todo o resultado da carga de trabalho.

Para fazer as alterações, conclua os seguintes passos:

  1. Mudar para o projeto do Alex:

    gcloud config set project ALEX_PROJECT_ID
    
  2. Atualize o fornecedor de Alex para remover a declaração 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=\"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'"
    
  3. Mudar para o projeto de Bola:

    gcloud config set project BOLA_PROJECT_ID
    
  4. Atualize o fornecedor de Bola para remover a declaração 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=\"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'"
    
  5. Crie uma nova VM com a imagem de depuração do espaço confidencial e tee-container-log-redirect defina como true nos metadados.

    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"
    

Ao contrário da imagem de produção, a imagem de depuração mantém a VM em execução após a conclusão da carga de trabalho. Isto significa que pode usar o SSH para estabelecer ligação à sua VM e continuar a depurar.

Limpeza

Para remover os recursos criados neste guia, conclua as seguintes instruções.

Limpe os recursos do Alex

  1. Mudar para o projeto do Alex:

    gcloud config set project ALEX_PROJECT_ID
    
  2. Elimine o Workload Identity Pool do Alex:

    gcloud iam workload-identity-pools delete ALEX_POOL_NAME \
        --location=global
    
  3. Elimine os contentores do Cloud Storage de Alex:

    gcloud storage rm gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_RESULTS_BUCKET_NAME --recursive
    
  4. Eliminar os ficheiros de salário do Alex e o código Go:

    rm ALEX_SALARY.txt \
        ALEX_ENCRYPTED_SALARY_FILE \
        salary.go salary \
        go.mod go.sum
    
  5. Opcional: desative ou destrua a chave do Cloud Key Management Service de Alex.

  6. Opcional: Encerra o projeto do Alex.

Limpe os recursos do Bola

  1. Mudar para o projeto de Bola:

    gcloud config set project BOLA_PROJECT_ID
    
  2. Elimine a VM de carga de trabalho:

    gcloud compute instances delete WORKLOAD_VM_NAME --zone=us-west1-b
    
  3. Elimine a conta de serviço que executa a carga de trabalho:

    gcloud iam service-accounts delete \
        WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com
    
  4. Elimine o Workload Identity Pool de Bola:

    gcloud iam workload-identity-pools delete BOLA_POOL_NAME \
        --location=global
    
  5. Elimine os contentores do Cloud Storage de Bola:

    gcloud storage rm gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_RESULTS_BUCKET_NAME --recursive
    
  6. Elimine os ficheiros de salário de Bola:

    rm BOLA_SALARY.txt \
        BOLA_ENCRYPTED_SALARY_FILE
    
  7. Opcional: desative ou destrua a chave do Cloud Key Management Service da Bola.

  8. Opcional: Encerra o projeto do Bola.